Skip to content

Commit 438c9cb

Browse files
authored
modularize bin/gtr into libraries and add BATS test suite (#119)
1 parent d7d0db8 commit 438c9cb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+5151
-3069
lines changed

.github/instructions/ai.instructions.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,26 @@
22
applyTo: adapters/ai/**/*.sh
33
---
44

5-
# AI Instructions
5+
# AI Adapter Instructions
66

7-
## Adding Features
7+
## When to Use a File vs Registry
88

9-
### New AI Tool Adapter (`adapters/ai/<name>.sh`)
9+
Most AI tools are defined as **registry entries** in `lib/adapters.sh` — no adapter file needed.
10+
Only create an adapter file in `adapters/ai/` for tools that need **custom behavior** beyond the standard builder (see `claude.sh` or `cursor.sh` for examples).
11+
12+
## Adding a Standard AI Tool (Registry)
13+
14+
Add a line to `_AI_REGISTRY` in `lib/adapters.sh`:
15+
16+
```
17+
yourname|yourcmd|ToolName not found. Install with: ...|Extra info;More info
18+
```
19+
20+
Format: `name|cmd|err_msg|info_lines` (info lines are semicolon-separated)
21+
22+
## Adding a Custom AI Tool (File Override)
23+
24+
Create `adapters/ai/<name>.sh` implementing:
1025

1126
```bash
1227
#!/usr/bin/env bash
@@ -27,7 +42,9 @@ ai_start() {
2742
}
2843
```
2944

30-
**Also update**: Same as editor adapters (README, completions, help text)
45+
File-based adapters take precedence over registry entries of the same name.
46+
47+
**Also update**: README, completions (bash/zsh/fish), help text in `lib/commands/help.sh`
3148

3249
## Contract & Guidelines
3350

@@ -38,5 +55,4 @@ ai_start() {
3855
- Accept extra args after `--`: preserve ordering (`ai_start` receives already-shifted args).
3956
- Prefer fast startup; heavy initialization belongs in hooks (`postCreate`), not adapters.
4057
- When adding adapter: update `cmd_help`, README tool list, and completions (bash/zsh/fish).
41-
- Test manually: `bash -c 'source adapters/ai/<tool>.sh && ai_can_start && echo OK'`.
4258
- Inspect function definition if needed: `declare -f ai_start`.

.github/instructions/editor.instructions.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,28 @@
22
applyTo: adapters/editor/**/*.sh
33
---
44

5-
# Editor Instructions
5+
# Editor Adapter Instructions
66

7-
## Adding Features
7+
## When to Use a File vs Registry
88

9-
### New Editor Adapter (`adapters/editor/<name>.sh`)
9+
Most editors are defined as **registry entries** in `lib/adapters.sh` — no adapter file needed.
10+
Only create an adapter file in `adapters/editor/` for editors that need **custom behavior** beyond what the standard/terminal builders provide (see `nano.sh` for an example).
11+
12+
## Adding a Standard Editor (Registry)
13+
14+
Add a line to `_EDITOR_REGISTRY` in `lib/adapters.sh`:
15+
16+
```
17+
yourname|yourcmd|standard|EditorName not found. Install from https://...|flags
18+
```
19+
20+
Format: `name|cmd|type|err_msg|flags`
21+
- `type`: `standard` (GUI app) or `terminal` (runs in terminal)
22+
- `flags`: comma-separated — `workspace` (supports .code-workspace), `background` (terminal bg)
23+
24+
## Adding a Custom Editor (File Override)
25+
26+
Create `adapters/editor/<name>.sh` implementing:
1027

1128
```bash
1229
#!/usr/bin/env bash
@@ -26,20 +43,21 @@ editor_open() {
2643
}
2744
```
2845

46+
File-based adapters take precedence over registry entries of the same name.
47+
2948
**Also update**:
3049

3150
- README.md (setup instructions)
3251
- All three completion files: `completions/gtr.bash`, `completions/_git-gtr`, `completions/gtr.fish`
33-
- Help text in `bin/gtr` (`cmd_help` function)
52+
- Help text in `lib/commands/help.sh` (`cmd_help` function)
3453

3554
## Contract & Guidelines
3655

3756
- Required functions: `editor_can_open` (probe via `command -v`), `editor_open <path>`.
3857
- Quote all paths; support spaces. Avoid changing PWD globally—no subshell needed (editor opens path).
3958
- Use `log_error` with actionable install guidance if command missing.
4059
- Keep adapter lean: no project scans, no blocking prompts.
41-
- Naming: file name = tool name (`zed.sh``zed` flag). Avoid uppercase.
60+
- Naming: file/registry name = tool name (`zed``zed` flag). Avoid uppercase.
4261
- Update: README editor list, completions (bash/zsh/fish), help (`Available editors:`), optional screenshots.
43-
- Manual test: `bash -c 'source adapters/editor/<tool>.sh && editor_can_open && editor_open . || echo fail'`.
4462
- Fallback behavior: if editor absent, fail clearly; do NOT silently defer to file browser.
4563
- Inspect function definition if needed: `declare -f editor_open`.

.github/instructions/lib.instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ applyTo: lib/**/*.sh
2121

2222
## Change Guidelines
2323

24-
- Preserve adapter contracts; do not rename exported functions used by `bin/gtr`.
24+
- Preserve adapter contracts; do not rename exported functions used by command handlers in `lib/commands/`.
2525
- Add new config keys with `gtr.<name>` prefix; avoid collisions.
2626
- For performance-sensitive loops (e.g. directory scans) prefer built-ins (`find`, `grep`) with minimal subshells.
2727
- Any new Git command: add fallback for older versions or guard with detection.

.github/workflows/lint.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
shellcheck:
11+
name: ShellCheck
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Install ShellCheck
17+
run: sudo apt-get update && sudo apt-get install -y shellcheck
18+
19+
- name: Run ShellCheck
20+
run: |
21+
shellcheck bin/gtr bin/git-gtr lib/*.sh lib/commands/*.sh adapters/editor/*.sh adapters/ai/*.sh
22+
23+
test:
24+
name: Tests
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: actions/checkout@v4
28+
29+
- name: Install BATS
30+
run: sudo apt-get update && sudo apt-get install -y bats
31+
32+
- name: Run tests
33+
run: bats tests/

CLAUDE.md

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
1212
- **Development/testing**: `./bin/gtr <command>` (direct execution)
1313
- **User-facing docs**: Always reference `git gtr`, never `./bin/gtr`
1414

15-
## CRITICAL: No Automated Tests
15+
## Testing
1616

17-
This project has **no test suite**. All testing is manual. After any change, run the relevant smoke tests:
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:
1823

1924
```bash
2025
./bin/gtr new test-feature # Create worktree
@@ -25,7 +30,9 @@ This project has **no test suite**. All testing is manual. After any change, run
2530
./bin/gtr rm test-feature # Clean up
2631
```
2732

28-
For exhaustive testing (hooks, copy patterns, adapters, `--force`, `--from-current`, etc.), see the full checklist in CONTRIBUTING.md or `.github/instructions/testing.instructions.md`.
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`.
2936

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

@@ -39,28 +46,35 @@ mkdir -p /tmp/gtr-test && cd /tmp/gtr-test && git init && git commit --allow-emp
3946
### Binary Structure
4047

4148
- `bin/git-gtr` — Thin wrapper enabling `git gtr` subcommand invocation
42-
- `bin/gtr`Main script (~1960 lines), contains `main()` dispatcher + all `cmd_*` handlers
49+
- `bin/gtr`Entry point: sources libraries and commands, contains `main()` dispatcher
4350

4451
### Module Structure
4552

46-
| File | Purpose |
47-
| ----------------- | ----------------------------------------------------------------------------------------------------------- |
48-
| `lib/core.sh` | Worktree CRUD: `create_worktree`, `remove_worktree`, `list_worktrees`, `resolve_target`, `resolve_base_dir` |
49-
| `lib/config.sh` | Git config wrapper with precedence: `cfg_get`, `cfg_default`, `cfg_get_all` |
50-
| `lib/copy.sh` | File/directory copying with glob patterns: `copy_patterns`, `copy_directories` |
51-
| `lib/hooks.sh` | Hook execution: `run_hooks_in` for postCreate/preRemove/postRemove |
52-
| `lib/ui.sh` | Logging (`log_error`, `log_info`, `log_warn`), prompts, formatting |
53-
| `lib/platform.sh` | OS detection, GUI helpers |
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).
5468

5569
### Adapters
5670

57-
Editor adapters in `adapters/editor/` and AI adapters in `adapters/ai/` are dynamically sourced via `load_editor_adapter()` and `load_ai_adapter()` in `bin/gtr`.
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/`.
5872

59-
**Editor adapters** (atom, cursor, emacs, idea, nano, nvim, pycharm, sublime, vim, vscode, webstorm, zed): implement `editor_can_open()` + `editor_open(path)`.
73+
**Registry-defined adapters**: atom, cursor, emacs, idea, nvim, pycharm, sublime, vim, vscode, webstorm, zed (editors) and aider, auggie, codex, continue, copilot, gemini, opencode (AI).
6074

61-
**AI adapters** (aider, auggie, claude, codex, continue, copilot, cursor, gemini, opencode): implement `ai_can_start()` + `ai_start(path, args...)`.
75+
**Custom adapter files**: `adapters/editor/nano.sh`, `adapters/ai/claude.sh`, `adapters/ai/cursor.sh` — these implement `editor_can_open()`/`editor_open()` or `ai_can_start()`/`ai_start()` directly.
6276

63-
**Generic fallback**: `GTR_EDITOR_CMD` / `GTR_AI_CMD` env vars allow custom tools without adapter files.
77+
**Loading order**: file override → registry → generic PATH fallback. `GTR_EDITOR_CMD` / `GTR_AI_CMD` env vars allow custom tools without adapters.
6478

6579
### Command Flow
6680

@@ -106,15 +120,17 @@ cmd_editor() → resolve_target() → load_editor_adapter() → editor_open()
106120

107121
### Adding a New Command
108122

109-
1. Add `cmd_<name>()` function in `bin/gtr`
110-
2. Add case entry in `main()` dispatcher (around line 71)
111-
3. Add help text in `cmd_help()`
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`
112126
4. Update all three completion files: `completions/gtr.bash`, `completions/_git-gtr`, `completions/git-gtr.fish`
113127
5. Update README.md
114128

115129
### Adding an Adapter
116130

117-
Create `adapters/{editor,ai}/<name>.sh` implementing the two required functions (see existing adapters for patterns). Then update: help text in `cmd_help()` and `load_*_adapter()`, all three completions, README.md.
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/claude.sh` for an example). File-based adapters take priority over registry entries.
118134

119135
### Updating the Version
120136

@@ -128,6 +144,17 @@ When adding commands or flags, update all three files:
128144
- `completions/_git-gtr` (Zsh)
129145
- `completions/git-gtr.fish` (Fish)
130146

147+
## Critical Gotcha: `set -e`
148+
149+
`bin/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+
131158
## Code Style
132159

133160
- Shebang: `#!/usr/bin/env bash`

0 commit comments

Comments
 (0)