A CLI for managing git worktrees. It keeps all your worktrees under ~/.arbor/worktrees so you can switch between branches without stashing or losing context.
Git worktrees are great: multiple branches checked out at once, no stashing, no half-finished commits. The problem is that git worktree add makes you pick a directory every time, and you end up with worktrees scattered everywhere.
Arbor puts them all in one place. arbor add feat/login creates the worktree, and with shell integration it cds you into it too. arbor rm feat/login cleans it up.
If you review PRs while working on your own feature, juggle hotfixes alongside long-running branches, or run tests in one worktree while coding in another — arbor keeps everything organized.
brew install morellodev/tap/arborDownload the latest binary for your platform from the releases page.
git clone https://github.com/morellodev/arbor.git
cd arbor
cargo install --path .You'll need Rust 1.85+ and Git.
# Clone a repo (creates a bare repo + worktree for the default branch)
arbor clone user/my-app
# Create a worktree for a branch
arbor add feat/login
# Switch to an existing worktree
arbor switch main
# See all worktrees
arbor ls
# Check which are dirty or ahead/behind
arbor status
# Done? Remove the worktree and its local branch
arbor rm -d feat/loginRun arbor init and it will show you what to add to your shell config:
arbor init
# ▸ Add the following to ~/.zshrc:
#
# eval "$(arbor init zsh)"This sets up two things: a wrapper so arbor add, arbor switch, and arbor clone automatically cd into the worktree, and dynamic tab completions for branch names.
The shell is auto-detected from $SHELL. You can also specify it explicitly:
# ~/.zshrc or ~/.bashrc
eval "$(arbor init zsh)" # or bash
# Fish: ~/.config/fish/config.fish
arbor init fish | source| Command | Alias | Description |
|---|---|---|
arbor add <branch> [--repo <name>] [--no-hooks] |
Create a worktree. Checks out an existing local branch, tracks a remote branch, or creates a new one. --repo lets you add from any directory. --no-hooks skips post-create hooks. |
|
arbor switch <branch> |
Switch to an existing worktree. Errors if the worktree doesn't exist. | |
arbor list [--all] [--json] |
ls |
List worktrees for the current repo. --all lists across all repos. --json for machine-readable output. |
arbor remove <branch> [-f] [-d] |
rm |
Remove a worktree. -f forces removal of dirty worktrees. -d also deletes the local branch. |
arbor dir <branch> |
Print the worktree path for a branch. Accepts both feature/auth and feature-auth. |
|
arbor clone <url> [--no-worktree] [--no-hooks] |
Clone as a bare repo and create a worktree for the default branch. Supports user/repo shorthand for GitHub. --no-hooks skips post-create hooks. |
|
arbor status [--short] [--all] |
Show dirty/clean state and ahead/behind counts for all worktrees. --all shows across all repos. |
|
arbor fetch [--all] |
Fetch from origin in the current bare repo. --all fetches across all repos. |
|
arbor clean [-d] |
Interactively select and remove unused worktrees. -d also deletes local branches. |
|
arbor prune |
Remove stale worktree references. | |
arbor init [shell] |
Set up shell integration (cd wrapper + completions). Auto-detects shell from $SHELL. |
arbor add feat/login inside a repo called my-app creates a worktree at:
~/.arbor/worktrees/my-app/feat-login
Slashes in branch names become dashes in the directory name.
For a worktree-only workflow, start with arbor clone to set up a bare repo:
arbor clone user/my-app
arbor add feat/loginOn first run, arbor creates ~/.arbor/config.toml:
repos_dir = "~/.arbor/repos"
worktree_dir = "~/.arbor/worktrees"Change these to store worktrees and bare repos somewhere else.
You can run commands automatically after a worktree is created by adding a .arbor.toml file to your repo root:
[hooks]
post_create = "npm install"Multiple commands are supported:
[hooks]
post_create = ["npm install", "cp .env.example .env"]Hooks run inside the new worktree directory with these environment variables available:
| Variable | Description |
|---|---|
ARBOR_WORKTREE |
Absolute path to the new worktree |
ARBOR_BRANCH |
Branch name |
ARBOR_REPO |
Repository name |
ARBOR_EVENT |
Hook event name (post_create) |
Hook output streams to stderr so it doesn't interfere with cd $(arbor add ...) piping. If a hook fails, arbor prints a warning and continues — the worktree is still created.
To skip hooks for a single invocation, pass --no-hooks:
arbor add feat/login --no-hooksIf arbor isn't what you're after, here are some other tools in this space:
| arbor | git worktree (bare) |
git-branchless | git-town | |
|---|---|---|---|---|
| Worktree management | Yes | Yes (manual) | Yes | No |
| Central worktree directory | Yes | No (you pick each time) | No | N/A |
| Auto-cd into worktree | Yes (shell integration) | No | No | No |
| GitHub shorthand clone | Yes (user/repo) |
No | No | No |
| Status across worktrees | Yes | No | Yes | No |
| Stacked diffs / rebase workflows | No | No | Yes | Yes |
| Branch sync with remote | No | No | Yes | Yes |
arbor is intentionally narrow. It manages worktrees and gets out of your way. If you need stacked diffs or branch sync workflows, git-branchless or git-town are better fits.
