Skip to content

feat(shell): PR-aware, main-checkout-first git checkout/switch - #47

Merged
potsrevennil merged 6 commits into
mainfrom
claude/smart-git-checkout-pr-32bbfe
Oct 20, 2026
Merged

potsrevennil merged 6 commits into
mainfrom
claude/smart-git-checkout-pr-32bbfe

Conversation

@potsrevennil

@potsrevennil potsrevennil commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Extends the zsh git wrapper in modules/shells/zsh/zshrc (also used by omz gco/gsw).

What changes

  • PRs via gh. git checkout #123, a PR URL, or a bare number that is neither a rev nor a path lands on the PR's exact head commit. If a local branch's tip is already that commit, it switches to that branch, preferring the one named like the PR's branch. Otherwise gh pr checkout creates a new branch named after the PR's branch, or pr-<n>, or pr-<n>-<sha> if those are taken. A local branch that has moved ahead of the PR is never reused.
  • Main checkout by default. A bare git checkout|switch <branch|pr> from any worktree now switches the main checkout and cds there. Before, it jumped to whichever worktree held the branch.
  • -. for the current worktree. gco dev -. (or gco -. dev) switches the current worktree instead. A bare . was rejected because git reads it as a pathspec, and without the wrapper git checkout dev . would overwrite files. Git rejects -. outright.
  • Freeing a held branch. If another linked worktree holds the branch, it is detached at the same commit to free it. A dirty worktree is left alone and the switch stops. If the switch then fails, the worktree is put back on its branch.
  • # stays literal. zsh-utils' completion plugin turns on EXTENDED_GLOB globally, which made gco #47 fail with bad pattern and nixpkgs#hello with no matches found. This turns it back off after plugins load; completion, p10k and fast-syntax-highlighting set it locally.
  • Not for agents. Under Claude Code ($CLAUDECODE) the wrapper passes straight to git, since agent shells snapshot this function and expect plain git in their own worktrees.
  • Commits, tags, paths and any other flags go straight to git. git worktree path <branch> is unchanged.

Reviewer notes

  • Checking out a claude/... branch from elsewhere takes it from that session's worktree if it is clean. Use -. or git worktree path when you only want to look.
  • Tested in scratch repos with a stub gh, originally across 13 cases: a free branch, a branch held by a clean or dirty worktree, -. in both positions, a branch the main repo already has, a failed switch with reattach, a commit, a remote-only branch, PRs with and without -., git grep -H passthrough, and plain git checkout .. The real gh pr checkout --branch path has not been run.
  • Overhead: the block adds ~0.14 ms to shell startup; other git commands take a fast path (~4 µs per call, vs ~6 ms for a git process). An intercepted checkout runs 3 extra git processes (one worktree list, one rev-parse, one for-each-ref).
  • CI lint only covers .nix files; zsh -n passes. 22 scratch-repo cases pass, including untracked-only holders, the current worktree's own branch, nested remote branch names, $CLAUDECODE, foreign PR URLs, all fallback names taken, -- -., and incomplete gh output.

🤖 Generated with Claude Code

potsrevennil and others added 6 commits October 15, 2026 13:16
With gh installed, a bare `git checkout|switch` (and omz gco/gsw) of
#123, a PR URL, or a number that is neither a rev nor a path lands on
the PR's exact head commit: stay put if HEAD is already there, else
switch to (or cd into the worktree of) a local branch whose tip is that
commit, preferring the PR's branch name. Only when no branch carries it
does `gh pr checkout` create one, named after the PR's branch unless
that name is taken (then pr-<n>, then pr-<n>-<sha>), so a local branch
that has moved ahead of the PR is never reused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bare `git checkout|switch <branch|pr>` from any worktree now switches
the main checkout and cds there, instead of jumping to whichever worktree
already has the branch; `-H` does it in the current worktree. A linked
worktree holding the branch is detached at the same commit to free it,
refused if it has uncommitted changes, and reattached if the switch then
fails. Commits, tags, paths and other flags still go straight to git.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`git checkout dev -.` (or `-. dev`) switches the current worktree
instead of the main checkout. Unlike a bare `.`, which git reads as a
pathspec and would restore files with if the wrapper were not loaded,
`-.` is rejected by git itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zsh-utils' completion plugin turns on EXTENDED_GLOB, which makes `#` a
glob operator, so `nixpkgs#hello` fails with "no matches found" and
`gco #47` with "bad pattern". `disable -p '#'` turns off just that
operator, keeping the rest of extended globbing; the `(#i)`-style glob
flags go with it. Replaces the commented-out `setopt noglob` attempt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pass straight to git under Claude Code ($CLAUDECODE): agents' shells
  snapshot this function and expect plain git in their own worktrees.
- Match remote branches exactly (<remote>/<name> for each remote), so a
  name that only tails a nested remote branch is not sent to the main
  checkout, where git would read it as a path and restore that file.
- Replace `disable -p '#'` with `unsetopt extended_glob`: the former
  silently broke `##` even in code that sets extendedglob locally.
- Ignore untracked files when deciding whether a holder worktree is
  dirty; detaching at the same commit never touches them.
- Checking out the current worktree's own branch stays put instead of
  detaching it and moving the branch to the main checkout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Error out when the PR's branch name, pr-<n> and pr-<n>-<sha> are all
  taken, instead of letting gh reuse the last one off the PR's commit.
- Count `-.` only before `--` (after it, it is a path) and keep
  empty arguments when stripping it.
- Refuse a PR URL whose owner/repo none of the remotes point at.
- Say so when gh returns incomplete PR info instead of failing silently.
- Read the main checkout and every branch's worktree from one
  `git worktree list`: an intercepted checkout now runs 3 extra git
  processes instead of 5-6, and other git commands take a fast path
  (~4 us) before any locals are set up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@potsrevennil
potsrevennil merged commit 26a5d33 into main Oct 20, 2026
7 checks passed
@potsrevennil
potsrevennil deleted the claude/smart-git-checkout-pr-32bbfe branch October 20, 2026 05:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant