Grove is a CLI tool that encapsulates the patterns that I use for working with Git worktrees locally on my machine. To learn more about this pattern, you can check out this blog post.
- Initialize repos with a bare clone optimized for worktrees
- Create, list, and remove worktrees
- Sync with origin and prune stale worktrees
- Run commands from anywhere within the project hierarchy
- Shell integration for seamless directory navigation
- Self-update to the latest version or PR build
- Linux (x64, arm64)
- macOS (x64, arm64)
- Windows (x64)
curl https://i.safia.sh/captainsafia/grove | shirm https://i.safia.sh/captainsafia/grove.ps1 | iexThis will download the appropriate binary for your system and install it to ~/.grove/bin.
To install a specific version:
curl https://i.safia.sh/captainsafia/grove/v1.0.0 | shFor Windows PowerShell:
irm https://i.safia.sh/captainsafia/grove/v1.0.0.ps1 | iexCreate a new directory structure optimized for git worktree workflows:
grove init https://github.com/user/repo.gitThis command will:
- Create a directory named after the repository (e.g.,
repo/) - Clone the repository as a bare clone into
repo/repo.git/ - Configure the remote fetch to support all branches
- Provide instructions for creating worktrees
After initialization, you can create worktrees:
cd repo
grove add main
grove add feature/new-featureCreate a new worktree for a branch:
grove add feature/new-featureCreate a new worktree with an auto-generated adjective-noun name:
grove add
# Example generated name: quiet-meadow
# If .groverc sets "branchPrefix": "safia", example: safia/quiet-meadow
# Directory remains: quiet-meadow
# branchPrefix only accepts alphanumeric charactersTrack a remote branch:
grove add feature/new-feature --track origin/feature/new-featureBootstrap a newly created worktree with project-scoped commands:
{
"branchPrefix": "safia",
"bootstrap": {
"commands": [
{ "program": "npm", "args": ["install"] },
{ "program": "cargo", "args": ["check"] }
]
}
}Save this as .groverc in your Grove project root (the directory that contains your bare clone, for example repo/.groverc next to repo/repo.git).
When grove add is called without an explicit branch name, Grove generates an adjective-noun name and prepends branchPrefix to the branch name when configured. branchPrefix must be alphanumeric only (letters and numbers). The worktree directory keeps the generated base name.
When grove add creates a worktree, it runs each bootstrap command in order inside that new worktree directory.
- Commands must be portable across Linux/macOS/Windows.
- Use executable + args only (no shell syntax like pipes,
&&, or redirects). - If one command fails, Grove continues running the remaining commands and reports a partial bootstrap state.
Remove a worktree:
grove remove feature/new-featureForce removal even with uncommitted changes:
grove remove feature/new-feature --forceSkip confirmation prompt:
grove remove feature/new-feature --yesOpen a new shell session in a worktree directory:
grove go feature-branchThis spawns a new shell in the worktree directory. Exit the shell (Ctrl+D or exit) to return to your previous directory.
You can also navigate by partial branch name for nested branches:
# If you have a worktree for feature/my-feature
grove go my-featureThe GROVE_WORKTREE environment variable is set to the branch name while in the worktree shell.
For a smoother experience, you can set up shell integration so grove go changes your current directory instead of spawning a new shell:
Bash:
echo 'eval "$(grove shell-init bash)"' >> ~/.bashrc
source ~/.bashrcZsh:
echo 'eval "$(grove shell-init zsh)"' >> ~/.zshrc
source ~/.zshrcFish:
echo 'eval "$(grove shell-init fish)"' >> ~/.config/fish/config.fish
source ~/.config/fish/config.fishWith shell integration enabled, grove go feature-branch will directly change your working directory.
Grove commands work from anywhere within your project hierarchy - you don't need to be in the bare clone directory. Whether you're deep inside a worktree's source code or at the project root, grove automatically discovers the repository:
# Works from inside a worktree
cd ~/projects/myproject/feature-branch/src/components
grove list # Discovers and lists all worktrees
# Works from the worktree root
cd ~/projects/myproject/feature-branch
grove add another-feature
# Works from the bare clone
cd ~/projects/myproject/myproject.git
grove syncGrove caches the discovered repository path in the GROVE_REPO environment variable for faster subsequent commands.
grove listShow detailed information:
grove list --detailsShow only dirty worktrees:
grove list --dirtyUpdate the bare clone with the latest changes from origin:
grove syncThis fetches the default branch (main or master) from origin and updates the local reference.
Sync a specific branch:
grove sync --branch developPreview what would be removed:
grove prune --dry-runRemove worktrees for branches merged to main:
grove pruneForce removal even if worktrees have uncommitted changes:
grove prune --forceUse a different base branch:
grove prune --base developRemove worktrees older than a specific duration (bypasses merge check):
Note: When using --older-than, the merge status check is bypassed, and all worktrees older than the specified duration will be removed. The --base flag cannot be used with --older-than.
You can use human-friendly formats (e.g., 30d, 2w, 6M, 1y) or ISO 8601 duration format (e.g., P30D, P2W, P6M, P1Y):
# Remove worktrees older than 30 days
grove prune --older-than 30d
# Remove worktrees older than 6 months
grove prune --older-than 6M
# Remove worktrees older than 1 year
grove prune --older-than 1y
# Preview what would be removed for worktrees older than 2 weeks
grove prune --older-than 2w --dry-run
# ISO 8601 format is also supported
grove prune --older-than P30DUpdate grove to the latest version:
grove self-updateUpdate to a specific version:
grove self-update v1.0.0
# or
grove self-update 1.0.0Update to a specific PR build (requires GitHub CLI):
grove self-update --pr 42Note: The self-update command uses the same installation script as the initial installation. If you installed grove using the quick install method, this command will update the binary in ~/.grove/bin. If you installed grove using a different method (e.g., manually downloading the binary), you may need to update it manually.
grove init <git-url>- Create a new worktree setupgrove add [name] [options]- Create a new worktreegrove go <name>- Navigate to a worktreegrove remove <name> [options]- Remove a worktreegrove list [options]- List all worktreesgrove sync [options]- Sync the bare clone with origingrove prune [options]- Remove worktrees for merged branchesgrove shell-init <shell>- Output shell integration function (bash, zsh, or fish)grove self-update [version] [options]- Update grove to a specific version or PRgrove version- Show version informationgrove help [command]- Show help
- Rust (stable toolchain)
- Git
# Clone the repository
git clone https://github.com/captainsafia/grove.git
cd grove
# Build the project
cargo build
# Build optimized release binary
cargo build --release# Build debug binary
cargo build
# Build optimized release binary
cargo build --release
# Run directly in development
cargo run -- <command>
# Type check without building
cargo check
# Run all tests
cargo test
# Clean build artifacts
cargo clean