Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ cmd_editor() → resolve_target() → load_editor_adapter() → editor_open()

**`.gtrconfig`**: Team-shared config using gitconfig syntax, parsed via `git config -f`. Keys map differently from git config (e.g., `gtr.copy.include` → `copy.include`, `gtr.hook.postCreate` → `hooks.postCreate`). See the .gtrconfig Key Mapping table in README or `docs/configuration.md`.

**`init` command**: Outputs shell functions for `gtr cd <branch>` navigation. Users add `eval "$(git gtr init bash)"` to their shell rc file.
**`init` command**: Outputs shell functions for `gtr cd <branch>` navigation. Output is cached to `~/.cache/gtr/` and auto-invalidates on version change. Users source the cache file directly in their shell rc for fast startup (see `git gtr help init`).

**`clean --merged`**: Removes worktrees whose PRs/MRs are merged. Auto-detects GitHub (`gh`) or GitLab (`glab`) from the `origin` remote URL. Override with `gtr.provider` config for self-hosted instances.

Expand Down
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ 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)"
gtr cd my-feature # Requires shell integration (see below)
cd "$(git gtr go my-feature)" # Alternative without shell integration

# List all worktrees
Expand Down Expand Up @@ -214,15 +214,27 @@ cd "$(git gtr go 1)" # Navigate to main repo
**Tip:** For easier navigation, use `git gtr init` to enable `gtr cd`:

```bash
# Add to ~/.bashrc or ~/.zshrc (one-time setup)
eval "$(git gtr init 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
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
source "$_gtr_init" 2>/dev/null || true; unset _gtr_init

# Fish (add to ~/.config/fish/config.fish)
git gtr init fish | source

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

The cache generates on first run and refreshes the next time `git gtr init <shell>` runs. To force-regenerate: `rm -rf ~/.cache/gtr`

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:
Expand Down
4 changes: 2 additions & 2 deletions bin/git-gtr
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ main() {
local _shell_name
_shell_name="$(basename "${SHELL:-bash}")"
log_error "'cd' requires shell integration (subprocesses cannot change your shell's directory)"
log_info "Set up with: eval \"\$(git gtr init $_shell_name)\""
log_info "Then use: gtr cd [<branch>]"
log_info "Run 'git gtr help init' for setup instructions"
log_info "Then use: gtr cd [<branch>]"
exit 1
;;
*)
Expand Down
10 changes: 2 additions & 8 deletions lib/commands/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,10 @@ cmd_doctor() {
fish) _rc_file="$HOME/.config/fish/config.fish" ;;
*) _rc_file="" ;;
esac
if [ -n "$_rc_file" ] && [ -f "$_rc_file" ] && grep -q 'git gtr init' "$_rc_file" 2>/dev/null; then
if [ -n "$_rc_file" ] && [ -f "$_rc_file" ] && grep -qE 'git gtr init|gtr/init-' "$_rc_file" 2>/dev/null; then
echo "[OK] Shell integration: loaded (gtr cd available)"
elif [ -n "$_rc_file" ]; then
local _init_hint
if [ "$_shell_name" = "fish" ]; then
_init_hint="git gtr init fish | source"
else
_init_hint="eval \"\$(git gtr init $_shell_name)\""
fi
echo "[i] Shell integration: $_init_hint in ${_rc_file##*/} for gtr cd"
echo "[i] Shell integration: run 'git gtr help init' for setup instructions"
fi

echo ""
Expand Down
21 changes: 16 additions & 5 deletions lib/commands/help.sh
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ Usage: git gtr go <branch>

Prints the absolute path to the specified worktree. Useful for navigation
with cd or for scripting. For direct cd support, use shell integration:
eval "$(git gtr init bash)" # then: gtr cd <branch>
git gtr help init # see setup instructions
gtr cd <branch> # then navigate directly

Special:
Use '1' for the main repo root: git gtr go 1
Expand Down Expand Up @@ -345,18 +346,27 @@ Usage: git gtr init <shell> [--as <name>]
Generates shell functions for enhanced features like 'gtr cd <branch>'
which changes directory to a worktree. Add to your shell configuration.

Output is cached to ~/.cache/gtr/ for fast shell startup (~1ms vs ~60ms).
The cache refreshes the next time 'git gtr init <shell>' runs (checks version).
With the recommended setup below, it regenerates when the cache file is missing.
To force-regenerate: rm -rf ~/.cache/gtr

Supported shells: bash, zsh, fish

Options:
--as <name> Set custom function name (default: gtr)
Useful if 'gtr' conflicts with another command (e.g., GNU tr)

Setup:
Setup (sources cached output directly for fast startup):
# Bash (add to ~/.bashrc)
eval "$(git gtr init bash)"
_gtr_init="${XDG_CACHE_HOME:-$HOME/.cache}/gtr/init-gtr.bash"
[[ -f "$_gtr_init" ]] || eval "$(git gtr init bash)" || true
source "$_gtr_init" 2>/dev/null || true; unset _gtr_init

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

# Fish (add to ~/.config/fish/config.fish)
git gtr init fish | source
Expand Down Expand Up @@ -558,7 +568,8 @@ SETUP & MAINTENANCE:
init <shell> [--as <name>]
Generate shell integration for cd support (bash, zsh, fish)
--as <name>: custom function name (default: gtr)
Usage: eval "$(git gtr init bash)"
Output is cached for fast startup (auto-invalidates on update)
See git gtr help init for recommended setup
With fzf: 'gtr cd' opens a command palette (preview, editor, AI, delete)

version
Expand Down
51 changes: 33 additions & 18 deletions lib/commands/init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,33 +50,49 @@ cmd_init() {
return 1
fi

# Resolve generator function
local generator
case "$shell" in
bash)
_init_bash | sed "s/__FUNC__/$func_name/g"
;;
zsh)
_init_zsh | sed "s/__FUNC__/$func_name/g"
;;
fish)
_init_fish | sed "s/__FUNC__/$func_name/g"
;;
"")
show_command_help
;;
bash) generator="_init_bash" ;;
zsh) generator="_init_zsh" ;;
fish) generator="_init_fish" ;;
"") show_command_help; return 0 ;;
*)
log_error "Unknown shell: $shell"
log_error "Supported shells: bash, zsh, fish"
log_info "Run 'git gtr init --help' for usage"
return 1
;;
esac

# Generate output (cached to ~/.cache/gtr/, auto-invalidates on version change)
local cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/gtr"
local cache_file="$cache_dir/init-${func_name}.${shell}"
local cache_stamp="# gtr-cache: version=${GTR_VERSION:-unknown} func=$func_name shell=$shell"

# Return cached output if version matches
if [ -f "$cache_file" ]; then
local first_line
first_line="$(head -1 "$cache_file")"
if [ "$first_line" = "$cache_stamp" ]; then
tail -n +2 "$cache_file"
return 0
fi
fi

# Generate, output, and cache (output first so set -e cache failures don't swallow it)
local output
output="$("$generator" | sed "s/__FUNC__/$func_name/g")"
printf '%s\n' "$output"
if mkdir -p "$cache_dir" 2>/dev/null; then
printf '%s\n%s\n' "$cache_stamp" "$output" > "$cache_file" 2>/dev/null || true
fi
}

_init_bash() {
cat <<'BASH'
# git-gtr shell integration
# Add to ~/.bashrc:
# eval "$(git gtr init bash)"
# git-gtr shell integration (cached to ~/.cache/gtr/)
# Setup: see git gtr help init

__FUNC__() {
if [ "$#" -gt 0 ] && [ "$1" = "cd" ]; then
Expand Down Expand Up @@ -196,9 +212,8 @@ BASH

_init_zsh() {
cat <<'ZSH'
# git-gtr shell integration
# Add to ~/.zshrc:
# eval "$(git gtr init zsh)"
# git-gtr shell integration (cached to ~/.cache/gtr/)
# Setup: see git gtr help init

__FUNC__() {
emulate -L zsh
Expand Down
57 changes: 57 additions & 0 deletions tests/init.bats
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ load test_helper

setup() {
source "$PROJECT_ROOT/lib/commands/init.sh"
# Isolate cache to temp dir so tests don't pollute ~/.cache or each other
export XDG_CACHE_HOME="$BATS_TMPDIR/gtr-init-cache-$$"
export GTR_VERSION="test"
}

teardown() {
rm -rf "$BATS_TMPDIR/gtr-init-cache-$$"
}

# ── Default function name ────────────────────────────────────────────────────
Expand Down Expand Up @@ -496,3 +503,53 @@ setup() {
[[ "$output" == *"command git gtr"* ]]
[[ "$output" == *"git gtr list --porcelain"* ]]
}

# ── caching (default behavior) ──────────────────────────────────────────────

@test "init creates cache file and returns output" {
GTR_VERSION="9.9.9" run cmd_init zsh
[ "$status" -eq 0 ]
[[ "$output" == *"gtr()"* ]]
[ -f "$XDG_CACHE_HOME/gtr/init-gtr.zsh" ]
}

@test "init returns cached output on second call" {
# First call: generates and caches
GTR_VERSION="9.9.9" run cmd_init bash
[ "$status" -eq 0 ]
local first_output="$output"
# Second call: reads from cache
GTR_VERSION="9.9.9" run cmd_init bash
[ "$status" -eq 0 ]
[ "$output" = "$first_output" ]
}

@test "cache invalidates when version changes" {
# Generate with version 1.0.0
GTR_VERSION="1.0.0" run cmd_init zsh
[ "$status" -eq 0 ]
# Check cache stamp
local stamp
stamp="$(head -1 "$XDG_CACHE_HOME/gtr/init-gtr.zsh")"
[[ "$stamp" == *"version=1.0.0"* ]]
# Regenerate with version 2.0.0
GTR_VERSION="2.0.0" run cmd_init zsh
[ "$status" -eq 0 ]
stamp="$(head -1 "$XDG_CACHE_HOME/gtr/init-gtr.zsh")"
[[ "$stamp" == *"version=2.0.0"* ]]
}

@test "cache uses --as func name in cache key" {
GTR_VERSION="9.9.9" run cmd_init bash --as myfn
[ "$status" -eq 0 ]
[[ "$output" == *"myfn()"* ]]
[ -f "$XDG_CACHE_HOME/gtr/init-myfn.bash" ]
}

@test "cache works for all shells" {
for sh in bash zsh fish; do
GTR_VERSION="9.9.9" run cmd_init "$sh"
[ "$status" -eq 0 ]
[ -f "$XDG_CACHE_HOME/gtr/init-gtr.${sh}" ]
done
}