]> git.wh0rd.org - home.git/blobdiff - .bin/git-rb-all
cros-board: update
[home.git] / .bin / git-rb-all
index a2f7922a9c84358ed6edca4bcb612b3fcfd0a547..5c3f305a01866ad42856f77215f5b881603d95a5 100755 (executable)
@@ -7,6 +7,7 @@ import functools
 import os
 from pathlib import Path
 import re
+import shlex
 import subprocess
 import sys
 
@@ -16,6 +17,7 @@ assert sys.version_info >= (3, 6), f'Python 3.6+ required but found {sys.version
 
 
 PROG = os.path.basename(__file__)
+DEBUG = False
 
 
 class Terminal:
@@ -58,6 +60,12 @@ class Color:
         return cls.BAD + msg + cls.NORMAL
 
 
+def dbg(*args, **kwargs):
+    """Print a debug |msg|."""
+    if DEBUG:
+        print(*args, file=sys.stderr, **kwargs)
+
+
 def fatal(msg):
     """Show an error |msg| then exit."""
     print(Color.bad(f'{PROG}: error: {msg}'), file=sys.stderr)
@@ -70,8 +78,17 @@ def git(args, **kwargs):
     kwargs.setdefault('stdout', subprocess.PIPE)
     kwargs.setdefault('stderr', subprocess.STDOUT)
     kwargs.setdefault('encoding', 'utf-8')
-    #print('+', 'git', *args)
-    return subprocess.run(['git'] + args, **kwargs)
+    if DEBUG:
+        dbg('+', 'git', shlex.join(args))
+    ret = subprocess.run(['git'] + args, **kwargs)
+    if DEBUG:
+        if ret.stdout:
+            dbg(ret.stdout.rstrip())
+        if ret.stderr:
+            dbg('stderr =', ret.stderr)
+        if ret.returncode:
+            dbg('++ exit', ret.returncode)
+    return ret
 
 
 @functools.lru_cache(maxsize=None)
@@ -102,6 +119,36 @@ def cherry_pick_inprogress():
     return Path(output).exists()
 
 
+@functools.lru_cache(maxsize=None)
+def top_dir() -> Path:
+    """Find the top dir of the git checkout."""
+    output = git(['rev-parse', '--show-toplevel'], stderr=subprocess.PIPE).stdout.strip()
+    return Path(output).resolve()
+
+
+@functools.lru_cache(maxsize=None)
+def git_dir() -> Path:
+    """Find the internal git dir for this project."""
+    output = git(['rev-parse', '--git-dir']).stdout.strip()
+    return Path(output).resolve()
+
+
+@functools.lru_cache(maxsize=None)
+def worktree_is_local(worktree: str) -> bool:
+    """See whether |worktree| is the cwd git repo."""
+    if not worktree:
+        return True
+
+    # If .git is a symlink, worktree result might be the target.
+    if worktree == str(git_dir().resolve()):
+        return True
+
+    # NB: worktree path is supposed to be absolute from for-each-ref, but it's
+    # not always, so we have to resolve it.  https://crbug.com/git/88
+    worktree = (git_dir() / worktree).resolve()
+    return worktree == top_dir()
+
+
 class AppendOption(argparse.Action):
     """Append the command line option (with no arguments) to dest.
 
@@ -127,6 +174,9 @@ def get_parser():
     parser.add_argument(
         '--catchup', action='store_true',
         help='run git-rb-catchup when rebasing')
+    parser.add_argument(
+        '-d', '--debug', action='store_true',
+        help='enable debug output')
     parser.add_argument(
         '-q', '--quiet', dest='git_options', action=AppendOption, default=['-q'],
         help='passthru to git rebase')
@@ -138,8 +188,14 @@ def main(argv):
     parser = get_parser()
     opts = parser.parse_args(argv)
 
+    global DEBUG
+    DEBUG = opts.debug
+
     # Switch to the top dir in case the working dir doesn't exist in every branch.
-    topdir = git(['rev-parse', '--show-toplevel']).stdout.strip()
+    try:
+        topdir = top_dir()
+    except subprocess.CalledProcessError as e:
+        sys.exit(f'{os.path.basename(sys.argv[0])}: {Path.cwd()}:\n{e}\n{e.stderr.strip()}')
     os.chdir(topdir)
 
     # Example output:
@@ -159,8 +215,11 @@ def main(argv):
         branch_width = max(branch_width, len(branch))
         if head == '*':
             curr_state = branch
-        if not (worktreepath and worktreepath != topdir):
             local_count += 1
+        elif worktree_is_local(worktreepath):
+            local_count += 1
+        else:
+            dbg(f'{worktreepath}:{branch}: Skipping branch checked out in diff worktree')
     # If there are no branches to rebase, go silently.
     if not local_count:
         return 0
@@ -176,7 +235,8 @@ def main(argv):
         _, worktreepath, branch, tracking, ahead_behind = line.split('|')
 
         # If it's a branch in another worktree, ignore it.
-        if worktreepath and worktreepath != topdir:
+        if not worktree_is_local(worktreepath):
+            dbg(f'{worktreepath}:{branch}: Skipping branch checked out in diff worktree')
             continue
 
         print(f'{Color.BRACKET}### {Color.GOOD}{branch:{branch_width}}{Color.NORMAL} ',
@@ -229,7 +289,7 @@ def main(argv):
         else:
             result = git(['rebase'] + opts.git_options, check=False)
             if result.returncode:
-                git(['rebase', '--abort'])
+                git(['rebase', '--abort'], check=False)
                 print(Color.bad('failed') + '\n' + result.stdout.strip())
             else:
                 print(Color.good('OK'))