]> git.wh0rd.org - home.git/commitdiff
git-rb-all: fix update-ref usage with current HEAD, and make nops faster
authorMike Frysinger <vapier@gentoo.org>
Mon, 15 Feb 2021 13:13:00 +0000 (08:13 -0500)
committerMike Frysinger <vapier@gentoo.org>
Mon, 15 Feb 2021 13:13:00 +0000 (08:13 -0500)
.bin/git-rb-all

index d2c6737b95c1f9a3bb6ca4f784106654124e40a9..92834ac6aef213abf1af4cb6e2c3a821536c89cb 100755 (executable)
@@ -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