#!/usr/bin/env python3 from contextlib import contextmanager from tempfile import TemporaryDirectory from pathlib import Path import argparse import os import shutil import subprocess import sys def absolute_path(path): return Path(path).resolve() def relative_path(path): path = Path(path) if path.is_absolute(): raise Exception(f'path `{path}` must be relative!') return path @contextmanager def chdir(cwd: Path): old = os.getcwd() os.chdir(cwd) try: yield cwd finally: os.chdir(old) def run(cmd, *args, **kwargs): print('$', ' '.join(cmd)) subprocess.check_call(cmd, *args, **kwargs) class GitRepo: def __init__(self, url): self.url = url self.cwd = TemporaryDirectory() subprocess.check_call([ 'git', 'clone', str(url), self.cwd.name, ]) def is_dirty(self): res = subprocess.check_output(['git', 'status', '--porcelain'], text=True).strip() return bool(res) def update(self, message, action, branch=None): with chdir(self.cwd.name): if not branch: cmd = ['git', 'branch', '--show-current'] branch = subprocess.check_output(cmd, text=True).strip() # Run action in repo's directory action() run(['git', 'add', '.']) if not self.is_dirty(): print('No changes detected, quitting') return run([ 'git', '-c', 'user.name=vipvap', '-c', 'user.email=vipvap@zenith.tech', 'commit', '--author="vipvap "', f'--message={message}', ]) for _ in range(5): try: run(['git', 'fetch', 'origin', branch]) run(['git', 'rebase', f'origin/{branch}']) run(['git', 'push', 'origin', branch]) return except subprocess.CalledProcessError as e: print(f'failed to update branch `{branch}`: {e}', file=sys.stderr) raise Exception(f'failed to update branch `{branch}`') def do_copy(args): src = args.src dst = args.dst if args.forbid_overwrite and dst.exists(): raise FileExistsError(f"File exists: '{dst}'") if src.is_dir(): shutil.rmtree(dst, ignore_errors=True) shutil.copytree(src, dst) else: shutil.copy(src, dst) def main(): parser = argparse.ArgumentParser(description='Git upload tool') parser.add_argument('--repo', type=str, metavar='URL', required=True, help='git repo url') parser.add_argument('--message', type=str, metavar='TEXT', help='commit message') commands = parser.add_subparsers(title='commands', dest='subparser_name') p_copy = commands.add_parser('copy', help='copy file into the repo') p_copy.add_argument('src', type=absolute_path, help='source path') p_copy.add_argument('dst', type=relative_path, help='relative dest path') p_copy.add_argument('--forbid-overwrite', action='store_true', help='do not allow overwrites') args = parser.parse_args() commands = { 'copy': do_copy, } action = commands.get(args.subparser_name) if action: message = args.message or 'update' GitRepo(args.repo).update(message, lambda: action(args)) else: parser.print_usage() if __name__ == '__main__': main()