Skip to content
Merged
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ git gtr ai my-feature # Start claude
git gtr run my-feature npm test # Run tests

# Navigate to worktree
gtr cd # Interactive picker (requires fzf + shell integration)
gtr cd my-feature # Requires: eval "$(git gtr init bash)"
cd "$(git gtr go my-feature)" # Alternative without shell integration

Expand Down Expand Up @@ -217,10 +218,13 @@ cd "$(git gtr go 1)" # Navigate to main repo
eval "$(git gtr init bash)"

# Then navigate with:
gtr cd # Interactive worktree picker (requires fzf)
gtr cd my-feature
gtr cd 1
```

With [fzf](https://github.com/junegunn/fzf) installed, `gtr cd` (no arguments) opens a command palette with git log preview and keybindings: `ctrl-e` editor, `ctrl-a` AI, `ctrl-d` delete, `ctrl-y` copy, `ctrl-r` refresh.

> **Note:** If `gtr` conflicts with another command (e.g., GNU `tr` from coreutils), use `--as` to pick a different name:
>
> ```bash
Expand Down
9 changes: 8 additions & 1 deletion lib/commands/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ cmd_doctor() {
# Check hosting provider
if [ -n "$repo_root" ]; then
local provider
provider=$(detect_provider 2>/dev/null)
provider=$(detect_provider 2>/dev/null) || true
if [ -n "$provider" ]; then
echo "[OK] Provider: $provider"
case "$provider" in
Expand All @@ -112,6 +112,13 @@ cmd_doctor() {
fi
fi

# Check fzf (optional, for interactive picker)
if command -v fzf >/dev/null 2>&1; then
echo "[OK] fzf: $(fzf --version 2>/dev/null | awk '{print $1}') (interactive picker available)"
else
echo "[i] fzf: not found (install for interactive picker: gtr cd)"
fi

echo ""
if [ "$issues" -eq 0 ]; then
echo "Everything looks good!"
Expand Down
12 changes: 12 additions & 0 deletions lib/commands/help.sh
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,17 @@ Setup:
After setup:
gtr cd my-feature # cd to worktree
gtr cd 1 # cd to main repo
gtr cd # interactive picker (requires fzf)
gtr <command> # same as git gtr <command>

Command palette (gtr cd with no arguments, requires fzf):
enter cd into selected worktree
ctrl-e open in editor
ctrl-a start AI tool
ctrl-d delete worktree (with confirmation)
ctrl-y copy files to worktree
ctrl-r refresh list
esc cancel
EOF
}

Expand Down Expand Up @@ -549,6 +559,7 @@ SETUP & MAINTENANCE:
Generate shell integration for cd support (bash, zsh, fish)
--as <name>: custom function name (default: gtr)
Usage: eval "$(git gtr init bash)"
With fzf: 'gtr cd' opens a command palette (preview, editor, AI, delete)

version
Show version
Expand All @@ -572,6 +583,7 @@ WORKFLOW EXAMPLES:
git gtr run feature/user-auth npm run dev # Start dev server

# Navigate to worktree directory
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
88 changes: 84 additions & 4 deletions lib/commands/init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,33 @@ __FUNC__() {
if [ "$#" -gt 0 ] && [ "$1" = "cd" ]; then
shift
local dir
dir="$(command git gtr go "$@")" && cd "$dir" && {
if [ "$#" -eq 0 ] && command -v fzf >/dev/null 2>&1; then
local _gtr_selection
_gtr_selection="$(command git gtr list --porcelain | fzf \
--delimiter=$'\t' \
--with-nth=2 \
--ansi \
--layout=reverse \
--border \
--prompt='Worktree> ' \
--header='enter:cd │ ctrl-e:editor │ ctrl-a:ai │ ctrl-d:delete │ ctrl-y:copy │ ctrl-r:refresh' \
--preview='git -C {1} log --oneline --graph --color=always -15 2>/dev/null; echo "---"; git -C {1} status --short 2>/dev/null' \
--preview-window=right:50% \
--bind='ctrl-e:execute(git gtr editor {2})' \
--bind='ctrl-a:execute(git gtr ai {2})' \
--bind='ctrl-d:execute(git gtr rm {2})+reload(git gtr list --porcelain)' \
--bind='ctrl-y:execute(git gtr copy {2})' \
--bind='ctrl-r:reload(git gtr list --porcelain)')" || return 0
[ -z "$_gtr_selection" ] && return 0
dir="$(printf '%s' "$_gtr_selection" | cut -f1)"
elif [ "$#" -eq 0 ]; then
echo "Usage: __FUNC__ cd <branch>" >&2
echo "Tip: Install fzf for an interactive picker (https://github.com/junegunn/fzf)" >&2
return 1
else
dir="$(command git gtr go "$@")" || return $?
fi
cd "$dir" && {
local _gtr_hooks _gtr_hook _gtr_seen _gtr_config_file
_gtr_hooks=""
_gtr_seen=""
Expand Down Expand Up @@ -152,10 +178,37 @@ _init_zsh() {
# eval "$(git gtr init zsh)"

__FUNC__() {
emulate -L zsh
if [ "$#" -gt 0 ] && [ "$1" = "cd" ]; then
shift
local dir
dir="$(command git gtr go "$@")" && cd "$dir" && {
if [ "$#" -eq 0 ] && command -v fzf >/dev/null 2>&1; then
local _gtr_selection
_gtr_selection="$(command git gtr list --porcelain | fzf \
--delimiter=$'\t' \
--with-nth=2 \
--ansi \
--layout=reverse \
--border \
--prompt='Worktree> ' \
--header='enter:cd │ ctrl-e:editor │ ctrl-a:ai │ ctrl-d:delete │ ctrl-y:copy │ ctrl-r:refresh' \
--preview='git -C {1} log --oneline --graph --color=always -15 2>/dev/null; echo "---"; git -C {1} status --short 2>/dev/null' \
--preview-window=right:50% \
--bind='ctrl-e:execute(git gtr editor {2})' \
--bind='ctrl-a:execute(git gtr ai {2})' \
--bind='ctrl-d:execute(git gtr rm {2})+reload(git gtr list --porcelain)' \
--bind='ctrl-y:execute(git gtr copy {2})' \
--bind='ctrl-r:reload(git gtr list --porcelain)')" || return 0
[ -z "$_gtr_selection" ] && return 0
dir="$(printf '%s' "$_gtr_selection" | cut -f1)"
elif [ "$#" -eq 0 ]; then
echo "Usage: __FUNC__ cd <branch>" >&2
echo "Tip: Install fzf for an interactive picker (https://github.com/junegunn/fzf)" >&2
return 1
else
dir="$(command git gtr go "$@")" || return $?
fi
cd "$dir" && {
local _gtr_hooks _gtr_hook _gtr_seen _gtr_config_file
_gtr_hooks=""
_gtr_seen=""
Expand Down Expand Up @@ -232,8 +285,35 @@ _init_fish() {

function __FUNC__
if test (count $argv) -gt 0; and test "$argv[1]" = "cd"
set -l dir (command git gtr go $argv[2..])
and cd $dir
set -l dir
if test (count $argv) -eq 1; and type -q fzf
set -l _gtr_selection (command git gtr list --porcelain | fzf \
--delimiter=\t \
--with-nth=2 \
--ansi \
--layout=reverse \
--border \
--prompt='Worktree> ' \
--header='enter:cd │ ctrl-e:editor │ ctrl-a:ai │ ctrl-d:delete │ ctrl-y:copy │ ctrl-r:refresh' \
--preview='git -C {1} log --oneline --graph --color=always -15 2>/dev/null; echo "---"; git -C {1} status --short 2>/dev/null' \
--preview-window=right:50% \
--bind='ctrl-e:execute(git gtr editor {2})' \
--bind='ctrl-a:execute(git gtr ai {2})' \
--bind='ctrl-d:execute(git gtr rm {2})+reload(git gtr list --porcelain)' \
--bind='ctrl-y:execute(git gtr copy {2})' \
--bind='ctrl-r:reload(git gtr list --porcelain)')
or return 0
test -z "$_gtr_selection"; and return 0
set dir (string split \t -- "$_gtr_selection")[1]
else if test (count $argv) -eq 1
echo "Usage: __FUNC__ cd <branch>" >&2
echo "Tip: Install fzf for an interactive picker (https://github.com/junegunn/fzf)" >&2
return 1
else
set dir (command git gtr go $argv[2..])
or return $status
end
cd $dir
and begin
set -l _gtr_hooks
set -l _gtr_seen
Expand Down
47 changes: 47 additions & 0 deletions tests/init.bats
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,53 @@ setup() {
[ "$status" -eq 1 ]
}

# ── fzf interactive picker ───────────────────────────────────────────────────

@test "bash output includes fzf picker for cd with no args" {
run cmd_init bash
[ "$status" -eq 0 ]
[[ "$output" == *"command -v fzf"* ]]
[[ "$output" == *"--prompt='Worktree> '"* ]]
[[ "$output" == *"--with-nth=2"* ]]
[[ "$output" == *"ctrl-e:execute"* ]]
}

@test "zsh output includes fzf picker for cd with no args" {
run cmd_init zsh
[ "$status" -eq 0 ]
[[ "$output" == *"command -v fzf"* ]]
[[ "$output" == *"--prompt='Worktree> '"* ]]
[[ "$output" == *"--with-nth=2"* ]]
[[ "$output" == *"ctrl-e:execute"* ]]
}

@test "fish output includes fzf picker for cd with no args" {
run cmd_init fish
[ "$status" -eq 0 ]
[[ "$output" == *"type -q fzf"* ]]
[[ "$output" == *"--prompt='Worktree> '"* ]]
[[ "$output" == *"--with-nth=2"* ]]
[[ "$output" == *"ctrl-e:execute"* ]]
}

@test "bash output shows fzf install hint when no args and no fzf" {
run cmd_init bash
[ "$status" -eq 0 ]
[[ "$output" == *'Install fzf for an interactive picker'* ]]
}

@test "fish output shows fzf install hint when no args and no fzf" {
run cmd_init fish
[ "$status" -eq 0 ]
[[ "$output" == *'Install fzf for an interactive picker'* ]]
}

@test "--as replaces function name in fzf fallback message" {
run cmd_init bash --as gwtr
[ "$status" -eq 0 ]
[[ "$output" == *'Usage: gwtr cd <branch>'* ]]
}

# ── git gtr passthrough preserved ────────────────────────────────────────────

@test "bash output passes non-cd commands to git gtr" {
Expand Down