Git Worktrees: The Feature That Waited a Decade for Its Moment
Git worktrees have been around since 2015. For most of that time, they were a curiosity. Now they're having a moment. This is the story of how they came to be and why they suddenly matter.
Git worktrees let you check out several branches of one repository in separate directories. Git added them in 2015; running coding agents in parallel gives developers another reason to use them.
An agent can edit its own checkout while you review a PR or work on another branch. To understand the benefits and limits of that setup, it helps to know what Git shares between worktrees.
Here is how worktrees developed, how they store files, and how to use them.
Part I: The Problem Before Worktrees
The Pain of Context Switching
Every developer knows this workflow:
Switching also disrupts your environment. Your editor reloads files. Your language server re-indexes. Your node_modules might need reinstalling. Your brain has to context-switch twice.
For years, developers worked around this with various hacks:
Multiple clones: Clone the repo twice. Work in one, switch in the other. But now you have two copies of the entire history, two sets of remotes to configure, and changes in one don't appear in the other without pushing and pulling.
git stash gymnastics: Stash everything, switch, work, switch back, pop. Works until you have multiple stashes and forget which is which. Or until you realize you stashed something you actually wanted to commit.
Just don't switch: Finish what you're doing before touching anything else. Works until your coworker needs an urgent review or production is on fire.
The git-new-workdir Script
Before official worktrees, git shipped a contributed script called git-new-workdir. It lived in contrib/workdir/git-new-workdir and did something clever but hacky: it created a new working directory and symlinked the .git internals.
Under the hood, it would:
- Create a new directory
- Symlink
.git/objects,.git/refs,.git/config, etc. - Create a separate
HEADandindexfor the new directory
It worked, mostly. But symlinks are fragile. The script wasn't officially supported. It didn't handle all edge cases. It was a hack, and everyone knew it.
The git community wanted something better.
Part II: Worktrees Land in Git 2.5
July 2015: The Feature Arrives
Git 2.5, released on July 29, 2015, introduced git worktree as a first-class feature. The primary author was Nguyễn Thái Ngọc Duy, who had been working on the concept for years.
The GitHub announcement explained the key change: each linked working tree gets its own checkout while referring back to the main repository for shared history.
The feature shipped with an experimental label and warnings about submodules. Submodule support remains incomplete in the Git worktree documentation; check those limits before using multiple checkouts of a superproject.
The initial commit message laid out the vision:
"A git repository can support multiple working trees, allowing you to check out more than one branch at a time."
The idea: a git repository is really two things:
- The object store: All your commits, trees, and blobs (the
.git/objectsdirectory) - The working state: What's currently checked out, staged, etc.
These don't have to live in the same place. Worktrees formalized this separation.
The Implementation
When you create a worktree, git does the following:
- Creates a new directory at the path you specified
- Creates a
.gitfile (not directory) in that location pointing back to the main repo - Creates an entry in
.git/worktrees/<name>/in the main repo - Checks out the branch in the new working directory
The new directory's .git is just a file containing:
And in the main repo, .git/worktrees/feature-branch/ contains:
This architecture means:
- Shared objects: Commits, trees, blobs exist once
- Shared refs: Branches and tags are visible from all worktrees
- Separate working state: Each worktree has its own HEAD, index, and working directory
The One Branch Rule
Git enforces a constraint: a branch can only be checked out in one worktree at a time. Try to check out the same branch twice and you'll get:
This prevents a class of confusing bugs. If two worktrees had the same branch and you committed in one, the other would have a dirty diff appear out of nowhere.
You can work around this with detached HEAD or by creating a new branch, but the protection is intentional.
Part III: The Anatomy of a Worktree
What Lives Where
Understanding the file layout helps demystify worktrees:
What's Shared, What's Not
| Shared Across Worktrees | Separate Per Worktree | |-------------------------|----------------------| | Object store (commits, blobs) | Working directory files | | Branch refs | HEAD (current branch) | | Tag refs | Index (staging area) | | Remote configuration | Untracked files | | Hooks | Local modifications | | Config (mostly) | MERGE_HEAD, REBASE_HEAD, etc. |
This means:
- Commits made in one worktree are immediately visible in others
- But the files on disk are completely independent
- You can have different uncommitted changes in each worktree
Garbage Collection and Pruning
Git tracks which worktrees are valid. If you delete a worktree directory without using git worktree remove, git doesn't immediately know. The metadata in .git/worktrees/ persists.
This checks each entry in .git/worktrees/ and removes any whose gitdir points to a non-existent directory.
Git also runs this automatically during garbage collection, so orphaned metadata doesn't accumulate forever.
Part IV: Why Worktrees Stayed Niche
For almost a decade, worktrees remained a power-user feature. Most developers had never heard of them. Why?
1. The Single-Branch Workflow Dominated
Most teams followed a simple pattern:
- Create feature branch
- Work until done
- Open PR
- Merge
- Delete branch
This is linear. You finish one thing before starting another. Worktrees solve parallel work, but most people weren't working in parallel.
2. Git GUIs Didn't Support Them
Popular git interfaces (GitKraken, SourceTree, GitHub Desktop, VS Code's git panel) either didn't support worktrees or treated them as second-class. If your tool doesn't expose a feature, you don't use it.
3. The Commands Were Obscure
Compare:
The worktree command is longer, requires a path argument, and the mental model (multiple working directories) is unfamiliar. Without a forcing function, people stick with what they know.
4. Disk Space Concerns
Each worktree is a full checkout of the repository. For large repos, this adds up. In the age of spinning disks and smaller SSDs, this mattered more than it does now.
Part V: Why Worktrees Are Having a Moment
The pattern that revived worktrees: a repository fanning out into a worktree per coding agent, each on its own branch with an independent diff
Several shifts have made worktrees suddenly relevant:
1. Code Review Culture
Modern development is interrupt-driven. You're working on feature A when:
- Someone requests a review on feature B
- You need to verify a bug exists on main
- A hotfix needs immediate attention
Each interruption previously meant stash/switch/pop. With worktrees, you open a different directory. Your feature A context is untouched.
2. CI/CD and Local Testing
Worktrees let you build and test branches independently. To check whether two PRs work together, you still need to combine their changes on a test branch and run the relevant checks.
3. Monorepo Workflows
Large monorepos make cloning expensive. If your repo is 10GB, cloning it twice for parallel work wastes 10GB. Worktrees share the object store, but each checkout still needs its own working files and may need dependencies and build artifacts.
4. AI Coding Agents
This is the big one.
When an AI agent modifies code, it needs somewhere to work. If it works in your directory, you have a problem:
- Your files change while you're editing
- You can't easily diff agent changes vs your changes
- Agent experiments pollute your working tree
Give the agent a separate checkout:
The agent can:
- Make changes freely
- Commit to its own branch
- Run builds and tests
- Iterate without changing files in your checkout
When it's done, you review the diff and merge. It's the same workflow as reviewing a coworker's PR, but the coworker is an AI and works in a worktree instead of a fork.
The checkouts are separate, but running processes may still share ports, databases, and other services. Worktrees do not isolate those resources.
Part VI: Worktrees Under the Hood
For those who want to understand the internals:
How git Finds the Repository
When you run a git command, git needs to find the .git directory. The search algorithm is:
- Look for
.gitin current directory - If
.gitis a file, read its contents forgitdir:path - Follow that path
- If that path contains a
commondirfile, the common git dir is elsewhere
This indirection is how worktrees work. The linked worktree has a .git file, not directory, and git follows the chain.
The commondir Mechanism
Each linked worktree's git directory (e.g., .git/worktrees/feature/) contains a commondir file pointing to the main .git directory.
When git needs something shared (objects, refs), it uses commondir. When it needs something worktree-specific (HEAD, index), it uses the worktree's directory.
The code that handles this is in setup.c and path.c in the git source. The function get_common_dir() resolves the path for shared resources.
Lock Files and Concurrent Access
Multiple worktrees can run git commands simultaneously. Git uses lock files to prevent corruption:
- Each worktree's
index.lock: Prevents concurrent changes to its staging area .git/refs/heads/<branch>.lock: Prevents concurrent ref updates.git/worktrees/<name>/locked: Marks a worktree as locked (won't be pruned)
The worktree-specific lock is useful when a worktree is on removable media or a network drive that might be temporarily unavailable.
Part VII: Getting the Most from Worktrees
If you're convinced worktrees are worth adopting, here's how to use them well:
Organize Your Worktrees
Keep worktrees in a predictable location:
Or keep them adjacent to the main repo:
Pick a convention and stick with it.
Name Worktrees After Branches
Reduces confusion:
Now the directory name matches the branch name.
Create a Helper Function
Add to your shell config:
Clean Up When Done
Worktrees are cheap to create and should be cheap to discard:
Don't let old worktrees accumulate.
Remember the Limitations
- Can't checkout the same branch in multiple worktrees
- Keep worktrees outside one another so their files are easy to distinguish
- Submodule support is incomplete; consult Git's documented limitations
- Some IDE features might not understand worktrees
Try a second checkout
The next time you need to review a PR while keeping unfinished work open, create a worktree for the review. Your current checkout stays in place, and both directories share the repository history. Remove the review worktree when you are done.
Related Posts
Keeping Terminal Sessions Alive Across Electron App Restarts
How we built a process-isolated terminal host that survives app restarts, handles backpressure gracefully, and enables cold restore from disk.

How to get hit (probably in the face but anywhere else works too)
I ran out of technical content to write for the blog so here is something I am interested in instead.
