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
197 changes: 197 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# AGENTS.md

This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.

## Project Overview

`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>`.

## Invocation

- **Production**: `git gtr <command>` (git subcommand via `bin/git-gtr` wrapper)
- **Development/testing**: `./bin/gtr <command>` (direct execution)
- **User-facing docs**: Always reference `git gtr`, never `./bin/gtr`

## Testing

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`).

1. Run automated tests: `bats tests/`
2. Run a single test file: `bats tests/config.bats`
3. Run a single test by name: `bats tests/config.bats --filter "cfg_map_to_file_key"`
4. Run relevant manual smoke tests:

```bash
./bin/gtr new test-feature # Create worktree
./bin/gtr new feature/auth # Slash branch → folder "feature-auth"
./bin/gtr list # Table output
./bin/gtr go test-feature # Print path
./bin/gtr run test-feature git status
./bin/gtr rm test-feature # Clean up
```

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`.

**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`.

**Tip**: Use a disposable repo for testing to avoid polluting your working tree:

```bash
mkdir -p /tmp/gtr-test && cd /tmp/gtr-test && git init && git commit --allow-empty -m "init"
/path/to/git-worktree-runner/bin/gtr new test-feature
```

## Architecture

### Binary Structure

- `bin/git-gtr` — Main entry point: sources libraries and commands, contains `main()` dispatcher
- `bin/gtr` — Convenience wrapper for development (`exec bin/git-gtr`)

### Module Structure

| File | Purpose |
| ------------------- | ----------------------------------------------------------------------------------------------------------- |
| `lib/ui.sh` | Logging (`log_error`, `log_info`, `log_warn`), prompts, formatting |
| `lib/args.sh` | Shared argument parser: flag specs (`--flag`, `--flag: val`, aliases), populates `_arg_*` vars |
| `lib/config.sh` | Git config wrapper with precedence: `cfg_get`, `cfg_default`, `cfg_get_all` |
| `lib/platform.sh` | OS detection, GUI helpers |
| `lib/core.sh` | Worktree CRUD: `create_worktree`, `remove_worktree`, `list_worktrees`, `resolve_target`, `resolve_base_dir` |
| `lib/copy.sh` | File/directory copying with glob patterns: `copy_patterns`, `copy_directories` |
| `lib/hooks.sh` | Hook execution: `run_hooks_in` for postCreate/preRemove/postRemove |
| `lib/provider.sh` | Remote hosting detection (GitHub/GitLab) and CLI integration for `clean --merged` |
| `lib/adapters.sh` | Adapter registry, builder functions, generic fallbacks, loader functions |
| `lib/launch.sh` | Editor/AI launch orchestration: `_open_editor`, `_auto_launch_editor`, `_auto_launch_ai` |
| `lib/commands/*.sh` | One file per subcommand: `cmd_create`, `cmd_remove`, etc. (16 files) |

Libraries are sourced in the order listed above (ui → args → config → ... → launch → commands/\*.sh glob).

### Adapters

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/`.

**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).

**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.

**Loading order**: file override → registry → generic PATH fallback. `GTR_EDITOR_CMD` / `GTR_AI_CMD` env vars allow custom tools without adapters.

### Command Flow

```
bin/gtr main() → case statement → cmd_*() handler → lib/*.sh functions → adapters (if needed)
```

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`.

**Example: `git gtr new my-feature`**

```
cmd_create() → resolve_base_dir() → create_worktree() → copy_patterns() → copy_directories() → run_hooks_in()
```

**Example: `git gtr editor my-feature`**

```
cmd_editor() → resolve_target() → load_editor_adapter() → editor_open()
```

## Key Implementation Details

**Branch Name Sanitization**: Slashes and special chars become hyphens. `feature/user-auth` → folder `feature-user-auth`.

**Special ID `1`**: Always refers to the main repository in `go`, `editor`, `ai`, `run`, etc.

**`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`.

**`resolve_base_dir()`** (lib/core.sh): Determines worktree storage location. Empty → `<repo>-worktrees` sibling; relative → from repo root; absolute → as-is; tilde → expanded.

**`create_worktree()`** (lib/core.sh): Intelligent track mode — tries remote first, then local branch, then creates new.

**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()`.

**`.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. 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.

## Common Development Tasks

### Adding a New Command

1. Create `lib/commands/<name>.sh` with `cmd_<name>()` function
2. Add case entry in `main()` dispatcher in `bin/gtr`
3. Add help text in `lib/commands/help.sh`
4. Update all three completion files: `completions/gtr.bash`, `completions/_git-gtr`, `completions/git-gtr.fish`
5. Update README.md

### Adding an Adapter

**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.

**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.

### Updating the Version

Update `GTR_VERSION` in `bin/git-gtr`.

### Shell Completion Updates

When adding commands or flags, update all three files:

- `completions/gtr.bash` (Bash)
- `completions/_git-gtr` (Zsh)
- `completions/git-gtr.fish` (Fish)

## Critical Gotcha: `set -e`

`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`:

```bash
result=$(my_func) || true # Prevents silent exit
if my_func; then ...; fi # Also safe (if guards the return)
```

This is the most common source of subtle bugs in this codebase.

## Code Style

- Shebang: `#!/usr/bin/env bash`
- `snake_case` functions/variables, `UPPER_CASE` constants
- 2-space indent, no tabs
- Always quote variables: `"$var"`
- `local` for function-scoped variables
- Target Bash 3.2+ (macOS default); Git 2.17+ minimum
- Git 2.22+ commands need fallbacks (e.g., `git branch --show-current` → `git rev-parse --abbrev-ref HEAD`)

## Configuration Reference

All config uses `gtr.*` prefix via `git config`. Key settings:

- `gtr.worktrees.dir` — Base directory (default: `<repo-name>-worktrees` sibling)
- `gtr.worktrees.prefix` — Folder prefix (default: `""`)
- `gtr.editor.default` / `gtr.ai.default` — Default editor/AI tool
- `gtr.copy.include` / `gtr.copy.exclude` — File glob patterns (multi-valued, use `--add`)
- `gtr.copy.includeDirs` / `gtr.copy.excludeDirs` — Directory patterns (multi-valued)
- `gtr.hook.postCreate` / `gtr.hook.preRemove` / `gtr.hook.postRemove` — Hook commands (multi-valued)

Hook env vars: `REPO_ROOT`, `WORKTREE_PATH`, `BRANCH`. preRemove hooks run with cwd in worktree; failure aborts removal unless `--force`.

## Debugging

```bash
bash -x ./bin/gtr <command> # Full trace
declare -f function_name # Check function definition
echo "Debug: var=$var" >&2 # Inspect variable
./bin/gtr doctor # Health check
./bin/gtr adapter # List available adapters
```

## Related Documentation

- `CONTRIBUTING.md` — Full contribution guidelines, coding standards, manual testing checklist
- `.github/copilot-instructions.md` — Condensed AI agent guide
- `.github/instructions/*.instructions.md` — File-pattern-specific guidance (testing, shell conventions, lib modifications, adapter contracts, completions)
- `docs/configuration.md` — Complete configuration reference
- `docs/advanced-usage.md` — Advanced workflows
4 changes: 3 additions & 1 deletion 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 new my-feature --cd # Create and cd (requires shell integration)
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 Down Expand Up @@ -230,6 +231,7 @@ test -f "$_gtr_init"; 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 cd # Interactive worktree picker (requires fzf)
gtr cd my-feature
gtr cd 1
Expand Down Expand Up @@ -360,7 +362,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 (runs in current shell)
# Re-source environment after gtr cd or gtr new --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
6 changes: 3 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,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, in current shell)
# Post-cd hooks (run after gtr cd or gtr new --cd, in current shell)
git gtr config add gtr.hook.postCd "source ./vars.sh"
```

Expand All @@ -306,11 +306,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` changes directory | Re-source environment, update shell context |
| `postCd` | After `gtr cd` or `gtr new --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 `gtr cd` (shell integration), not `git gtr go`. Failures warn but don't undo the `cd`.
> **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.

**Environment variables available in hooks:**

Expand Down
9 changes: 6 additions & 3 deletions lib/commands/help.sh
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,8 @@ git gtr init - Generate shell integration
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.
and 'gtr new <branch> --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).
The cache refreshes the next time 'git gtr init <shell>' runs (checks version).
Expand Down Expand Up @@ -377,6 +378,7 @@ Setup (sources cached output directly for fast startup):
eval "$(git gtr init zsh --as gwtr)"

After setup:
gtr new my-feature --cd # create and cd into worktree
gtr cd my-feature # cd to worktree
gtr cd 1 # cd to main repo
gtr cd # interactive picker (requires fzf)
Expand Down Expand Up @@ -568,7 +570,7 @@ SETUP & MAINTENANCE:
Usage: eval "$(git gtr completion zsh)"

init <shell> [--as <name>]
Generate shell integration for cd support (bash, zsh, fish)
Generate shell integration for gtr cd and gtr new --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 Down Expand Up @@ -596,6 +598,7 @@ WORKFLOW EXAMPLES:
git gtr run feature/user-auth npm run dev # Start dev server

# Navigate to worktree directory
gtr new hotfix --cd # Create and cd into worktree (with shell integration)
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 @@ -646,7 +649,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, shell integration only)
gtr.hook.postCd Post-cd hooks (multi-valued, gtr cd / gtr new --cd only)
gtr.ui.color Color output mode (auto, always, never; default: auto)

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