Git Worktrees Are Underrated

5 min read

  • git
  • cli
  • productivity
  • rust
  • open-source
Terminal showing multiple git worktrees

If you’ve ever stashed half-finished work to review a PR, or lost a train of thought switching branches, you should know about git worktrees. They’ve been in git since 2015, but most developers I talk to have never heard of them.

The problem with branches

The standard git workflow is linear. You’re on a branch, you need to context-switch, so you stash your changes (or worse, make a throwaway commit), switch branches, do your thing, switch back, and pop the stash. If you’re lucky, nothing conflicts.

This gets worse when you’re juggling multiple things at once. Reviewing a colleague’s PR while your feature branch is mid-refactor. Running tests on one branch while coding on another. Hotfixing production while your main work sits frozen.

A git repository has one working directory. One branch checked out at a time. Everything else has to wait.

Worktrees

Git worktrees let you check out multiple branches simultaneously, each in its own directory. They share the same .git history and objects, so you’re not cloning the entire repo, but each one has its own working directory and index.

git worktree add ../feat-login feat/login

Now feat/login is checked out in ../feat-login. You can open it in a separate editor window, run tests there, or just let it sit while you work on something else.

When you’re done:

git worktree remove ../feat-login

Why nobody uses them

If worktrees are so useful, why aren’t they more popular? I think it comes down to two things.

First, the directory problem. Every time you create a worktree, you have to pick a path. There’s no convention for where they go. After a week you end up with directories scattered across your filesystem: ../feat-login, ~/tmp/hotfix-auth, ../../pr-review. Good luck remembering where anything is.

Second, there are no ergonomics. You can’t easily list your worktrees with status info, cd into one quickly, or run something like npm install in the new directory after checkout. The raw git commands work, but they don’t make worktrees feel like a real workflow.

These aren’t fundamental problems with worktrees. They’re tooling gaps. That’s why I built arbor.

A better workflow with arbor

Arbor is a CLI that manages git worktrees. It keeps them organized in a central directory (~/.arbor/worktrees/) and handles the stuff that plain git doesn’t.

# Clone a repo (sets up a bare repo + default branch worktree)
arbor clone user/my-app

# Create a worktree for a new feature
arbor add feat/login

# Shell integration auto-cds you into the worktree
# You're now in ~/.arbor/worktrees/my-app/feat-login

# Need to review a PR? Open another worktree
arbor add fix/auth-bug

# See all your worktrees with dirty/clean status
arbor status

Every worktree for my-app lives under ~/.arbor/worktrees/my-app/. Branch slashes become dashes in directory names, so the filesystem stays tidy. When you’re done with a branch:

arbor rm -d feat/login

That removes the worktree and the local branch in one step.

What makes it stick

I’ve been using worktrees for a while, but I kept falling off because of the friction. A few things in arbor made the difference for me.

Shell integration. arbor init sets up a wrapper so that arbor add and arbor switch automatically cd you into the worktree directory. Without it you create the worktree and then have to cd into it yourself every single time, which gets old fast.

Post-create hooks. You can add an .arbor.toml to your repo:

[hooks]
post_create = "npm install"

Every new worktree gets its dependencies installed automatically. You can chain commands too:

[hooks]
post_create = ["npm install", "cp .env.example .env"]

And arbor status --all shows the state of every worktree across all your repos, which is nice for a quick scan before you call it a day.

Where I use them most

PR reviews. arbor add the branch, review it in a separate editor window, remove it when done. My own work is untouched.

Long test suites. Tests running on one branch, coding on another. No waiting around.

Hotfixes. arbor add hotfix/critical, fix it, push it, remove it. Back to what I was doing.

Comparing behavior. Two worktrees open side by side, one on main and one on my feature branch. Way easier than switching back and forth.

Big migrations. Upgrading a framework to a new major version in a worktree while the rest of the team keeps shipping from main. No half-broken build blocking anyone.

Try it

Arbor is open source, written in Rust. Install with Homebrew:

brew install morellodev/tap/arbor

Or grab a binary from the releases page.

Run arbor init after installing to set up shell integration. Takes about 30 seconds.

If you’ve never tried worktrees, give them a shot. And if you have but gave up because of the directory mess, this might be worth another look.

The repo is at github.com/morellodev/arbor.