Skip to content

Commit f5eee45

Browse files
scarf005chatgpt-codex-connector[bot]helizaga
authored
Add PR worktree checkout command (#187)
* feat: add pull request worktree command Assisted-by: pi:gpt-5.5 Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> * fix: make pr worktrees gh-aware Assisted-by: pi:gpt-5.5 Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> * fix: harden pr worktree checkout Assisted-by: pi:gpt-5.5 Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> * fix: address pr review feedback Assisted-by: pi:gpt-5.5 Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> --------- Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com> Co-authored-by: Tom Elizaga <tom.elizaga@gmail.com>
1 parent 1b510ff commit f5eee45

16 files changed

Lines changed: 918 additions & 63 deletions

File tree

README.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ git gtr new my-feature # Create worktree folder: my-feature
9393
git gtr new my-feature --editor # Create and open in editor
9494
git gtr new my-feature --ai # Create and start AI tool
9595
git gtr new my-feature -e -a # Create, open editor, then start AI
96+
git gtr pr 123 # Create worktree for pull request #123
9697
git gtr editor my-feature # Open in cursor
9798
git gtr ai my-feature # Start claude
9899

@@ -101,6 +102,7 @@ git gtr run my-feature npm test # Run tests
101102

102103
# Navigate to worktree
103104
gtr new my-feature --cd # Create and cd (requires shell integration)
105+
gtr pr 123 --cd # Create a PR worktree and cd
104106
gtr cd # Interactive picker (requires fzf + shell integration)
105107
gtr cd my-feature # Requires shell integration (see below)
106108
cd "$(git gtr go my-feature)" # Alternative without shell integration
@@ -123,6 +125,7 @@ While `git worktree` is powerful, it's verbose and manual. `git gtr` adds qualit
123125
| ----------------- | ------------------------------------------ | ---------------------------------------- |
124126
| Create worktree | `git worktree add ../repo-feature feature` | `git gtr new feature` |
125127
| Create + open | `git worktree add ... && cursor .` | `git gtr new feature --editor` |
128+
| Checkout PR | `gh pr checkout 123` | `git gtr pr 123` |
126129
| Open in editor | `cd ../repo-feature && cursor .` | `git gtr editor feature` |
127130
| Start AI tool | `cd ../repo-feature && claude` | `git gtr ai feature` |
128131
| Copy config files | Manual copy/paste | Auto-copy via `gtr.copy.include` |
@@ -184,6 +187,34 @@ git gtr new my-feature --name descriptive-variant
184187
- `--ai`, `-a`: Start AI tool after creation
185188
- `--yes`: Non-interactive mode
186189

190+
### `git gtr pr <number|url|branch> [options]`
191+
192+
Create a worktree from a GitHub pull request. Requires GitHub CLI (`gh`) for PR lookup.
193+
When supported, GTR delegates checkout to `gh pr checkout --worktree`; otherwise it
194+
uses a compatibility path that preserves configured Git remotes and protocols.
195+
The default local branch is the PR head branch so GitHub CLI can infer the PR from inside the worktree.
196+
197+
```bash
198+
git gtr pr 123 # Branch/folder from PR head branch
199+
git gtr pr 123 --branch review/fix # Custom local branch
200+
git gtr pr 123 --folder review # Custom folder name
201+
gtr pr 123 --cd # Create and cd with shell integration
202+
```
203+
204+
**Options:**
205+
206+
- `--branch <name>`, `-b`: Local branch name to use (default: PR head branch)
207+
- `--repo <repo>`, `-R`: Repository for `gh pr view`
208+
- `--remote <name>`: Override remote/repository used to fetch `refs/pull/<number>/head`
209+
- `--no-copy`: Skip file copying
210+
- `--no-hooks`: Skip post-create hooks
211+
- `--force`: Allow same branch in multiple worktrees (**requires --name or --folder**)
212+
- `--name <suffix>`: Custom folder name suffix
213+
- `--folder <name>`: Custom folder name
214+
- `--editor`, `-e`: Open in editor after creation
215+
- `--ai`, `-a`: Start AI tool after creation
216+
- `--yes`: Non-interactive mode
217+
187218
### `git gtr editor <branch> [--editor <name>]`
188219

189220
Open worktree in editor (uses `gtr.editor.default` or `--editor` flag).
@@ -218,21 +249,22 @@ cd "$(git gtr go 1)" # Navigate to main repo
218249
```bash
219250
# Bash (add to ~/.bashrc)
220251
_gtr_init="${XDG_CACHE_HOME:-$HOME/.cache}/gtr/init-gtr.bash"
221-
[[ -f "$_gtr_init" ]] || eval "$(git gtr init bash)" || true
252+
[[ -f "$_gtr_init" ]] && head -n 1 "$_gtr_init" | grep -q ' init=6 ' || eval "$(git gtr init bash)" || true
222253
source "$_gtr_init" 2>/dev/null || true; unset _gtr_init
223254

224255
# Zsh (add to ~/.zshrc)
225256
_gtr_init="${XDG_CACHE_HOME:-$HOME/.cache}/gtr/init-gtr.zsh"
226-
[[ -f "$_gtr_init" ]] || eval "$(git gtr init zsh)" || true
257+
[[ -f "$_gtr_init" ]] && head -n 1 "$_gtr_init" | grep -q ' init=6 ' || eval "$(git gtr init zsh)" || true
227258
source "$_gtr_init" 2>/dev/null || true; unset _gtr_init
228259

229260
# Fish (add to ~/.config/fish/config.fish)
230261
set -l _gtr_init (test -n "$XDG_CACHE_HOME" && echo $XDG_CACHE_HOME || echo $HOME/.cache)/gtr/init-gtr.fish
231-
test -f "$_gtr_init"; or git gtr init fish >/dev/null 2>&1
262+
test -f "$_gtr_init"; and head -n 1 "$_gtr_init" | string match -q '* init=6 *'; or git gtr init fish >/dev/null 2>&1
232263
source "$_gtr_init" 2>/dev/null
233264

234265
# Then navigate with:
235266
gtr new my-feature --cd # Create and land in the new worktree
267+
gtr pr 123 --cd # Create PR worktree and land in it
236268
gtr cd # Interactive worktree picker (requires fzf)
237269
gtr cd my-feature
238270
gtr cd 1
@@ -393,7 +425,7 @@ git gtr config add gtr.hook.postCreate "npm install"
393425
# Inherit sparse-checkout from the base worktree on Git 2.36+ (default: on)
394426
git gtr config set gtr.sparse.inherit true
395427

396-
# Re-source environment after gtr cd or gtr new --cd (runs in current shell)
428+
# Re-source environment after gtr cd, gtr new --cd, or gtr pr --cd (runs in current shell)
397429
git gtr config add gtr.hook.postCd "source ./vars.sh"
398430

399431
# Disable color output (or use "always" to force it)

bin/git-gtr

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ main() {
5858
new)
5959
cmd_create "$@"
6060
;;
61+
pr)
62+
cmd_pr "$@"
63+
;;
6164
rm)
6265
cmd_remove "$@"
6366
;;

completions/_git-gtr

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ _git-gtr() {
2929
local -a commands
3030
commands=(
3131
'new:Create a new worktree'
32+
'pr:Create a pull request worktree'
3233
'go:Navigate to worktree'
3334
'run:Execute command in worktree'
3435
'copy:Copy files between worktrees'
@@ -80,6 +81,28 @@ _git-gtr() {
8081
return
8182
fi
8283

84+
# Early handler for `pr` command
85+
if (( CURRENT >= 4 )) && [[ $words[3] == pr ]]; then
86+
_arguments \
87+
'1:pull request:' \
88+
'--branch[Local branch name]:branch:' \
89+
'-b[Local branch name]:branch:' \
90+
'--repo[GitHub repository]:repo:' \
91+
'-R[GitHub repository]:repo:' \
92+
'--remote[Remote used to fetch PR ref]:remote:' \
93+
'--no-copy[Skip file copying]' \
94+
'--no-hooks[Skip post-create hooks]' \
95+
'--force[Allow same branch in multiple worktrees]' \
96+
'--name[Custom folder name suffix]:name:' \
97+
'--folder[Custom folder name (replaces default)]:folder:' \
98+
'--yes[Non-interactive mode]' \
99+
'--editor[Open in editor after creation]' \
100+
'-e[Open in editor after creation]' \
101+
'--ai[Start AI tool after creation]' \
102+
'-a[Start AI tool after creation]'
103+
return
104+
fi
105+
83106
# Early handler for `clean` command
84107
if (( CURRENT >= 4 )) && [[ $words[3] == clean ]]; then
85108
_arguments \

completions/git-gtr.fish

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ end
3535

3636
# Commands
3737
complete -f -c git -n '__fish_git_gtr_needs_command' -a new -d 'Create a new worktree'
38+
complete -f -c git -n '__fish_git_gtr_needs_command' -a pr -d 'Create a pull request worktree'
3839
complete -f -c git -n '__fish_git_gtr_needs_command' -a go -d 'Navigate to worktree'
3940
complete -f -c git -n '__fish_git_gtr_needs_command' -a run -d 'Execute command in worktree'
4041
complete -f -c git -n '__fish_git_gtr_needs_command' -a rm -d 'Remove worktree(s)'
@@ -75,6 +76,19 @@ complete -c git -n '__fish_git_gtr_using_command new' -l yes -d 'Non-interactive
7576
complete -c git -n '__fish_git_gtr_using_command new' -s e -l editor -d 'Open in editor after creation'
7677
complete -c git -n '__fish_git_gtr_using_command new' -s a -l ai -d 'Start AI tool after creation'
7778

79+
# Pull request command options
80+
complete -c git -n '__fish_git_gtr_using_command pr' -s b -l branch -d 'Local branch name' -r
81+
complete -c git -n '__fish_git_gtr_using_command pr' -s R -l repo -d 'GitHub repository' -r
82+
complete -c git -n '__fish_git_gtr_using_command pr' -l remote -d 'Remote used to fetch PR ref' -r
83+
complete -c git -n '__fish_git_gtr_using_command pr' -l no-copy -d 'Skip file copying'
84+
complete -c git -n '__fish_git_gtr_using_command pr' -l no-hooks -d 'Skip post-create hooks'
85+
complete -c git -n '__fish_git_gtr_using_command pr' -l force -d 'Allow same branch in multiple worktrees'
86+
complete -c git -n '__fish_git_gtr_using_command pr' -l name -d 'Custom folder name suffix' -r
87+
complete -c git -n '__fish_git_gtr_using_command pr' -l folder -d 'Custom folder name (replaces default)' -r
88+
complete -c git -n '__fish_git_gtr_using_command pr' -l yes -d 'Non-interactive mode'
89+
complete -c git -n '__fish_git_gtr_using_command pr' -s e -l editor -d 'Open in editor after creation'
90+
complete -c git -n '__fish_git_gtr_using_command pr' -s a -l ai -d 'Start AI tool after creation'
91+
7892
# Remove command options
7993
complete -c git -n '__fish_git_gtr_using_command rm' -l delete-branch -d 'Delete branch'
8094
complete -c git -n '__fish_git_gtr_using_command rm' -l force -d 'Force removal even if worktree has uncommitted changes or untracked files'

completions/gtr.bash

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ _git_gtr() {
2525

2626
# If we're completing the first argument after 'git gtr'
2727
if [ "$cword" -eq 2 ]; then
28-
COMPREPLY=($(compgen -W "new go run copy editor ai rm mv rename ls list clean doctor adapter config completion init trust help version" -- "$cur"))
28+
COMPREPLY=($(compgen -W "new pr go run copy editor ai rm mv rename ls list clean doctor adapter config completion init trust help version" -- "$cur"))
2929
return 0
3030
fi
3131

@@ -104,6 +104,11 @@ _git_gtr() {
104104
COMPREPLY=($(compgen -W "auto remote local none" -- "$cur"))
105105
fi
106106
;;
107+
pr)
108+
if [[ "$cur" == -* ]]; then
109+
COMPREPLY=($(compgen -W "--branch -b --repo -R --remote --no-copy --no-hooks --force --name --folder --yes --editor -e --ai -a" -- "$cur"))
110+
fi
111+
;;
107112
completion)
108113
# Complete with shell names
109114
if [ "$cword" -eq 3 ]; then

docs/configuration.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ git gtr config add gtr.hook.preRemove "npm run cleanup"
337337
# Post-remove hooks
338338
git gtr config add gtr.hook.postRemove "echo 'Cleaned up!'"
339339

340-
# Post-cd hooks (run after gtr cd or gtr new --cd, in current shell)
340+
# Post-cd hooks (run after gtr cd, gtr new --cd, or gtr pr --cd, in current shell)
341341
git gtr config add gtr.hook.postCd "source ./vars.sh"
342342
```
343343

@@ -348,11 +348,11 @@ git gtr config add gtr.hook.postCd "source ./vars.sh"
348348
| `postCreate` | After worktree creation | Setup, install dependencies |
349349
| `preRemove` | Before worktree deletion | Cleanup requiring directory access |
350350
| `postRemove` | After worktree deletion | Notifications, logging |
351-
| `postCd` | After `gtr cd` or `gtr new --cd` changes directory | Re-source environment, update shell context |
351+
| `postCd` | After `gtr cd`, `gtr new --cd`, or `gtr pr --cd` changes directory | Re-source environment, update shell context |
352352

353353
> **Note:** Pre-remove hooks abort removal on failure. Use `--force` to skip failed hooks.
354354
>
355-
> **Note:** `postCd` hooks run in the **current shell** (not a subshell) so they can modify environment variables. They only run via shell integration (`gtr cd`, `gtr new --cd`), not raw `git gtr` commands or `git gtr go`. Failures warn but don't undo the directory change.
355+
> **Note:** `postCd` hooks run in the **current shell** (not a subshell) so they can modify environment variables. They only run via shell integration (`gtr cd`, `gtr new --cd`, `gtr pr --cd`), not raw `git gtr` commands or `git gtr go`. Failures warn but don't undo the directory change.
356356

357357
**Environment variables available in hooks:**
358358

lib/commands/create.sh

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,7 @@ cmd_create() {
197197

198198
# Construct folder name for display
199199
local folder_name
200-
if [ -n "$folder_override" ]; then
201-
folder_name=$(sanitize_branch_name "$folder_override")
202-
elif [ -n "$custom_name" ]; then
203-
folder_name="$(sanitize_branch_name "$branch_name")-${custom_name}"
204-
else
205-
folder_name=$(sanitize_branch_name "$branch_name")
206-
fi
200+
folder_name=$(_resolve_folder_name "$branch_name" "$custom_name" "$folder_override") || exit 1
207201

208202
log_step "Creating worktree: $folder_name"
209203
echo "Location: $base_dir/${prefix}${folder_name}"

lib/commands/help.sh

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,40 @@ Examples:
4343
EOF
4444
}
4545

46+
_help_pr() {
47+
cat <<'EOF'
48+
git gtr pr - Create a worktree for a GitHub pull request
49+
50+
Usage: git gtr pr <number|url|branch> [options]
51+
52+
Creates a worktree from a GitHub pull request, similar to gh pr checkout.
53+
Uses gh's native worktree checkout when available and a compatible fallback
54+
otherwise, preserving configured Git remotes and protocols.
55+
The default local branch is the PR head branch so GitHub CLI can infer the PR
56+
from inside the worktree. Requires GitHub CLI (gh) for PR lookup.
57+
58+
Options:
59+
-b, --branch <name> Local branch name to use (default: PR head branch)
60+
-R, --repo <repo> Select GitHub repository for gh pr view
61+
--remote <name> Override remote/repository used to fetch refs/pull/<number>/head
62+
--no-copy Skip file copying (gtr.copy.include patterns)
63+
--no-hooks Skip post-create hooks
64+
--force Allow same branch in multiple worktrees
65+
(requires --name or --folder to distinguish worktrees)
66+
--name <suffix> Custom folder name suffix (appended after branch name)
67+
--folder <name> Custom folder name (replaces default entirely)
68+
--yes Non-interactive mode (skip prompts)
69+
-e, --editor Open in editor after creation
70+
-a, --ai Start AI tool after creation
71+
72+
Examples:
73+
git gtr pr 123 # Branch/folder from PR head branch
74+
git gtr pr 123 --branch review/fix # Custom local branch
75+
git gtr pr https://github.com/OWNER/REPO/pull/123 --folder review
76+
gtr pr 123 --cd # With shell integration
77+
EOF
78+
}
79+
4680
_help_editor() {
4781
cat <<'EOF'
4882
git gtr editor - Open worktree in editor
@@ -369,8 +403,9 @@ git gtr init - Generate shell integration
369403
370404
Usage: git gtr init <shell> [--as <name>]
371405
372-
Generates shell functions for enhanced features like 'gtr cd <branch>'
373-
and 'gtr new <branch> --cd', which can change the current shell directory.
406+
Generates shell functions for enhanced features like 'gtr cd <branch>',
407+
'gtr new <branch> --cd', and 'gtr pr <number> --cd', which can change the
408+
current shell directory.
374409
Add to your shell configuration.
375410
376411
Output is cached to ~/.cache/gtr/ for fast shell startup (~1ms vs ~60ms).
@@ -387,24 +422,25 @@ Options:
387422
Setup (sources cached output directly for fast startup):
388423
# Bash (add to ~/.bashrc)
389424
_gtr_init="${XDG_CACHE_HOME:-$HOME/.cache}/gtr/init-gtr.bash"
390-
[[ -f "$_gtr_init" ]] || eval "$(git gtr init bash)" || true
425+
[[ -f "$_gtr_init" ]] && head -n 1 "$_gtr_init" | grep -q ' init=6 ' || eval "$(git gtr init bash)" || true
391426
source "$_gtr_init" 2>/dev/null || true; unset _gtr_init
392427
393428
# Zsh (add to ~/.zshrc)
394429
_gtr_init="${XDG_CACHE_HOME:-$HOME/.cache}/gtr/init-gtr.zsh"
395-
[[ -f "$_gtr_init" ]] || eval "$(git gtr init zsh)" || true
430+
[[ -f "$_gtr_init" ]] && head -n 1 "$_gtr_init" | grep -q ' init=6 ' || eval "$(git gtr init zsh)" || true
396431
source "$_gtr_init" 2>/dev/null || true; unset _gtr_init
397432
398433
# Fish (add to ~/.config/fish/config.fish)
399434
set -l _gtr_init (test -n "$XDG_CACHE_HOME" && echo $XDG_CACHE_HOME || echo $HOME/.cache)/gtr/init-gtr.fish
400-
test -f "$_gtr_init"; or git gtr init fish >/dev/null 2>&1
435+
test -f "$_gtr_init"; and head -n 1 "$_gtr_init" | string match -q '* init=6 *'; or git gtr init fish >/dev/null 2>&1
401436
source "$_gtr_init" 2>/dev/null
402437
403438
# Custom function name (avoids conflict with coreutils gtr)
404439
eval "$(git gtr init zsh --as gwtr)"
405440
406441
After setup:
407442
gtr new my-feature --cd # create and cd into worktree
443+
gtr pr 123 --cd # create PR worktree and cd
408444
gtr cd my-feature # cd to worktree
409445
gtr cd 1 # cd to main repo
410446
gtr cd # interactive picker (requires fzf)
@@ -528,6 +564,20 @@ CORE COMMANDS (daily workflow):
528564
-e, --editor: open in editor after creation
529565
-a, --ai: start AI tool after creation
530566
567+
pr <number|url|branch> [options]
568+
Create a worktree from a GitHub pull request (requires gh)
569+
-b, --branch <name>: local branch name (default: PR head branch)
570+
-R, --repo <repo>: repository for gh pr view
571+
--remote <name>: override remote/repository used to fetch refs/pull/<number>/head
572+
--no-copy: skip file copying
573+
--no-hooks: skip post-create hooks
574+
--force: allow same branch in multiple worktrees (requires --name or --folder)
575+
--name <suffix>: custom folder name suffix
576+
--folder <name>: custom folder name
577+
--yes: non-interactive mode
578+
-e, --editor: open in editor after creation
579+
-a, --ai: start AI tool after creation
580+
531581
editor <branch> [--editor <name>]
532582
Open worktree in editor (uses gtr.editor.default or --editor)
533583
Special: use '1' to open repo root
@@ -628,7 +678,7 @@ SETUP & MAINTENANCE:
628678
Usage: eval "$(git gtr completion zsh)"
629679
630680
init <shell> [--as <name>]
631-
Generate shell integration for gtr cd and gtr new --cd (bash, zsh, fish)
681+
Generate shell integration for gtr cd, gtr new --cd, and gtr pr --cd (bash, zsh, fish)
632682
--as <name>: custom function name (default: gtr)
633683
Output is cached for fast startup (refreshes when 'git gtr init' runs)
634684
See git gtr help init for recommended setup
@@ -648,6 +698,7 @@ WORKFLOW EXAMPLES:
648698
649699
# Daily workflow
650700
git gtr new feature/user-auth # Create worktree (folder: feature-user-auth)
701+
git gtr pr 123 # Create worktree for pull request #123
651702
git gtr editor feature/user-auth # Open in editor
652703
git gtr ai feature/user-auth # Start AI tool
653704
@@ -657,6 +708,7 @@ WORKFLOW EXAMPLES:
657708
658709
# Navigate to worktree directory
659710
gtr new hotfix --cd # Create and cd into worktree (with shell integration)
711+
gtr pr 123 --cd # Create PR worktree and cd
660712
gtr cd # Interactive picker (requires fzf)
661713
gtr cd feature/user-auth # With shell integration (git gtr init)
662714
cd "$(git gtr go feature/user-auth)" # Without shell integration
@@ -709,7 +761,7 @@ CONFIGURATION OPTIONS:
709761
gtr.hook.postCreate Post-create hooks (multi-valued)
710762
gtr.hook.preRemove Pre-remove hooks (multi-valued, abort on failure)
711763
gtr.hook.postRemove Post-remove hooks (multi-valued)
712-
gtr.hook.postCd Post-cd hooks (multi-valued, gtr cd / gtr new --cd only)
764+
gtr.hook.postCd Post-cd hooks (multi-valued, gtr cd / gtr new --cd / gtr pr --cd only)
713765
gtr.ui.color Color output mode (auto, always, never; default: auto)
714766
715767
────────────────────────────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)