git-arborist
v0.1.15
Published
Git Worktrees, Finally Simple
Readme
Git Arborist — Git Worktrees, Finally Simple
A fast, opinionated CLI for managing git worktrees. Zero friction.
arb wraps git's worktree commands with smart defaults, automated project setup, a plugin system for GitHub/Graphite integrations, and a live TUI dashboard.
Built with TypeScript + Bun. Ships as a single binary.
Install
Homebrew (macOS / Linux)
brew install rspencerfink/git-arborist/arbcurl one-liner
curl -fsSL https://raw.githubusercontent.com/rspencerfink/git-arborist/main/install.sh | bashnpm / bun
bun add -g git-arborist # requires Bun runtime
# or
npm i -g git-arborist # also requires Bun installedGitHub Releases
Download the binary for your platform from Releases, then:
chmod +x arb-darwin-arm64 # or your platform
mv arb-darwin-arm64 /usr/local/bin/arbFrom source (requires Bun)
git clone https://github.com/rspencerfink/git-arborist.git
cd git-arborist
bun install
bun run build # produces ./arb binary
mv arb /usr/local/bin/Shell integration
arb go and arb main need to change your shell's directory. Add this to your shell config:
# zsh (~/.zshrc)
eval "$(arb shell-init zsh)"
# bash (~/.bashrc)
eval "$(arb shell-init bash)"
# fish (~/.config/fish/config.fish)
arb shell-init fish | sourceShell completions
# zsh
arb completions zsh > ~/.zfunc/_arb
# bash
arb completions bash > /etc/bash_completion.d/arb
# fish
arb completions fish > ~/.config/fish/completions/arb.fishQuick start
cd your-repo
# Create a worktree for a new branch
arb add -b my-feature
# Switch to it (requires shell integration)
arb go my-feature
# See all worktrees
arb ls
# Go back to the main worktree
arb main
# Done? Remove it
arb rm my-featureCommands
Lifecycle
| Command | Description |
|---------|-------------|
| arb add <branch> | Create a worktree for an existing branch |
| arb add -b <name> | Create a new branch + worktree |
| arb add -b <name> --base <ref> | Create a new branch from a specific ref |
| arb rm [name] | Remove a worktree (interactive picker if no name) |
| arb rm <name> --force | Force remove even if dirty |
| arb rm <name> --branch | Also delete the branch |
| arb go [name] | Switch to a worktree (interactive picker if no name) |
| arb ls | List all worktrees with status |
| arb main | Switch back to the main worktree |
Status & cleanup
| Command | Description |
|---------|-------------|
| arb status | Formatted table with branch, head, dirty status, ahead/behind |
| arb dash | Live TUI dashboard with auto-refresh |
| arb prune | Remove worktrees whose branches are merged |
| arb prune --dry-run | Preview what would be pruned |
| arb gc | Prune stale refs + run git garbage collection |
| arb clean <name> | Reset a worktree to a clean state |
Project setup
| Command | Description |
|---------|-------------|
| arb init | Scaffold a .arborist.toml config file in your repo |
| arb setup <name> | Re-run setup hooks on an existing worktree |
| arb clone <repo> | Clone a repository |
| arb clone <repo> --bare | Clone in bare-worktree layout (.bare/ + worktrees as siblings) |
Utilities
| Command | Description |
|---------|-------------|
| arb run <name> -- <cmd> | Run a command inside a worktree's directory |
| arb open <name> | Open a worktree in your editor |
| arb tmux <name> | Open a worktree in a new tmux window |
| arb config list | Show current configuration |
| arb config get <key> | Get a config value |
| arb config edit | Open project config in $EDITOR |
| arb config edit --global | Open global config in $EDITOR |
| arb plugin list | List available plugins and their status |
Configuration
arb uses TOML configuration with two levels:
- Project:
.arborist.tomlin your repo root (commit this) - Global:
~/.config/arborist/config.toml
Project config takes precedence over global config.
Example .arborist.toml
# Where worktrees are created (relative to repo root)
# Available variables: {{ branch }}, with filters: {{ branch | sanitize }}
worktree_path = "../.worktrees/{{ branch | sanitize }}"
# Editor for `arb open`
editor = "code"
# Copy files from main worktree after creation
[[setup.copy]]
from = ".env.local"
# Symlink directories from main worktree
[[setup.symlink]]
from = "node_modules"
# Run commands after creation (conditional on file existence)
[[setup.run]]
command = "bun install --frozen-lockfile"
if_exists = "bun.lock"
[[setup.run]]
command = "cp .env.example .env"
if_exists = ".env.example"
# Enable plugins
[plugins.deps]
enabled = true
strategy = "symlink" # "symlink" | "install" | "copy"
[plugins.env]
enabled = true
[plugins.tmux]
enabled = true
[plugins.github]
enabled = true
[plugins.graphite]
enabled = trueTemplate variables
The worktree_path setting supports template variables:
| Variable | Example input | Output |
|----------|--------------|--------|
| {{ branch }} | feature/auth | feature/auth |
| {{ branch \| sanitize }} | feature/auth@v2! | feature/auth-v2 |
| {{ branch \| lowercase }} | Feature/Auth | feature/auth |
| {{ branch \| uppercase }} | feature/auth | FEATURE/AUTH |
Plugins
Plugins hook into worktree lifecycle events and can extend the command tree.
Built-in plugins
deps — Dependency management
Automatically handles node_modules when creating worktrees.
[plugins.deps]
enabled = true
strategy = "symlink" # fast, disk-efficient (default)
# strategy = "install" # fresh install via detected package manager
# strategy = "copy" # full copy of node_modulesDetects your package manager automatically (bun, pnpm, yarn, npm).
env — Environment files
Copies .env* files from the main worktree to new worktrees (skips .env.example).
[plugins.env]
enabled = truetmux — Tmux integration
Automatically opens new worktrees in a tmux window (only when running inside tmux).
[plugins.tmux]
enabled = truegithub — GitHub PR status
Shows PR status and CI checks in arb ls, arb dash, and on worktree creation. Requires the GitHub CLI (gh).
[plugins.github]
enabled = trueAdds the arb pr command to show PR status for all worktrees.
graphite — Stack awareness
Shows Graphite stack position and restack warnings. Requires the Graphite CLI (gt).
[plugins.graphite]
enabled = trueAdds commands:
arb stack— Show the current Graphite stackarb submit— Submit the current stack
TUI Dashboard
arb dash launches a live, interactive dashboard:
arb dashboard │ 3 worktrees │ refreshed 2:15:30 PM
Branch Head Status Sync Path
──────────────────────────────────────────────────────────────────────────────
* main a1b2c3d ✓ clean — /repo
feature/auth d4e5f6a ~2 ?1 ↑1 /worktrees/feature-auth
bugfix/login g7h8i9j +1 ↓2 /worktrees/bugfix-login
[j/k] navigate [r] refresh [q] quitFlags:
--pr— Show GitHub PR column (or enable thegithubplugin)--graphite— Show Graphite stack column (or enable thegraphiteplugin)
Auto-refreshes every 5 seconds.
Worktree path layout
By default, worktrees are created as siblings to your repo:
projects/
├── my-repo/ # main worktree
├── .worktrees/
│ ├── feature-auth/ # arb add -b feature/auth
│ └── bugfix-login/ # arb add -b bugfix/loginWith arb clone --bare, you get a flat layout:
projects/
├── my-repo/
│ ├── .bare/ # git object store
│ ├── .git # gitdir pointer to .bare
│ └── main/ # main worktreePlugin API
Create custom plugins as .ts files in .arborist/plugins/ or as npm packages named arborist-plugin-*.
import type { ArboristPlugin } from 'git-arborist/plugins/types';
const myPlugin: ArboristPlugin = {
name: 'my-plugin',
version: '1.0.0',
hooks: {
async 'worktree:created'(ctx, wt) {
// Runs after a worktree is created
},
async 'worktree:removing'(ctx, wt) {
// Return false to prevent removal
},
async 'status:extend'(ctx, wt) {
// Add custom columns to arb ls / arb dash
return [{ label: 'Deploy', value: 'staging' }];
},
},
commands: [
{
name: 'my-command',
description: 'Does something custom',
async run(ctx, args) {
// Available as `arb my-command`
},
},
],
};
export default myPlugin;Available hooks
| Hook | When | Arguments |
|------|------|-----------|
| worktree:created | After worktree creation | (ctx, worktreeInfo) |
| worktree:ready | After creation + setup complete | (ctx, worktreeInfo) |
| worktree:removing | Before removal (return false to cancel) | (ctx, worktreeInfo) |
| worktree:removed | After removal | (ctx, { name, branch }) |
| worktree:switch | When switching worktrees | (ctx, from, to) |
| status:extend | During status display | (ctx, worktreeInfo) → StatusExtension[] |
| prune:shouldRemove | During prune evaluation | (ctx, worktreeInfo) → boolean \| null |
Development
bun install
bun run dev -- ls # run commands during development
bun test # run test suite
bun run lint # lint with biome
bun run build # compile to standalone binaryLicense
MIT
