From eb86c710971dc2a6e3e5995be4a3fefd85f1a67c Mon Sep 17 00:00:00 2001 From: Mike Frysinger Date: Mon, 15 Feb 2021 08:13:00 -0500 Subject: [PATCH] git-rb-all: fix update-ref usage with current HEAD, and make nops faster --- .bin/git-rb-all | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/.bin/git-rb-all b/.bin/git-rb-all index d2c6737..92834ac 100755 --- a/.bin/git-rb-all +++ b/.bin/git-rb-all @@ -3,6 +3,7 @@ """Helper to rebase all local branches.""" import argparse +import functools import os from pathlib import Path import re @@ -10,6 +11,10 @@ import subprocess import sys +# Not sure if newer features are used. +assert sys.version_info >= (3, 6), f'Python 3.6+ required but found {sys.version}' + + PROG = os.path.basename(__file__) @@ -54,12 +59,21 @@ def fatal(msg): def git(args, **kwargs): """Run git.""" kwargs.setdefault('check', True) - kwargs.setdefault('capture_output', True) + kwargs.setdefault('stdout', subprocess.PIPE) + kwargs.setdefault('stderr', subprocess.STDOUT) kwargs.setdefault('encoding', 'utf-8') #print('+', 'git', *args) return subprocess.run(['git'] + args, **kwargs) +@functools.lru_cache(maxsize=None) +def checkout_is_dirty(): + """Determine whether the checkout is dirty (e.g. modified files).""" + output = git(['status', '--porcelain']).stdout + return any(x for x in output.splitlines() if '?' not in x[0:2]) + + +@functools.lru_cache(maxsize=None) def rebase_inprogress(): """Determine whether a rebase is already in progress.""" output = git(['rev-parse', '--git-path', 'rebase-merge']).stdout.strip() @@ -142,10 +156,10 @@ def main(argv): if not curr_state: fatal('unable to resolve current branch') - is_dirty = None + switched_head = False branches = {} for line in state: - head, worktreepath, branch, tracking, ahead_behind = line.split('|') + _, worktreepath, branch, tracking, ahead_behind = line.split('|') # If it's a branch in another worktree, ignore it. if worktreepath and worktreepath != topdir: @@ -165,19 +179,26 @@ def main(argv): print('Up-to-date!') continue elif not ahead: - git(['update-ref', f'refs/heads/{branch}', tracking]) - print('fast forwarded') + # If we haven't switched the checkout, update-ref on current HEAD + # will get us into a dirty checkout, so use merge to handle it. + if switched_head or curr_state != branch: + git(['update-ref', f'refs/heads/{branch}', tracking]) + print('fast forwarded [updated ref]') + else: + result = git(['merge', '-q', '--ff-only'], check=False) + if result.returncode: + print(f'{Color.BAD}unable to merge{Color.NORMAL}\n' + result.stdout.strip()) + else: + print('fast forwarded [merged]') continue - if is_dirty is None: - output = git(['status', '--porcelain']).stdout - is_dirty = any(x for x in output.splitlines() if '?' not in x[0:2]) - if is_dirty: + if checkout_is_dirty(): print(f'{Color.BAD}unable to rebase: tree is dirty{Color.NORMAL}') continue print(f'rebasing [{ahead_behind}] ', end='', flush=True) git(['checkout', '-q', branch]) + switched_head = True if opts.catchup: print() result = git(['rb-catchup'], capture_output=False, check=False) @@ -189,7 +210,8 @@ def main(argv): else: print('OK!') - git(['checkout', '-q', curr_state]) + if switched_head: + git(['checkout', '-q', curr_state]) return 0 -- 2.39.2