Skip to content

Commit 23c3f59

Browse files
authored
Add shell-integration support for new --cd (#151)
1 parent 99368b0 commit 23c3f59

File tree

7 files changed

+509
-95
lines changed

7 files changed

+509
-95
lines changed

AGENTS.md

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
`git gtr` (Git Worktree Runner) is a cross-platform CLI tool written in Bash that simplifies git worktree management. It wraps `git worktree` with quality-of-life features like editor integration, AI tool support, file copying, and hooks. Installed as a git subcommand: `git gtr <command>`.
8+
9+
## Invocation
10+
11+
- **Production**: `git gtr <command>` (git subcommand via `bin/git-gtr` wrapper)
12+
- **Development/testing**: `./bin/gtr <command>` (direct execution)
13+
- **User-facing docs**: Always reference `git gtr`, never `./bin/gtr`
14+
15+
## Testing
16+
17+
This project uses **BATS tests** for core functions and **manual smoke tests** for end-to-end workflows. CI runs ShellCheck + BATS automatically on PRs (`.github/workflows/lint.yml`).
18+
19+
1. Run automated tests: `bats tests/`
20+
2. Run a single test file: `bats tests/config.bats`
21+
3. Run a single test by name: `bats tests/config.bats --filter "cfg_map_to_file_key"`
22+
4. Run relevant manual smoke tests:
23+
24+
```bash
25+
./bin/gtr new test-feature # Create worktree
26+
./bin/gtr new feature/auth # Slash branch → folder "feature-auth"
27+
./bin/gtr list # Table output
28+
./bin/gtr go test-feature # Print path
29+
./bin/gtr run test-feature git status
30+
./bin/gtr rm test-feature # Clean up
31+
```
32+
33+
For exhaustive manual testing (hooks, copy patterns, adapters, `--force`, `--from-current`, etc.), see the full checklist in CONTRIBUTING.md or `.github/instructions/testing.instructions.md`.
34+
35+
**Test files**: `adapters`, `config`, `copy_safety`, `integration_lifecycle`, `parse_args`, `provider`, `resolve_base_dir`, `sanitize_branch_name` (all in `tests/`). Shared fixtures in `tests/test_helper.bash`.
36+
37+
**Tip**: Use a disposable repo for testing to avoid polluting your working tree:
38+
39+
```bash
40+
mkdir -p /tmp/gtr-test && cd /tmp/gtr-test && git init && git commit --allow-empty -m "init"
41+
/path/to/git-worktree-runner/bin/gtr new test-feature
42+
```
43+
44+
## Architecture
45+
46+
### Binary Structure
47+
48+
- `bin/git-gtr` — Main entry point: sources libraries and commands, contains `main()` dispatcher
49+
- `bin/gtr` — Convenience wrapper for development (`exec bin/git-gtr`)
50+
51+
### Module Structure
52+
53+
| File | Purpose |
54+
| ------------------- | ----------------------------------------------------------------------------------------------------------- |
55+
| `lib/ui.sh` | Logging (`log_error`, `log_info`, `log_warn`), prompts, formatting |
56+
| `lib/args.sh` | Shared argument parser: flag specs (`--flag`, `--flag: val`, aliases), populates `_arg_*` vars |
57+
| `lib/config.sh` | Git config wrapper with precedence: `cfg_get`, `cfg_default`, `cfg_get_all` |
58+
| `lib/platform.sh` | OS detection, GUI helpers |
59+
| `lib/core.sh` | Worktree CRUD: `create_worktree`, `remove_worktree`, `list_worktrees`, `resolve_target`, `resolve_base_dir` |
60+
| `lib/copy.sh` | File/directory copying with glob patterns: `copy_patterns`, `copy_directories` |
61+
| `lib/hooks.sh` | Hook execution: `run_hooks_in` for postCreate/preRemove/postRemove |
62+
| `lib/provider.sh` | Remote hosting detection (GitHub/GitLab) and CLI integration for `clean --merged` |
63+
| `lib/adapters.sh` | Adapter registry, builder functions, generic fallbacks, loader functions |
64+
| `lib/launch.sh` | Editor/AI launch orchestration: `_open_editor`, `_auto_launch_editor`, `_auto_launch_ai` |
65+
| `lib/commands/*.sh` | One file per subcommand: `cmd_create`, `cmd_remove`, etc. (16 files) |
66+
67+
Libraries are sourced in the order listed above (ui → args → config → ... → launch → commands/\*.sh glob).
68+
69+
### Adapters
70+
71+
Most adapters are defined declaratively in the **adapter registry** (`lib/adapters.sh`) using pipe-delimited entries. Custom adapters that need special logic remain as override files in `adapters/editor/` and `adapters/ai/`.
72+
73+
**Registry-defined adapters**: antigravity, atom, cursor, emacs, idea, nvim, pycharm, sublime, vim, vscode, webstorm, zed (editors) and aider, auggie, codex, continue, copilot, gemini, opencode (AI).
74+
75+
**Custom adapter files**: `adapters/editor/nano.sh`, `adapters/ai/Codex.sh`, `adapters/ai/cursor.sh` — these implement `editor_can_open()`/`editor_open()` or `ai_can_start()`/`ai_start()` directly.
76+
77+
**Loading order**: file override → registry → generic PATH fallback. `GTR_EDITOR_CMD` / `GTR_AI_CMD` env vars allow custom tools without adapters.
78+
79+
### Command Flow
80+
81+
```
82+
bin/gtr main() → case statement → cmd_*() handler → lib/*.sh functions → adapters (if needed)
83+
```
84+
85+
Key dispatch: `new``cmd_create`, `rm``cmd_remove`, `mv|rename``cmd_rename`, `go``cmd_go`, `run``cmd_run`, `editor``cmd_editor`, `ai``cmd_ai`, `copy``cmd_copy`, `ls|list``cmd_list`, `clean``cmd_clean`, `init``cmd_init`, `config``cmd_config`, `completion``cmd_completion`, `doctor``cmd_doctor`, `adapter``cmd_adapter`.
86+
87+
**Example: `git gtr new my-feature`**
88+
89+
```
90+
cmd_create() → resolve_base_dir() → create_worktree() → copy_patterns() → copy_directories() → run_hooks_in()
91+
```
92+
93+
**Example: `git gtr editor my-feature`**
94+
95+
```
96+
cmd_editor() → resolve_target() → load_editor_adapter() → editor_open()
97+
```
98+
99+
## Key Implementation Details
100+
101+
**Branch Name Sanitization**: Slashes and special chars become hyphens. `feature/user-auth` → folder `feature-user-auth`.
102+
103+
**Special ID `1`**: Always refers to the main repository in `go`, `editor`, `ai`, `run`, etc.
104+
105+
**`resolve_target()`** (lib/core.sh): Resolves branch names/IDs to worktree paths. Checks: special ID → current branch in main → sanitized path match → full scan. Returns TSV: `is_main\tpath\tbranch`.
106+
107+
**`resolve_base_dir()`** (lib/core.sh): Determines worktree storage location. Empty → `<repo>-worktrees` sibling; relative → from repo root; absolute → as-is; tilde → expanded.
108+
109+
**`create_worktree()`** (lib/core.sh): Intelligent track mode — tries remote first, then local branch, then creates new.
110+
111+
**Config Precedence** (`cfg_default` in lib/config.sh): local git config → `.gtrconfig` file → global/system git config → env vars → fallback. Multi-value keys (`gtr.copy.include`, hooks, etc.) are merged and deduplicated via `cfg_get_all()`.
112+
113+
**`.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`.
114+
115+
**`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`).
116+
117+
**`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.
118+
119+
## Common Development Tasks
120+
121+
### Adding a New Command
122+
123+
1. Create `lib/commands/<name>.sh` with `cmd_<name>()` function
124+
2. Add case entry in `main()` dispatcher in `bin/gtr`
125+
3. Add help text in `lib/commands/help.sh`
126+
4. Update all three completion files: `completions/gtr.bash`, `completions/_git-gtr`, `completions/git-gtr.fish`
127+
5. Update README.md
128+
129+
### Adding an Adapter
130+
131+
**Standard adapters** (just a command name + error message): Add an entry to `_EDITOR_REGISTRY` or `_AI_REGISTRY` in `lib/adapters.sh`. Then update: help text in `lib/commands/help.sh`, all three completions, README.md.
132+
133+
**Custom adapters** (special logic needed): Create `adapters/{editor,ai}/<name>.sh` implementing the two required functions (see `adapters/ai/Codex.sh` for an example). File-based adapters take priority over registry entries.
134+
135+
### Updating the Version
136+
137+
Update `GTR_VERSION` in `bin/git-gtr`.
138+
139+
### Shell Completion Updates
140+
141+
When adding commands or flags, update all three files:
142+
143+
- `completions/gtr.bash` (Bash)
144+
- `completions/_git-gtr` (Zsh)
145+
- `completions/git-gtr.fish` (Fish)
146+
147+
## Critical Gotcha: `set -e`
148+
149+
`bin/git-gtr` runs with `set -e`. Any unguarded non-zero return silently exits the entire script. When calling functions that may `return 1`, guard with `|| true`:
150+
151+
```bash
152+
result=$(my_func) || true # Prevents silent exit
153+
if my_func; then ...; fi # Also safe (if guards the return)
154+
```
155+
156+
This is the most common source of subtle bugs in this codebase.
157+
158+
## Code Style
159+
160+
- Shebang: `#!/usr/bin/env bash`
161+
- `snake_case` functions/variables, `UPPER_CASE` constants
162+
- 2-space indent, no tabs
163+
- Always quote variables: `"$var"`
164+
- `local` for function-scoped variables
165+
- Target Bash 3.2+ (macOS default); Git 2.17+ minimum
166+
- Git 2.22+ commands need fallbacks (e.g., `git branch --show-current``git rev-parse --abbrev-ref HEAD`)
167+
168+
## Configuration Reference
169+
170+
All config uses `gtr.*` prefix via `git config`. Key settings:
171+
172+
- `gtr.worktrees.dir` — Base directory (default: `<repo-name>-worktrees` sibling)
173+
- `gtr.worktrees.prefix` — Folder prefix (default: `""`)
174+
- `gtr.editor.default` / `gtr.ai.default` — Default editor/AI tool
175+
- `gtr.copy.include` / `gtr.copy.exclude` — File glob patterns (multi-valued, use `--add`)
176+
- `gtr.copy.includeDirs` / `gtr.copy.excludeDirs` — Directory patterns (multi-valued)
177+
- `gtr.hook.postCreate` / `gtr.hook.preRemove` / `gtr.hook.postRemove` — Hook commands (multi-valued)
178+
179+
Hook env vars: `REPO_ROOT`, `WORKTREE_PATH`, `BRANCH`. preRemove hooks run with cwd in worktree; failure aborts removal unless `--force`.
180+
181+
## Debugging
182+
183+
```bash
184+
bash -x ./bin/gtr <command> # Full trace
185+
declare -f function_name # Check function definition
186+
echo "Debug: var=$var" >&2 # Inspect variable
187+
./bin/gtr doctor # Health check
188+
./bin/gtr adapter # List available adapters
189+
```
190+
191+
## Related Documentation
192+
193+
- `CONTRIBUTING.md` — Full contribution guidelines, coding standards, manual testing checklist
194+
- `.github/copilot-instructions.md` — Condensed AI agent guide
195+
- `.github/instructions/*.instructions.md` — File-pattern-specific guidance (testing, shell conventions, lib modifications, adapter contracts, completions)
196+
- `docs/configuration.md` — Complete configuration reference
197+
- `docs/advanced-usage.md` — Advanced workflows

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ git gtr ai my-feature # Start claude
100100
git gtr run my-feature npm test # Run tests
101101

102102
# Navigate to worktree
103+
gtr new my-feature --cd # Create and cd (requires shell integration)
103104
gtr cd # Interactive picker (requires fzf + shell integration)
104105
gtr cd my-feature # Requires shell integration (see below)
105106
cd "$(git gtr go my-feature)" # Alternative without shell integration
@@ -230,6 +231,7 @@ test -f "$_gtr_init"; or git gtr init fish >/dev/null 2>&1
230231
source "$_gtr_init" 2>/dev/null
231232

232233
# Then navigate with:
234+
gtr new my-feature --cd # Create and land in the new worktree
233235
gtr cd # Interactive worktree picker (requires fzf)
234236
gtr cd my-feature
235237
gtr cd 1
@@ -360,7 +362,7 @@ git gtr config add gtr.copy.include "**/.env.example"
360362
# Run setup after creating worktrees
361363
git gtr config add gtr.hook.postCreate "npm install"
362364

363-
# Re-source environment after gtr cd (runs in current shell)
365+
# Re-source environment after gtr cd or gtr new --cd (runs in current shell)
364366
git gtr config add gtr.hook.postCd "source ./vars.sh"
365367

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

docs/configuration.md

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

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

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

311311
> **Note:** Pre-remove hooks abort removal on failure. Use `--force` to skip failed hooks.
312312
>
313-
> **Note:** `postCd` hooks run in the **current shell** (not a subshell) so they can modify environment variables. They only run via `gtr cd` (shell integration), not `git gtr go`. Failures warn but don't undo the `cd`.
313+
> **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.
314314

315315
**Environment variables available in hooks:**
316316

lib/commands/help.sh

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,8 @@ git gtr init - Generate shell integration
344344
Usage: git gtr init <shell> [--as <name>]
345345
346346
Generates shell functions for enhanced features like 'gtr cd <branch>'
347-
which changes directory to a worktree. Add to your shell configuration.
347+
and 'gtr new <branch> --cd', which can change the current shell directory.
348+
Add to your shell configuration.
348349
349350
Output is cached to ~/.cache/gtr/ for fast shell startup (~1ms vs ~60ms).
350351
The cache refreshes the next time 'git gtr init <shell>' runs (checks version).
@@ -377,6 +378,7 @@ Setup (sources cached output directly for fast startup):
377378
eval "$(git gtr init zsh --as gwtr)"
378379
379380
After setup:
381+
gtr new my-feature --cd # create and cd into worktree
380382
gtr cd my-feature # cd to worktree
381383
gtr cd 1 # cd to main repo
382384
gtr cd # interactive picker (requires fzf)
@@ -568,7 +570,7 @@ SETUP & MAINTENANCE:
568570
Usage: eval "$(git gtr completion zsh)"
569571
570572
init <shell> [--as <name>]
571-
Generate shell integration for cd support (bash, zsh, fish)
573+
Generate shell integration for gtr cd and gtr new --cd (bash, zsh, fish)
572574
--as <name>: custom function name (default: gtr)
573575
Output is cached for fast startup (refreshes when 'git gtr init' runs)
574576
See git gtr help init for recommended setup
@@ -596,6 +598,7 @@ WORKFLOW EXAMPLES:
596598
git gtr run feature/user-auth npm run dev # Start dev server
597599
598600
# Navigate to worktree directory
601+
gtr new hotfix --cd # Create and cd into worktree (with shell integration)
599602
gtr cd # Interactive picker (requires fzf)
600603
gtr cd feature/user-auth # With shell integration (git gtr init)
601604
cd "$(git gtr go feature/user-auth)" # Without shell integration
@@ -646,7 +649,7 @@ CONFIGURATION OPTIONS:
646649
gtr.hook.postCreate Post-create hooks (multi-valued)
647650
gtr.hook.preRemove Pre-remove hooks (multi-valued, abort on failure)
648651
gtr.hook.postRemove Post-remove hooks (multi-valued)
649-
gtr.hook.postCd Post-cd hooks (multi-valued, shell integration only)
652+
gtr.hook.postCd Post-cd hooks (multi-valued, gtr cd / gtr new --cd only)
650653
gtr.ui.color Color output mode (auto, always, never; default: auto)
651654
652655
────────────────────────────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)