Git Worktrees & Parallel Development Jump to heading
The interruption is familiar: you are halfway through a change, something urgent arrives, and the options are all bad. Stash the work and hope it applies cleanly later; commit something half-finished with a message you will have to rewrite; or clone the repository again and wait several minutes to duplicate objects you already have on disk. A worktree is the fourth option β a second checked-out directory backed by the same object store, created in a second. This part of Git Workflow Architecture & Branching Strategies covers how they work, where they surprise people, and the housekeeping that keeps them useful.
Prerequisites Jump to heading
What a Worktree Shares and What It Does Not Jump to heading
A worktree is a checked-out directory linked to the repositoryβs object database. Commits, branches, tags and the reflog are shared instantly β a commit made in one worktree is immediately visible from the other. What is not shared is everything outside version control: build output, dependency directories, editor state, and any file matched by .gitignore.
The shared object store is what makes this cheap: creating a worktree writes a few files and checks out a tree, rather than transferring a decade of history a second time.
Step 1 β Create One and See the Cost Jump to heading
# A worktree for an existing branch
git worktree add ../app-review origin/feature/refund-window
# A worktree with a new branch created at the same time
git worktree add -b fix/urgent-timeout ../app-hotfix origin/main # Verification: both are listed, and the disk cost is the working files only
git worktree list
du -sh .git ../app-hotfix The second command is the interruption-handling case: a new branch from the current default branch, in its own directory, without touching the work in progress you already have checked out.
Step 2 β Understand the One-Branch-Per-Worktree Rule Jump to heading
Git refuses to check out the same branch in two worktrees, because two working copies advancing one ref would produce results nobody wants.
git worktree add ../another main
# fatal: 'main' is already checked out at '/home/dev/app' # Inspect a branch without checking it out
git worktree add --detach ../app-inspect origin/main # Verification: a detached worktree holds no branch and blocks nothing
git -C ../app-inspect status --short --branch | head -1 Detached worktrees are the right tool for anything read-only: running a build against a specific commit, comparing behaviour between two revisions, or bisecting β the technique used in automating git bisect with a test script.
Step 3 β Handle Per-Worktree Configuration Jump to heading
Most configuration is shared, which is usually what you want and occasionally exactly wrong β a worktree used for a client project may need a different committer email.
# Enable per-worktree configuration, then set a value in one worktree only
git config extensions.worktreeConfig true
git config --worktree user.email '[email protected]' # Verification: the two worktrees now report different values
git config --get user.email
git -C ../app-client config --get user.email The conditional-include mechanism described in shipping a team gitconfig with includeIf is usually a better fit when the distinction is by directory rather than by worktree, and it survives the worktree being recreated.
Step 4 β Deal With What Is Not Version Controlled Jump to heading
A fresh worktree has no dependency directory, no build cache and no local environment file. The build fails, and the failure is confusing because the source is identical.
# Symlink an expensive shared directory rather than reinstalling it
ln -s ../app/node_modules ../app-review/node_modules # only if versions match
# Or install properly, which is correct but slower
(cd ../app-review && npm ci) # Copy local environment files that are ignored by design
cp .env.local ../app-review/.env.local 2>/dev/null || true SAFETY WARNING β symlinking a dependency directory between worktrees is safe only while both branches resolve to the same dependency versions. The moment one branch changes the lockfile, the shared directory serves the wrong tree to one of them, and the resulting failures point at application code rather than at the symlink. Prefer a real install for anything long-lived, and reserve the symlink for short-lived review worktrees.
Step 5 β Remove Them Deliberately Jump to heading
Worktrees accumulate. Deleting the directory without telling Git leaves a stale administrative entry, and the branch stays locked to a directory that no longer exists.
# The correct removal
git worktree remove ../app-review
# After deleting a directory by hand, clean up the bookkeeping
git worktree prune --verbose # Verification: no stale entries remain
git worktree list --porcelain | grep -c '^worktree' A worktree containing uncommitted changes will refuse to be removed, which is the behaviour you want. Forcing it discards the work permanently β the recovery options in recovering lost commits with git reflog do not help with files that were never committed.
Operating Worktrees Across a Team Jump to heading
Worktrees are a local technique, so there is nothing to roll out β but a few shared conventions prevent the common frustrations. The first is naming. A directory called ../app2 tells nobody anything a week later; ../app-PAY-812 or ../app-review does, and it makes the cleanup decision obvious. Teams that adopt worktrees seriously usually settle on a sibling directory pattern β the main clone in ~/work/app, everything else in ~/work/app-<purpose> β so that shell completion, editor workspaces and build scripts can all assume the layout.
The second is build output. Build systems that write into a fixed path relative to the repository root are fine; those that write to an absolute path, or that cache by project name rather than by path, will have two worktrees fighting over one directory. The symptom is a build that succeeds in one worktree and then mysteriously rebuilds everything in the other. Check this before recommending worktrees to the team, because it is the single most common reason people try them once and go back to cloning.
The third is editor and tooling state. Language servers, test runners and container setups frequently keep per-project state keyed on the directory, which works correctly, and occasionally keyed on the repository, which does not. Where a tool misbehaves, a second clone is a legitimate fallback β the comparison is laid out in worktrees vs multiple clones.
Finally, agree on a cleanup habit. A git worktree list that returns eleven entries, six of which point at branches merged months ago, is the state every team reaches without one. A weekly prune, or a shell alias that lists worktrees whose branches are already merged, keeps the list short enough to be useful. The mechanics of deciding what is safe to remove are the same as for remote branches, and are covered in cleaning up stale worktrees safely.
Configuration Reference Jump to heading
| Command or setting | Effect | When to use |
|---|---|---|
git worktree add <path> <ref> | Creates a linked working copy | The everyday case |
git worktree add -b <branch> <path> | Creates a branch and a worktree together | Handling an interruption |
git worktree add --detach <path> <ref> | Checks out without claiming a branch | Read-only inspection, bisecting |
git worktree list --porcelain | Machine-readable inventory | Scripts and cleanup checks |
git worktree remove <path> | Removes directory and bookkeeping | Always prefer this to rm -rf |
git worktree prune | Clears entries for deleted directories | After a manual deletion |
extensions.worktreeConfig | Enables per-worktree config values | Different identity per worktree |
git worktree lock | Prevents pruning of a removable volume | Worktrees on external media |
Troubleshooting Jump to heading
| Symptom | Likely cause | Fix |
|---|---|---|
already checked out | The branch is claimed by another worktree | Use --detach, or work on a different branch |
| Worktree directory gone but branch still locked | Directory deleted without remove | git worktree prune |
| Build fails only in the new worktree | Dependencies or ignored config are missing | Install dependencies; copy local env files |
| Two worktrees rebuilding each otherβs cache | Build output keyed on project name, not path | Set an explicit per-worktree output directory |
remove refuses to run | Uncommitted changes present | Commit, stash, or review before forcing |
| Worktree on a removable disk pruned itself | Git could not see the path | git worktree lock while it is detached |
Frequently Asked Questions Jump to heading
Is a worktree just a cheaper clone? Jump to heading
It is cheaper in the way that matters β disk and time β but it is not isolated. The branches and the object store are shared, so a rewrite performed in one worktree is immediately visible in the others. That sharing is the feature; it is also the reason a genuinely independent experiment sometimes wants a real clone.
Can I have a worktree on a different filesystem? Jump to heading
Yes, and it works well, but a worktree on removable media will be pruned if Git cannot see the path when it runs housekeeping. git worktree lock prevents that, and unlock restores normal behaviour when the volume is back.
Do worktrees help with large repositories? Jump to heading
Substantially, because the expensive part of a second copy is the object transfer and worktrees skip it entirely. Combined with the sparse checkout technique in sparse-checkout for large monorepos, a worktree over a large repository can be both quick to create and small on disk.
What happens to a worktree when I rewrite history? Jump to heading
The rewrite applies to the shared refs, so the other worktreeβs HEAD may end up pointing at a commit that is no longer on its branch. Git will tell you the branch has diverged rather than silently losing anything, but it is a good reason to avoid rewriting a branch that another worktree has checked out.
Do worktrees work well with containerised development? Jump to heading
They work, with one adjustment: container mounts are per directory, so each worktree needs its own container or its own mount configuration. Development setups that assume a single project directory β a fixed volume path, a container name derived from the project rather than the path β will have two worktrees fighting over one container, and the symptom is a build that succeeds once and then behaves as though files are missing.
The fix is to derive container names and volume paths from the worktree directory rather than from the repository name, which most tooling supports through an environment variable. Where it does not, running the second worktreeβs container with an explicit project name is usually a one-line override. It is worth testing this before adopting worktrees on a containerised project, because discovering it midway through an urgent fix is the situation the technique was supposed to prevent.
Related Jump to heading
- Reviewing a Pull Request in a Second Worktree β reading someoneβs branch without disturbing your own.
- Running Long Builds in a Separate Worktree β keeping a slow build off the directory you are editing.
- Cleaning Up Stale Worktrees Safely β pruning without discarding uncommitted work.
- Worktrees vs Multiple Clones β when a second clone is genuinely the better answer.
- Scripting a Worktree-per-Ticket Workflow β turning the whole cycle into two commands.