A fast, intuitive CLI that makes Git worktrees as simple as switching branches.
Without Grove:
git clone --bare git@github.com:org/repo.git .bare
echo "gitdir: ./.bare" > .git
git worktree add main
cd main
cp ../other-worktree/.env . # Don't forget this
npm install # Or this
# Later: cd ../feat-auth to switchWith Grove:
grove clone git@github.com:org/repo
grove add feat/auth --switch # .env copied, hooks run automatically
grove switch main # Just like git checkout, but each branch keeps its own directorydemo.mp4
Note
Grove is under active development. Core functionality is stable, but APIs may change between major versions.
New to worktrees?
Git worktrees let you work on multiple branches simultaneously in separate directories. No stashing. No "wrong branch" mistakes. Your work stays exactly where you left it.
The catch: git worktree is clunky. Grove makes it feel like git checkout β but each branch gets its own persistent directory.
- Git 2.48+ β Grove uses
--relative-pathsfor portable worktrees
curl -fsSL https://raw.githubusercontent.com/sQVe/grove/main/install.sh | shgo install github.com/sqve/grove/cmd/grove@latestgit clone https://github.com/sQVe/grove && cd grove
go build -o grove ./cmd/grove
sudo mv grove /usr/local/bin/Download .deb or .rpm packages from GitHub Releases.
Grove works without additional dependencies, but installing the GitHub CLI (gh) enables enhanced features:
- PR worktrees: Create worktrees from pull requests with
grove add --pr 123orgrove clone https://github.com/owner/repo/pull/123 - Squash-merge detection:
grove pruneaccurately detects branches merged via GitHub's squash-and-merge, even with multiple commits. Withoutgh, only single-commit squash merges are detected via git.
See GitHub CLI installation for setup instructions.
Without this, grove switch only prints a path β you'd have to cd manually. Add to your shell config:
# bash (~/.bashrc) or zsh (~/.zshrc)
eval "$(grove switch shell-init)"
# fish (~/.config/fish/config.fish)
grove switch shell-init | source
# powershell ($PROFILE)
grove switch shell-init | Invoke-ExpressionTab completion for commands, flags, and worktree names.
# bash
eval "$(grove completion bash)"
# zsh
eval "$(grove completion zsh)"
# fish
grove completion fish | source
# powershell
grove completion powershell | Invoke-Expression# Clone a repository
grove clone https://github.com/owner/repo
cd repo
# Start a feature
grove add feat/auth --switch
# Check your worktrees
grove list
# Switch back to main
grove switch main
# Clean up when done
grove remove feat/auth --branchgrove clone <url> [directory]
Clone a repository into a Grove workspace.
Flags:
--branches <list>β Comma-separated branches to create worktrees for--shallowβ Shallow clone (depth=1)-v, --verboseβ Show git output
Examples:
grove clone https://github.com/owner/repo
grove clone https://github.com/owner/repo my-project
grove clone https://github.com/owner/repo --branches main,develop
grove clone https://github.com/owner/repo/pull/123 # Clone and checkout PRgrove init new [directory] / grove init convert
Initialize a Grove workspace.
Subcommands:
new [directory]β Create new workspaceconvertβ Convert existing repository
Flags (convert):
--branches <list>β Additional branches to create worktrees for-v, --verboseβ Show git output
Examples:
grove init new my-project
grove init convert
grove init convert --branches develop,staginggrove add [branch|PR-URL|ref]
Add a worktree for a branch, pull request, or ref.
Flags:
-s, --switchβ Switch to worktree after creating--base <branch>β Create new branch from base instead of HEAD--name <name>β Custom directory name-d, --detachβ Detached HEAD state--pr <number>β Create worktree for a pull request--from <worktree>β Source worktree for file preservation (name or branch)--resetβ Reset diverged PR branch to match remote (use with--pr)
Examples:
grove add feat/auth
grove add feat/auth --switch
grove add --base main feat/auth
grove add --pr 123 # PR by number
grove add --pr 123 --reset # PR, discarding local commits
grove add --detach v1.0.0 # Tag in detached HEAD
grove add --from dev feat/auth # Copy .env from dev worktreegrove switch <worktree>
Switch to a worktree by directory or branch name.
Requires shell integration (see Setup section).
Examples:
grove switch main
grove switch feat-auth
grove switch feat/authgrove list
List all worktrees with status.
Flags:
--fastβ Skip remote sync checks--filter <status>β Filter by:dirty,ahead,behind,gone,locked--jsonβ JSON output-v, --verboseβ Show paths and upstreams
Examples:
grove list
grove list --fast
grove list --filter dirty
grove list --filter ahead,behind
grove list --jsongrove status
Show current worktree status.
Flags:
-v, --verboseβ Show all diagnostic sections--jsonβ JSON output
Examples:
grove status
grove status --verbosegrove remove <worktree>...
Remove one or more worktrees.
Flags:
-f, --forceβ Remove even if dirty or locked--branchβ Also delete the branch
Examples:
grove remove feat-auth
grove remove feat-auth --branch
grove remove --force wip
grove remove feat-auth bugfix-123 # Remove multiplegrove move <worktree> <new-branch>
Rename a branch and its worktree.
Examples:
grove move feat/old feat/newgrove lock <worktree>...
Lock one or more worktrees to prevent removal.
Flags:
--reason <text>β Reason for locking
Examples:
grove lock main
grove lock release --reason "Production release"
grove lock feat-auth bugfix-123 # Lock multiplegrove unlock <worktree>...
Unlock one or more worktrees.
Examples:
grove unlock feat-auth
grove unlock feat-auth bugfix-123 # Unlock multiplegrove prune
Remove worktrees with deleted upstream branches. Dry-run by default.
When removing worktrees whose upstream was deleted on remote, local branches are also deleted.
Flags:
--commitβ Actually remove (default is dry-run)-f, --forceβ Remove even if dirty, locked, or unpushed--stale <duration>β Include inactive worktrees (e.g.,30d,2w)--mergedβ Include branches merged into default branch--detachedβ Include detached worktrees
Examples:
grove prune # Dry-run
grove prune --commit # Actually remove
grove prune --stale 30d --commit
grove prune --merged --commit
grove prune --detached --commitgrove exec [worktrees...] -- <command>
Execute a command in worktrees.
Flags:
-a, --allβ Execute in all worktrees--fail-fastβ Stop on first failure
Examples:
grove exec --all -- npm install
grove exec main feat-auth -- git pull
grove exec --all --fail-fast -- go build
grove exec --all -- bash -c "npm install && npm test"grove config <subcommand>
Manage configuration.
Subcommands:
listβ Show all settingsget <key>β Get valueset <key> <value>β Set value (requires--sharedor--global)unset <key>β Remove setting (requires--sharedor--global)initβ Create.grove.tomltemplate
Flags:
--sharedβ Target.grove.toml--globalβ Target git config
Examples:
grove config list
grove config get preserve.patterns
grove config set --global plain true
grove config set --shared autolock.patterns "main,release/*"
grove config initgrove doctor
Diagnose workspace issues.
Flags:
--fixβ Auto-fix safe issues--jsonβ JSON output--perfβ Disk space analysis
Detects:
- Dependency versions (Git 2.48+, optional gh CLI)
- Broken
.gitpointers - Stale worktree entries
- Unreachable remotes
- Invalid
.grove.tomlsyntax - Hook commands not found in PATH
- Stale lock files
Examples:
grove doctor
grove doctor --fix
grove doctor --perfGrove uses a two-layer configuration system:
.grove.tomlβ Team settings (checked into repository)- Git config (
~/.gitconfig) β Personal settings
Run grove config init to create a .grove.toml template.
Default configuration
# Grove - Git worktree management
# https://github.com/sqve/grove
[preserve]
# Files to copy from the current worktree when creating a new one.
# Useful for environment files and local configuration that shouldn't be in git.
# Supports glob patterns. These patterns override git config grove.preserve.
patterns = [
".env",
".env.keys",
".env.local",
".env.*.local",
".envrc",
".grove.toml",
"docker-compose.override.yml",
]
# Path segments to exclude from preservation.
# Files containing any of these path segments will be skipped.
exclude = [
# Caches
".cache",
"__pycache__",
# Build output
"build",
"coverage",
"dist",
"out",
"target",
# Dependencies
".venv",
"node_modules",
"vendor",
"venv",
]
[link]
# Directories to symlink from the source worktree when creating a new one.
# Useful for sharing tool state (e.g., .beads) across worktrees.
# Matched against directory names in the worktree root.
# Creates relative symlinks so the workspace remains portable.
patterns = []
[hooks]
# Shell commands to run after creating a worktree.
# Runs sequentially, stops on first failure.
# Examples: ["npm install"], ["go mod download", "make setup"]
add = []
[autolock]
# Branch patterns to automatically lock when creating worktrees.
# Locked worktrees are protected from accidental deletion.
# Supports exact matches and trailing /* wildcards (e.g., "release/*").
patterns = ["develop", "main", "master"]
# Use Nerd Font icons in output (when not in plain mode).
nerd_fonts = true
# Threshold for marking worktrees as stale (no commits within this period).
# Format: number followed by d (days), w (weeks), or m (months).
stale_threshold = "30d"
# Disable colors and symbols in output.
plain = false
# Enable debug logging.
debug = falseBeads auto-discovers its .beads database by walking up the directory tree. Initialize Beads at the workspace root and all worktrees share the same state automatically:
cd my-project # workspace root (where .bare/ lives)
bd init --skip-hooksSince all worktrees are siblings under the workspace root, bd finds .beads/ in the parent directory from any worktree. No symlinks or configuration needed.
bd init installs git hooks with a relative core.hooksPath that doesn't resolve correctly from worktrees, so use --skip-hooks and set up sync hooks separately.
Manual core.hooksPath
Install hooks to .beads/hooks/ and set an absolute path from any worktree:
bd hooks install --beads
git config core.hooksPath "$(cd "$(git rev-parse --show-toplevel)/.." && pwd)/.beads/hooks"Hook framework integration
If you use a hook framework, register bd hook <stage> as a local hook for the pre-commit, post-merge, and post-checkout stages. This avoids core.hooksPath entirely. See the Beads hooks documentation for the supported stages.
Husky and lefthook set core.hooksPath to a relative path (.husky or .lefthook). In a bare worktree setup, this config is shared across all worktrees via .bare/config. The relative path resolves correctly from any worktree because the directory exists in every checkout. Re-run the installer after worktree creation to ensure the path is set:
Husky
[hooks]
add = ["npm install", "npx husky"]lefthook
[hooks]
add = ["lefthook install"]pre-commit writes hook scripts directly into the git hooks directory (.bare/hooks/), which is shared by all worktrees. Run pre-commit install once from any worktree β no Grove hook needed.
Next.js build output (.next) and Turborepo cache (.turbo) can be shared across worktrees to avoid redundant rebuilds. Never symlink node_modules/ β it contains absolute paths and branch-specific dependency versions.
[link]
patterns = [".next", ".turbo"]Rust target/ directories consume significant disk space. Sharing via symlink avoids duplicate compilation across worktrees.
[link]
patterns = ["target"]Parallel cargo build across worktrees sharing the same target directory will serialize on file locks.
AI coding tools store local configuration in git-ignored files that are lost in new worktrees. Preserve them to carry project context and settings across worktrees.
[preserve]
patterns = [
"CLAUDE.local.md",
".claude/settings.local.json",
".aider.conf.yml",
".copilot/config.json",
]These patterns are additive β they extend the default preserve patterns, not replace them.
Contributions welcome! See CONTRIBUTING.md for development setup and guidelines.