Neels Hofmeyr has submitted this change and it was merged. ( https://gerrit.osmocom.org/11706 )
Change subject: gits: use git plumbing commands ...................................................................... gits: use git plumbing commands Instead of 'git status' and 'git branch', which change their output depending on the git version and locale, use the low-level plumbing commands. 'gits status' output is exactly the same, 'gits rebase' output is a bit less redundant now (that was easier to implement). Change-Id: I42544313d14db126c99e2d9a02b8f63031944947 --- M src/gits 1 file changed, 92 insertions(+), 92 deletions(-) Approvals: osmith: Verified Neels Hofmeyr: Looks good to me, approved diff --git a/src/gits b/src/gits index 81083f1..8b75278 100755 --- a/src/gits +++ b/src/gits @@ -18,7 +18,6 @@ import sys import subprocess -import re import argparse import os import shlex @@ -28,11 +27,6 @@ helps to save your time with: status, fetch, rebase, ... ''' -re_status_mods = re.compile('^\t(modified|deleted):.*') -re_status_branch_name = re.compile('On branch ([^ ]*)') -re_branch_name = re.compile('^..([^ ]+) .*') -re_ahead_behind = re.compile('ahead [0-9]+|behind [0-9]+') - def error(*msgs): sys.stderr.write(''.join(msgs)) @@ -67,52 +61,69 @@ return subprocess.check_output(['git', '-C', git_dir, ] + list(args)).decode('utf-8') -def git_branch(git_dir): - status = git_output(git_dir, 'status', '--long') - m = re_status_branch_name.find(status) - if not m: - error('No current branch in %r' % git_dir) - return m.group(1) +def git_bool(git_dir, *args): + try: + subprocess.check_output(['git', '-C', git_dir, ] + list(args)) + return True + except subprocess.CalledProcessError as e: + return False -def git_status(git_dir, verbose=False): - status_lines = git_output(git_dir, 'status').splitlines() - if verbose and len(status_lines): - print(status_lines[0]) +def git_branch_exists(git_dir, branch='origin/master'): + return git_bool(git_dir, 'rev-parse', '--quiet', '--verify', branch) - on_branch = None - branch_status_str = None - local_mods = False - ON_BRANCH = 'On branch ' - STATUS = 'Your branch' +def git_ahead_behind(git_dir, branch='master', remote='origin'): + ''' Count revisions ahead/behind of the remote branch. + returns: (ahead, behind) (e.g. (0, 5)) ''' - for l in status_lines: - if l.startswith(ON_BRANCH): - if on_branch: - error('cannot parse status, more than one branch?') - on_branch = l[len(ON_BRANCH):] - elif l.startswith(STATUS): - if 'Your branch is up to date' in l: - branch_status_str = l - elif 'Your branch is up-to-date' in l: - branch_status_str = l - elif 'Your branch is ahead' in l: - branch_status_str = 'ahead: ' + l - elif 'Your branch is behind' in l: - branch_status_str = 'behind: ' + l - elif 'have diverged' in l: - branch_status_str = 'diverged: ' + l - else: - error('unknown status str: %r' % l) - else: - m = re_status_mods.match(l) - if m: - local_mods = True + # Missing remote branch + if not git_branch_exists(git_dir, remote + '/' + branch): + return (0, 0) - if verbose: - print('%s%s' % (branch_status_str, '\nLOCAL MODS' if local_mods else '')) - return (on_branch, branch_status_str, local_mods) + behind = git_output(git_dir, 'rev-list', '--count', '%s..%s/%s' % (branch, remote, branch)) + ahead = git_output(git_dir, 'rev-list', '--count', '%s/%s..%s' % (remote, branch, branch)) + return (int(ahead.rstrip()), int(behind.rstrip())) + + +def git_branches(git_dir, obj='refs/heads'): + ret = git_output(git_dir, 'for-each-ref', obj, '--format', '%(refname:short)') + return ret.splitlines() + + +def git_branch_current(git_dir): + ret = git_output(git_dir, 'rev-parse', '--abbrev-ref', 'HEAD').rstrip() + if ret == 'HEAD': + return None + return ret + + +def git_has_modifications(git_dir): + return not git_bool(git_dir, 'diff-index', '--quiet', 'HEAD') + + +def git_can_fast_forward(git_dir, branch='master', remote='origin'): + return git_bool(git_dir, 'merge-base', '--is-ancestor', 'HEAD', remote + '/' + branch) + + +def format_branch_ahead_behind(branch, ahead, behind): + ''' branch: string like "master" + ahead, behind: integers like 5, 3 + returns: string like "master", "master[+5]", "master[-3]", "master[+5|-3]" ''' + # Just the branch + if not ahead and not behind: + return branch + + # Suffix with ahead/behind + ret = branch + '[' + if ahead: + ret += '+' + str(ahead) + if behind: + ret += '|' + if behind: + ret += '-' + str(behind) + ret += ']' + return ret def git_branch_summary(git_dir): @@ -122,36 +133,22 @@ interesting_branch_names = ('master',) strs = [git_dir, ] - - on_branch, branch_status_str, has_mods = git_status(git_dir) - - if has_mods: + if git_has_modifications(git_dir): strs.append('MODS') - branch_strs = git_output(git_dir, 'branch', '-vv').splitlines() - - for line in branch_strs: - m = re_branch_name.match(line) - name = m.group(1) - - current_branch = False - if line.startswith('*'): - current_branch = True - elif name not in interesting_branch_names: + branch_current = git_branch_current(git_dir) + for branch in git_branches(git_dir): + is_current = (branch == branch_current) + if not is_current and branch not in interesting_branch_names: continue - ahead_behind = re_ahead_behind.findall(line) - if not ahead_behind and not current_branch: + + ahead, behind = git_ahead_behind(git_dir, branch) + if not ahead and not behind and not is_current: # skip branches that are "not interesting" continue - ahead_behind = [ - x.replace('ahead ', '+').replace('behind ', '-') for x in ahead_behind] - branch_info = name - if ahead_behind: - branch_info = branch_info + ('[%s]' % '|'.join(ahead_behind)) - - strs.append(''.join(branch_info)) - + # Branch with ahead/behind origin info ("master[+1|-5]") + strs.append(format_branch_ahead_behind(branch, ahead, behind)) return strs @@ -236,14 +233,15 @@ def rebase(git_dir): - orig_branch, branch_status_str, local_mods = git_status( - git_dir, verbose=True) - + orig_branch = git_branch_current(git_dir) if orig_branch is None: print('Not on a branch: %s' % git_dir) raise SkipThisRepo() - if local_mods: + print('Rebasing branch: ' + orig_branch) + ahead, behind = git_ahead_behind(git_dir, orig_branch) + + if git_has_modifications(git_dir): do_commit = ask(git_dir, 'Local mods.', 'c commit to this branch', '<name> commit to new branch', @@ -259,17 +257,29 @@ git(git_dir, 'commit', '-am', 'wip', may_fail=True) git(git_dir, 'checkout', orig_branch) - _, _, local_mods = git_status(git_dir) - - if local_mods: + if git_has_modifications(git_dir): print('There still are local modifications') raise SkipThisRepo() - if branch_status_str is None: + # Missing upstream branch + if not git_branch_exists(git_dir, 'origin/' + orig_branch): print('there is no upstream branch for %r' % orig_branch) - elif branch_status_str.startswith('behind'): - if 'and can be fast-forwarded' in branch_status_str: + # Diverged + elif ahead and behind: + do_reset = ask(git_dir, 'Diverged.', + '%s: git reset --hard origin/%s?' % ( + orig_branch, orig_branch), + '<empty> no', + 'OK yes (write OK in caps!)', + valid_answers=('', 'OK')) + + if do_reset == 'OK': + git(git_dir, 'reset', '--hard', 'origin/%s' % orig_branch) + + # Behind + elif behind: + if git_can_fast_forward(git_dir, orig_branch): print('fast-forwarding...') git(git_dir, 'merge') else: @@ -282,7 +292,8 @@ if do_merge == 'ok': git(git_dir, 'merge') - elif branch_status_str.startswith('ahead'): + # Ahead + elif ahead: do_commit = ask(git_dir, 'Ahead. commit to new branch?', '<empty> no', '<name> create new branch', @@ -300,17 +311,6 @@ if do_reset == 'OK': git(git_dir, 'reset', '--hard', 'origin/%s' % orig_branch) - elif branch_status_str.startswith('diverged'): - do_reset = ask(git_dir, 'Diverged.', - '%s: git reset --hard origin/%s?' % ( - orig_branch, orig_branch), - '<empty> no', - 'OK yes (write OK in caps!)', - valid_answers=('', 'OK')) - - if do_reset == 'OK': - git(git_dir, 'reset', '--hard', 'origin/%s' % orig_branch) - return orig_branch -- To view, visit https://gerrit.osmocom.org/11706 To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: osmo-dev Gerrit-Branch: master Gerrit-MessageType: merged Gerrit-Change-Id: I42544313d14db126c99e2d9a02b8f63031944947 Gerrit-Change-Number: 11706 Gerrit-PatchSet: 1 Gerrit-Owner: osmith <osm...@sysmocom.de> Gerrit-Reviewer: Neels Hofmeyr <nhofm...@sysmocom.de> Gerrit-Reviewer: osmith <osm...@sysmocom.de>