Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ git gtr new my-feature # Create worktree folder: my-feature
git gtr new my-feature --editor # Create and open in editor
git gtr new my-feature --ai # Create and start AI tool
git gtr new my-feature -e -a # Create, open editor, then start AI
git gtr pr 123 # Create worktree for pull request #123
git gtr editor my-feature # Open in cursor
git gtr ai my-feature # Start claude

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

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

### `git gtr pr <number|url|branch> [options]`

Create a worktree from a GitHub pull request. Requires GitHub CLI (`gh`) for PR lookup.
The default local branch is the PR head branch so GitHub CLI can infer the PR from inside the worktree.

```bash
git gtr pr 123 # Branch/folder from PR head branch
git gtr pr 123 --branch review/fix # Custom local branch
git gtr pr 123 --folder review # Custom folder name
gtr pr 123 --cd # Create and cd with shell integration
```

**Options:**

- `--branch <name>`, `-b`: Local branch name to use (default: PR head branch)
- `--repo <repo>`, `-R`: Repository for `gh pr view`
- `--remote <name>`: Override remote/repository used to fetch `refs/pull/<number>/head`
- `--no-copy`: Skip file copying
- `--no-hooks`: Skip post-create hooks
- `--force`: Allow same branch in multiple worktrees (**requires --name or --folder**)
- `--name <suffix>`: Custom folder name suffix
- `--folder <name>`: Custom folder name
- `--editor`, `-e`: Open in editor after creation
- `--ai`, `-a`: Start AI tool after creation
- `--yes`: Non-interactive mode

### `git gtr editor <branch> [--editor <name>]`

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

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

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

# Then navigate with:
gtr new my-feature --cd # Create and land in the new worktree
gtr pr 123 --cd # Create PR worktree and land in it
gtr cd # Interactive worktree picker (requires fzf)
gtr cd my-feature
gtr cd 1
Expand Down Expand Up @@ -390,7 +420,7 @@ git gtr config add gtr.copy.include "**/.env.example"
# Run setup after creating worktrees
git gtr config add gtr.hook.postCreate "npm install"

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

# Disable color output (or use "always" to force it)
Expand Down
3 changes: 3 additions & 0 deletions bin/git-gtr
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ main() {
new)
cmd_create "$@"
;;
pr)
cmd_pr "$@"
;;
rm)
cmd_remove "$@"
;;
Expand Down
23 changes: 23 additions & 0 deletions completions/_git-gtr
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ _git-gtr() {
local -a commands
commands=(
'new:Create a new worktree'
'pr:Create a pull request worktree'
'go:Navigate to worktree'
'run:Execute command in worktree'
'copy:Copy files between worktrees'
Expand Down Expand Up @@ -78,6 +79,28 @@ _git-gtr() {
return
fi

# Early handler for `pr` command
if (( CURRENT >= 4 )) && [[ $words[3] == pr ]]; then
_arguments \
'1:pull request:' \
'--branch[Local branch name]:branch:' \
'-b[Local branch name]:branch:' \
'--repo[GitHub repository]:repo:' \
'-R[GitHub repository]:repo:' \
'--remote[Remote used to fetch PR ref]:remote:' \
'--no-copy[Skip file copying]' \
'--no-hooks[Skip post-create hooks]' \
'--force[Allow same branch in multiple worktrees]' \
'--name[Custom folder name suffix]:name:' \
'--folder[Custom folder name (replaces default)]:folder:' \
'--yes[Non-interactive mode]' \
'--editor[Open in editor after creation]' \
'-e[Open in editor after creation]' \
'--ai[Start AI tool after creation]' \
'-a[Start AI tool after creation]'
return
fi

# Early handler for `clean` command
if (( CURRENT >= 4 )) && [[ $words[3] == clean ]]; then
_arguments \
Expand Down
14 changes: 14 additions & 0 deletions completions/git-gtr.fish
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ end

# Commands
complete -f -c git -n '__fish_git_gtr_needs_command' -a new -d 'Create a new worktree'
complete -f -c git -n '__fish_git_gtr_needs_command' -a pr -d 'Create a pull request worktree'
complete -f -c git -n '__fish_git_gtr_needs_command' -a go -d 'Navigate to worktree'
complete -f -c git -n '__fish_git_gtr_needs_command' -a run -d 'Execute command in worktree'
complete -f -c git -n '__fish_git_gtr_needs_command' -a rm -d 'Remove worktree(s)'
Expand Down Expand Up @@ -73,6 +74,19 @@ complete -c git -n '__fish_git_gtr_using_command new' -l yes -d 'Non-interactive
complete -c git -n '__fish_git_gtr_using_command new' -s e -l editor -d 'Open in editor after creation'
complete -c git -n '__fish_git_gtr_using_command new' -s a -l ai -d 'Start AI tool after creation'

# Pull request command options
complete -c git -n '__fish_git_gtr_using_command pr' -s b -l branch -d 'Local branch name' -r
complete -c git -n '__fish_git_gtr_using_command pr' -s R -l repo -d 'GitHub repository' -r
complete -c git -n '__fish_git_gtr_using_command pr' -l remote -d 'Remote used to fetch PR ref' -r
complete -c git -n '__fish_git_gtr_using_command pr' -l no-copy -d 'Skip file copying'
complete -c git -n '__fish_git_gtr_using_command pr' -l no-hooks -d 'Skip post-create hooks'
complete -c git -n '__fish_git_gtr_using_command pr' -l force -d 'Allow same branch in multiple worktrees'
complete -c git -n '__fish_git_gtr_using_command pr' -l name -d 'Custom folder name suffix' -r
complete -c git -n '__fish_git_gtr_using_command pr' -l folder -d 'Custom folder name (replaces default)' -r
complete -c git -n '__fish_git_gtr_using_command pr' -l yes -d 'Non-interactive mode'
complete -c git -n '__fish_git_gtr_using_command pr' -s e -l editor -d 'Open in editor after creation'
complete -c git -n '__fish_git_gtr_using_command pr' -s a -l ai -d 'Start AI tool after creation'

# Remove command options
complete -c git -n '__fish_git_gtr_using_command rm' -l delete-branch -d 'Delete branch'
complete -c git -n '__fish_git_gtr_using_command rm' -l force -d 'Force removal even if worktree has uncommitted changes or untracked files'
Expand Down
7 changes: 6 additions & 1 deletion completions/gtr.bash
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ _git_gtr() {

# If we're completing the first argument after 'git gtr'
if [ "$cword" -eq 2 ]; then
COMPREPLY=($(compgen -W "new go run copy editor ai rm mv rename ls list clean doctor adapter config completion init trust help version" -- "$cur"))
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"))
return 0
fi

Expand Down Expand Up @@ -104,6 +104,11 @@ _git_gtr() {
COMPREPLY=($(compgen -W "auto remote local none" -- "$cur"))
fi
;;
pr)
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "--branch -b --repo -R --remote --no-copy --no-hooks --force --name --folder --yes --editor -e --ai -a" -- "$cur"))
fi
;;
completion)
# Complete with shell names
if [ "$cword" -eq 3 ]; then
Expand Down
6 changes: 3 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ git gtr config add gtr.hook.preRemove "npm run cleanup"
# Post-remove hooks
git gtr config add gtr.hook.postRemove "echo 'Cleaned up!'"

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

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

> **Note:** Pre-remove hooks abort removal on failure. Use `--force` to skip failed hooks.
>
> **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.
> **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.

**Environment variables available in hooks:**

Expand Down
8 changes: 1 addition & 7 deletions lib/commands/create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,7 @@ cmd_create() {

# Construct folder name for display
local folder_name
if [ -n "$folder_override" ]; then
folder_name=$(sanitize_branch_name "$folder_override")
elif [ -n "$custom_name" ]; then
folder_name="$(sanitize_branch_name "$branch_name")-${custom_name}"
else
folder_name=$(sanitize_branch_name "$branch_name")
fi
folder_name=$(_resolve_folder_name "$branch_name" "$custom_name" "$folder_override") || exit 1

log_step "Creating worktree: $folder_name"
echo "Location: $base_dir/${prefix}${folder_name}"
Expand Down
64 changes: 57 additions & 7 deletions lib/commands/help.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,38 @@ Examples:
EOF
}

_help_pr() {
cat <<'EOF'
git gtr pr - Create a worktree for a GitHub pull request

Usage: git gtr pr <number|url|branch> [options]

Creates a worktree from a GitHub pull request, similar to gh pr checkout.
The default local branch is the PR head branch so GitHub CLI can infer the PR
from inside the worktree. Requires GitHub CLI (gh) for PR lookup.

Options:
-b, --branch <name> Local branch name to use (default: PR head branch)
-R, --repo <repo> Select GitHub repository for gh pr view
--remote <name> Override remote/repository used to fetch refs/pull/<number>/head
--no-copy Skip file copying (gtr.copy.include patterns)
--no-hooks Skip post-create hooks
--force Allow same branch in multiple worktrees
(requires --name or --folder to distinguish worktrees)
--name <suffix> Custom folder name suffix (appended after branch name)
--folder <name> Custom folder name (replaces default entirely)
--yes Non-interactive mode (skip prompts)
-e, --editor Open in editor after creation
-a, --ai Start AI tool after creation

Examples:
git gtr pr 123 # Branch/folder from PR head branch
git gtr pr 123 --branch review/fix # Custom local branch
git gtr pr https://github.com/OWNER/REPO/pull/123 --folder review
gtr pr 123 --cd # With shell integration
EOF
}

_help_editor() {
cat <<'EOF'
git gtr editor - Open worktree in editor
Expand Down Expand Up @@ -367,8 +399,9 @@ git gtr init - Generate shell integration

Usage: git gtr init <shell> [--as <name>]

Generates shell functions for enhanced features like 'gtr cd <branch>'
and 'gtr new <branch> --cd', which can change the current shell directory.
Generates shell functions for enhanced features like 'gtr cd <branch>',
'gtr new <branch> --cd', and 'gtr pr <number> --cd', which can change the
current shell directory.
Add to your shell configuration.

Output is cached to ~/.cache/gtr/ for fast shell startup (~1ms vs ~60ms).
Expand All @@ -385,24 +418,25 @@ Options:
Setup (sources cached output directly for fast startup):
# Bash (add to ~/.bashrc)
_gtr_init="${XDG_CACHE_HOME:-$HOME/.cache}/gtr/init-gtr.bash"
[[ -f "$_gtr_init" ]] || eval "$(git gtr init bash)" || true
[[ -f "$_gtr_init" ]] && head -n 1 "$_gtr_init" | grep -q ' init=6 ' || eval "$(git gtr init bash)" || true
source "$_gtr_init" 2>/dev/null || true; unset _gtr_init

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

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

# Custom function name (avoids conflict with coreutils gtr)
eval "$(git gtr init zsh --as gwtr)"

After setup:
gtr new my-feature --cd # create and cd into worktree
gtr pr 123 --cd # create PR worktree and cd
gtr cd my-feature # cd to worktree
gtr cd 1 # cd to main repo
gtr cd # interactive picker (requires fzf)
Expand Down Expand Up @@ -526,6 +560,20 @@ CORE COMMANDS (daily workflow):
-e, --editor: open in editor after creation
-a, --ai: start AI tool after creation

pr <number|url|branch> [options]
Create a worktree from a GitHub pull request (requires gh)
-b, --branch <name>: local branch name (default: PR head branch)
-R, --repo <repo>: repository for gh pr view
--remote <name>: override remote/repository used to fetch refs/pull/<number>/head
--no-copy: skip file copying
--no-hooks: skip post-create hooks
--force: allow same branch in multiple worktrees (requires --name or --folder)
--name <suffix>: custom folder name suffix
--folder <name>: custom folder name
--yes: non-interactive mode
-e, --editor: open in editor after creation
-a, --ai: start AI tool after creation

editor <branch> [--editor <name>]
Open worktree in editor (uses gtr.editor.default or --editor)
Special: use '1' to open repo root
Expand Down Expand Up @@ -626,7 +674,7 @@ SETUP & MAINTENANCE:
Usage: eval "$(git gtr completion zsh)"

init <shell> [--as <name>]
Generate shell integration for gtr cd and gtr new --cd (bash, zsh, fish)
Generate shell integration for gtr cd, gtr new --cd, and gtr pr --cd (bash, zsh, fish)
--as <name>: custom function name (default: gtr)
Output is cached for fast startup (refreshes when 'git gtr init' runs)
See git gtr help init for recommended setup
Expand All @@ -646,6 +694,7 @@ WORKFLOW EXAMPLES:

# Daily workflow
git gtr new feature/user-auth # Create worktree (folder: feature-user-auth)
git gtr pr 123 # Create worktree for pull request #123
git gtr editor feature/user-auth # Open in editor
git gtr ai feature/user-auth # Start AI tool

Expand All @@ -655,6 +704,7 @@ WORKFLOW EXAMPLES:

# Navigate to worktree directory
gtr new hotfix --cd # Create and cd into worktree (with shell integration)
gtr pr 123 --cd # Create PR worktree and cd
gtr cd # Interactive picker (requires fzf)
gtr cd feature/user-auth # With shell integration (git gtr init)
cd "$(git gtr go feature/user-auth)" # Without shell integration
Expand Down Expand Up @@ -706,7 +756,7 @@ CONFIGURATION OPTIONS:
gtr.hook.postCreate Post-create hooks (multi-valued)
gtr.hook.preRemove Pre-remove hooks (multi-valued, abort on failure)
gtr.hook.postRemove Post-remove hooks (multi-valued)
gtr.hook.postCd Post-cd hooks (multi-valued, gtr cd / gtr new --cd only)
gtr.hook.postCd Post-cd hooks (multi-valued, gtr cd / gtr new --cd / gtr pr --cd only)
gtr.ui.color Color output mode (auto, always, never; default: auto)

────────────────────────────────────────────────────────────────────────────────
Expand Down
Loading