This file provides guidance to coding agents when working with code in this repository.
Git Workers is an interactive CLI tool for managing Git worktrees, written in Rust. It provides a menu-driven interface for creating, deleting, switching, and renaming worktrees, with shell integration for automatic directory switching.
# Development build
cargo build
# Release build
cargo build --release
# Run directly (development)
cargo run
# Run the binary
./target/debug/gw
./target/release/gw
# Run tests
cargo test
# Run specific test
cargo test test_name
# Run tests single-threaded (for flaky tests)
cargo test -- --test-threads=1
# Run tests with output for debugging
cargo test test_name -- --nocapture
# Run with logging enabled
RUST_LOG=debug cargo run
RUST_LOG=git_workers=trace cargo run# Format check and apply
cargo fmt --check
cargo fmt
# Clippy (linter)
cargo clippy --all-features -- -D warnings
# Type check
cargo check --all-features
# Generate documentation
cargo doc --no-deps --open
# Run all checks (using bun if available)
bun run check
# Coverage report (requires cargo-llvm-cov)
cargo llvm-cov --html --lib --ignore-filename-regex '(tests/|src/main\.rs|src/bin/)' --open- Follow
Conventional Commitsfor all commit messages - Format:
<type>(<scope>)?: <description> - Common types in this repository:
feat: user-facing feature additionsfix: bug fixes and behavior correctionsrefactor: structural changes without behavior changestest: test additions or test-only refactorsdocs: documentation-only changeschore: maintenance work with no product behavior impactci: CI or automation workflow changesbuild: build system or dependency management changes
- Keep the subject concise, imperative, and lowercase where natural
- Do not mix structural changes and behavior changes in the same commit
- Examples:
refactor(app): move menu dispatch into app modulefix(create): preserve selected tag when creating worktreetest(rename): cover cancel flow in rename prompt
# Install locally from source
cargo install --path .
# Setup shell integration
./setup.sh
# Or manually add to ~/.bashrc or ~/.zshrc:
source /path/to/git-workers/shell/gw.sh- Interactive worktree operations are driven from the
applayer and delegated intousecases - Existing public paths such as
commands,infrastructure, andrepository_infoare kept as compatibility facades - The project supports shell-assisted directory switching, lifecycle hooks, file copying, tag-based creation, and validated custom paths
- Current refactoring policy prioritizes preserving observable behavior over aggressively removing compatibility layers
src/
├── main.rs # Thin CLI entry point (`--version` + app startup)
├── lib.rs # Public module exports and backward-compatible re-exports
├── app/ # Menu loop, action dispatch, presenter helpers
├── usecases/ # Main worktree operations (create/delete/list/rename/switch/search)
├── adapters/ # Config, shell, filesystem, Git, UI, and hook adapters
├── domain/ # Repository context and domain-level helpers
├── commands/ # Backward-compatible facades over usecases
├── config.rs # Configuration model and access helpers
├── repository_info.rs # Backward-compatible facade for repo context display
├── infrastructure/ # Backward-compatible exports for older module paths
├── core/ # Legacy core logic retained during migration
├── ui.rs # User interface abstraction used by prompts and tests
├── input_esc_raw.rs # ESC-aware input helpers
├── constants.rs # Centralized strings and formatting constants
├── support/ # Terminal and styling support utilities
└── utils.rs # Shared utilities and compatibility helpers
main->appapp->usecasesusecases->adapters,domain,ui,config,infrastructurecommandsandrepository_infoshould stay thin and delegate to the newer modules- Public compatibility paths are intentionally preserved unless a breaking change is explicitly planned
- dialoguer + console: Interactive CLI (Select, Confirm, Input prompts)
- git2: Git repository operations (branch listing, commit info)
- std::process::Command: Git CLI invocation (worktree add/prune)
- colored: Terminal output coloring
- fuzzy-matcher: Worktree search functionality
- indicatif: Progress bar display
Automatic directory switching on worktree change requires special implementation due to Unix process restrictions:
- Binary writes path to file specified by
GW_SWITCH_FILEenv var - Shell function (
shell/gw.sh) reads the file and executescd - Legacy fallback:
SWITCH_TO:/pathmarker on stdout
Define lifecycle hooks in .git-workers.toml:
[hooks]
post-create = ["npm install", "cp .env.example .env"]
pre-remove = ["rm -rf node_modules"]
post-switch = ["echo 'Switched to {{worktree_name}}'"]Template variables:
{{worktree_name}}: The worktree name{{worktree_path}}: Absolute path to worktree
First worktree creation offers two options:
- Same level as repository:
../worktree-name- Creates worktrees as siblings to the repository - Custom path: User specifies any relative path (e.g.,
main,branches/feature,worktrees/name)
For bare repositories with .bare pattern, use custom path to create worktrees inside the project directory:
- Custom path:
main→my-project/main/ - Custom path:
feature-1→my-project/feature-1/
Subsequent worktrees follow the established pattern automatically.
All interactive prompts support ESC cancellation through custom input_esc_raw module:
input_esc_raw()returnsOption<String>(None on ESC)Select::interact_opt()for menu selectionsConfirm::interact_opt()for confirmations
Since Git lacks native rename functionality:
- Move directory with
fs::rename - Update
.git/worktrees/<name>metadata directory - Update gitdir files in both directions
- Optionally rename associated branch if it matches worktree name
- GitHub Actions:
.github/workflows/ci.yml(test, lint, build) - Release workflow:
.github/workflows/release.yml(automated releases) - Homebrew tap: Updates
wasabeef/homebrew-gw-tapon release - Pre-commit hooks:
lefthook.yml(format, clippy)
- The repository currently has 51 test files across
unit,integration,e2e, andperformance - The safest full verification command is
cargo test --all-features -- --test-threads=1 cargo fmt --checkandcargo clippy --all-features -- -D warningsare expected before shipping significant changes- Some tests remain sensitive to parallel execution because they manipulate Git repositories and process-wide state
- Use
--nocapturewhen debugging interactive or repository-context behavior
- "Permission denied" when running tests: Tests create temporary directories; ensure proper permissions
- "Repository not found" errors: Tests require git to be configured (
git config --global user.name/email) - Flaky test failures: Use
--test-threads=1to avoid race conditions in worktree operations - "Lock file exists" errors: Clean up
.git/git-workers-worktree.lockif tests are interrupted
-
ALWAYS use inline variable syntax in format! macros:
format!("{variable}")instead offormat!("{}", variable) -
This applies to ALL format-like macros:
format!,println!,eprintln!,log::info!,log::warn!,log::error!, etc. -
Examples:
// ✅ Correct format!("Device {name} created successfully") println!("Found {count} devices") log::info!("Starting device {identifier}") // ❌ Incorrect format!("Device {} created successfully", name) println!("Found {} devices", count) log::info!("Starting device {}", identifier)
-
This rule is enforced by
clippy::uninlined_format_argswhich treats violations as errors in CI -
Apply this consistently across ALL files including main source, tests, examples, and binary targets
- Only works within Git repositories
- Requires initial commit (bare repositories supported)
- Cannot rename current worktree
- Cannot rename worktrees with detached HEAD
- Shell integration supports Bash/Zsh only
- No Windows support (macOS and Linux only)
- The CLI is primarily interactive, with
--versionas the supported non-interactive flag
Bare repositories:
- Check main/master worktree directories only
Non-bare repositories:
- Current directory (current worktree)
- Main/master worktree directories (fallback)
Ignored files such as .env can be copied into new worktrees through .git-workers.toml.
[files]
copy = [".env", ".env.local", "config/local.json"]
# source = "path/to/source"- Copy runs after worktree creation and before post-create hooks
- Missing source files warn but do not abort worktree creation
- Paths are validated to prevent traversal and invalid destinations
- File handling includes symlink checks, depth limits, and permission preservation where applicable
.github/workflows/ci.ymlruns the main validation pipeline.github/workflows/release.ymlhandles release automationlefthook.ymlruns pre-commit checks such asfmtandclippypackage.jsonprovides helper scripts:bun run formatbun run lintbun run testbun run check
The codebase uses two approaches for Git operations:
- git2 library: For read operations (listing branches, getting commit info)
- std::process::Command: For write operations (worktree add/remove) to ensure compatibility
Example pattern:
// Read operation using git2
let repo = Repository::open(".")?;
let branches = repo.branches(Some(BranchType::Local))?;
// Write operation using Command
Command::new("git")
.args(&["worktree", "add", path, branch])
.output()?;- Use
anyhow::Resultfor application-level errors - Provide context with
.context()for better error messages - Show user-friendly messages via
utils::display_error() - Never panic in production code; handle all error cases gracefully
The ui::UserInterface trait enables testing of interactive features:
- Mock implementations for tests
- Real implementation wraps dialoguer
- All user interactions go through this abstraction