From 3080f80ea6c444073cd956d07bcf1d342d8b95b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Gl=C3=BCpker?= Date: Mon, 4 Jun 2018 18:57:06 +0200 Subject: Initial released version --- updateGit.py | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100755 updateGit.py diff --git a/updateGit.py b/updateGit.py new file mode 100755 index 0000000..10acc45 --- /dev/null +++ b/updateGit.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 + +import os +import subprocess +import sys +from concurrent.futures import ThreadPoolExecutor + +SAFEBRANCHES = ['master', 'develop', 'rc'] +DEBUG = False + +# TODO: local changes, branch updated. Is there a way to know, whether rebase would be successful? If yes: do so + +def dPrint(*args, **kwargs): + if DEBUG: + print(' '.join(map(str, args)), **kwargs) + +def getStdout(command): + process = None + process = subprocess.run(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if process.returncode != 0: + print('Failed to execute command:', command) + if process.stdout: + print(' Stdout:') + print(process.stdout.decode('UTF-8')) + if process.stderr: + print(' Stderr:') + print(process.stderr.decode('UTF-8')) + return '' + if process.stdout: + return process.stdout.decode('UTF-8').strip() + return '' + +def getGitStdout(path, command): + dPrint('[ running', command, 'on', path, ']') + if isinstance(command, str): + cmd = 'git -C %s %s' % (path, command) + return getStdout(cmd.split()) + if isinstance(command, list): + cmd = ['git', '-C', path] + cmd.extend(command) + return getStdout(cmd) + +def findChanges(statusoutput): + dPrint('Parsing for changes:') + for line in statusoutput.split('\n'): + dPrint(' Status Line:', line) + if line.startswith('A ') or line.startswith('D ') or line.startswith('M '): + return True + return False + +def updatePath(path): + friendly_path = os.path.basename(path) + dPrint('Repository:', path) + head = getGitStdout(path, 'rev-parse --abbrev-ref HEAD') + changed = findChanges(getGitStdout(path, 'status --porcelain')) + cur_branch = getGitStdout(path, 'rev-parse --abbrev-ref HEAD') + + # Fetch changes, delete removed repositories + getGitStdout(path, 'fetch --all --prune') + + # Go through branches and merge with upstream + branches = getGitStdout(path, [ + 'for-each-ref', + '--format=%(refname:short)#%(upstream)#%(upstream:track,nobracket)', + 'refs/heads' + ]) + for branch in branches.split('\n'): + local_branch, upstream_branch, tracking = branch.split('#') + if not upstream_branch or tracking == 'gone': + print('[%s]' % friendly_path, local_branch, 'has no upstream branch.') + continue + if local_branch == cur_branch and changed: + print('[%s]' % friendly_path, 'has a working branch with changes, skipped.') + continue + dPrint('Update for', friendly_path, local_branch, upstream_branch) + local = getGitStdout(path, ['rev-parse', '--quiet', '--verify', local_branch]) + remote = getGitStdout(path, ['rev-parse', '--quiet', '--verify', upstream_branch]) + base = getGitStdout(path, ['merge-base', local_branch, upstream_branch]) + dPrint(' Branch:', local_branch) + dPrint(' Upstream:', upstream_branch) + dPrint(' Local', local) + dPrint(' Remote', remote) + dPrint(' Base', base) + if local == remote: + continue + if local == base: + if local_branch == cur_branch: + print('[%s]' % friendly_path, 'Updated branch ', cur_branch, + getGitStdout(path, ['merge', remote])) + else: + print('[%s] Updated branch %s:' % (friendly_path, cur_branch), + getGitStdout(path, ['branch', '-f', local_branch, remote])) + if remote == base: + print('[%s] %s is ahead. Consider pushing your changes.' % (friendly_path, local_branch)) + + # Find safe branch (master/development/rc) + branches = getGitStdout(path, [ + 'for-each-ref', + '--format=%(refname:short)', + 'refs/heads' + ]).split('\n') + for branch in SAFEBRANCHES: + if branch in branches: + safe_branch = branch + break + + # No safe branch found. What is this? + if not safe_branch: + print('Does the repository', friendly_path, 'not have any safe branch?') + return + + # print('Safe branch:', safe_branch) + + # Checkout master, if detached HEAD and not proceeded + if head == 'HEAD': + if changed: + print('[%s] Head detached, but changes found. Stash them to checkout %s.' % ( + friendly_path, + safe_branch + )) + else: + headrev = getGitStdout(path, 'rev-parse --quiet --verify HEAD') + mergerev = getGitStdout(path, ['merge-base', 'HEAD', safe_branch]) + if headrev == mergerev: + print('[%s] Head detached, checking %s out.' % (friendly_path, safe_branch), + getGitStdout(path, 'checkout ' + safe_branch)) + else: + print('[%s] Head detached, but not part of safe branch!' % friendly_path) + + # Delete branches, that were already merged to master + merged_branches = getGitStdout(path, [ + 'branch', + '--format=%(refname:short)', + '--merged=%s' % safe_branch + ]).split('\n') + # print('[%s]' % friendly_path, 'Deletion of branches', merged_branches, SAFEBRANCHES, cur_branch) + for merged_branch in merged_branches: + if merged_branch in SAFEBRANCHES: + continue + if merged_branch == cur_branch: + continue + get_tracking_status = getGitStdout(path, [ + 'for-each-ref', + '--format=%(upstream:track,nobracket)', + 'refs/heads/%s' % merged_branch + ]) + if get_tracking_status.strip() == 'gone': + print('[%s]' % friendly_path, 'Removing branch by force, upstream is gone.') + print('[%s]' % friendly_path, + getGitStdout(path, ['branch', '-D', merged_branch])) + else: + print('[%s]' % friendly_path, + getGitStdout(path, ['branch', '-d', merged_branch])) + +def main(): + paths = [] + if len(sys.argv) == 1: + paths.append('.') + else: + for path in sys.argv[1:]: + paths.append(path) + + repos = [] + for path in paths: + for subdir, dirs, _ in os.walk(path): + # dPrint(subdir, dir, files) + if '.git' in dirs: + repos.append(subdir) + + if len(repos) == 0: + print('No repositories found.') + return + + print('Updating', len(repos), 'repositories.') + executor = ThreadPoolExecutor(max_workers=len(repos)) + for repo in repos: + # updatePath(subdir) + executor.submit(updatePath, repo) + executor.shutdown() + + +if __name__ == "__main__": + main() -- cgit v1.2.3