Skip to main content

git/gpr: improve handling of stacked PRs

ID
a874725
date
2026-08-13 15:50:06+00:00
author
Alex Chan <alexc@tailscale.com>
parent
57e5ee3
message
git/gpr: improve handling of stacked PRs

Rather than opening every PR against `main`, look for remote branches
between the latest commit and main that might be existing PRs.
changed files
1 file, 66 additions, 5 deletions

Changed files

git/gpr (155 → 1680)

diff --git a/git/gpr b/git/gpr
index b153387..323fc7c 100755
--- a/git/gpr
+++ b/git/gpr
@@ -1,7 +1,68 @@
-#!/usr/bin/env bash
-# Open a GitHub pull request for the current branch.
+#!/usr/bin/env python3
 
-set -o errexit
-set -o nounset
+import subprocess
+import webbrowser
 
-open "$(_get_github_url)/compare/$(gcb)?expand=1"
+
+def git(*args: str) -> str:
+    """
+    Run a Git command and return the output.
+    """
+    cmd = ["git"] + list(args)
+    return subprocess.check_output(cmd, text=True).strip()
+
+
+def get_parent(commit_id: str) -> str:
+    """
+    Return the commit ID of the parent of this commit.
+    """
+    return git("log", "--pretty=%P", "-n", "1", commit_id)
+
+
+def get_remote_branches(commit_id: str) -> list[str]:
+    """
+    Return a list of remote branches where this commit is the tip.
+    """
+    branches = git(
+        "for-each-ref",
+        "--format=%(refname:short)",
+        "refs/remotes/",
+        "--points-at",
+        commit_id,
+    ).splitlines()
+    return [b for b in branches if b != "origin"]
+
+
+def get_local_branches(commit_id: str) -> list[str]:
+    """
+    Return a list of local branches where this commit is the tip.
+    """
+    return git(
+        "for-each-ref",
+        "--format=%(refname:short)",
+        "refs/heads/",
+        "--points-at",
+        commit_id,
+    ).splitlines
+
+
+if __name__ == "__main__":
+    current_branch = git("rev-parse", "--abbrev-ref", "HEAD")
+    base_branch = "main"
+
+    commit = git("rev-parse", "HEAD")
+    for _ in range(5):
+        parent = get_parent(commit)
+
+        if rb := get_remote_branches(parent):
+            base_branch = rb[0].replace("origin/", "")
+            break
+
+        if lb := get_local_branches(parent):
+            if lb[0] == "main":
+                break
+
+    github_url = subprocess.check_output("_get_github_url", text=True).strip()
+
+    url = f"{github_url}/compare/{base_branch}...{current_branch}?expand=1"
+    webbrowser.open(url)