Git Worktrees: Work on Multiple Branches at Once
A worktree is a checked-out working directory attached to a repository. Every repository has one by
default — the directory containing your files. git worktree lets you add more, each with a different
branch checked out, all sharing a single object database.
The problem it solves is mundane and constant: you are halfway through something and need to look at a different branch. The traditional answers are stashing, committing work in progress, or cloning the repository again. Worktrees are usually better than all three.
The shape of it
Section titled “The shape of it”A single repository object database at the top, labelled .git with objects and refs. Three working trees branch from it below: the main working tree with the main branch checked out, a linked worktree with the feature branch checked out, and another linked worktree with the hotfix branch checked out. All three share the same object database.
Shared: the object database, refs, remotes, configuration, stashes and hooks. One copy of everything.
Per worktree: its own HEAD, its own index, its own working directory, and its own reflog for HEAD.
That split is what makes worktrees cheap. Adding one costs a checkout of the files, not a copy of the history.
Creating one
Section titled “Creating one”git worktree add ../project-feature feature/parserWhat it doesCreates a new directory at the given path, checks out the named branch into it, and registers it with the repository.
Why we run itIt gives you a second branch on disk without cloning the repository again or disturbing your current work.
Expected resultTwo lines: a preparation message and the commit that was checked out. Your current working tree is unchanged.
Preparing worktree (checking out 'feature/parser')HEAD is now at 49062b4 Initial commitYour original directory is untouched. ../project-feature now contains the branch’s files.
To create a new branch at the same time:
git worktree add -b hotfix/1.2.1 ../project-hotfixPreparing worktree (new branch 'hotfix/1.2.1')HEAD is now at 49062b4 Initial commitWithout a branch argument, git worktree add ../path creates a branch named after the directory.
Listing them
Section titled “Listing them”git worktree list/home/you/project 49062b4 [main]/home/you/project-feature 49062b4 [feature/parser]/home/you/project-hotfix 49062b4 [hotfix/1.2.1]For scripting, the machine-readable form:
git worktree list --porcelainworktree /home/you/projectHEAD 49062b4a90db9aa4bd5059752c7656090848cf33branch refs/heads/main
worktree /home/you/project-featureHEAD 49062b4a90db9aa4bd5059752c7656090848cf33branch refs/heads/feature/parserWhat is on disk
Section titled “What is on disk”The main working tree keeps its .git directory. A linked worktree gets a .git file instead:
cat ../project-feature/.gitgitdir: /home/you/project/.git/worktrees/project-featureThat pointer directs Git to per-worktree state stored inside the original repository:
Directoryproject/
Directory.git/
Directoryobjects/ shared by every worktree
- …
Directoryrefs/ shared
- …
- config shared
Directoryworktrees/
Directoryproject-feature/
- HEAD this worktree’s HEAD
- index this worktree’s index
- commondir points back to the shared .git
Directorylogs/
- …
Notice there is no objects directory under worktrees/project-feature. That is the whole efficiency
argument: one object database, several checkouts.
Git Repository Structure covers the
.git-as-a-file case.
One branch, one worktree
Section titled “One branch, one worktree”Git refuses to check out the same branch in two worktrees:
git worktree add ../dup feature/parserPreparing worktree (checking out 'feature/parser')fatal: 'feature/parser' is already used by worktree at '/home/you/project-feature'This is a safety rule, not a limitation. Two working trees on one branch would let you commit from one while the other’s index still reflects the old state.
If you genuinely want the same commit in two places — to build two configurations from identical code, for example — check it out detached:
git worktree add --detach ../build-test feature/parserHEAD is now at 49062b4 Initial commitA detached worktree has no branch, so the rule does not apply. You cannot commit to a branch from it, which is usually exactly what you want for a build or test checkout.
Removing them
Section titled “Removing them”git worktree remove ../project-featureWhat it doesDeletes the worktree's directory and removes its administrative entry from the repository.
Why we run itIt is the correct way to clean up. Deleting the directory manually leaves stale metadata behind.
Expected resultNo output on success. The branch that was checked out there is not deleted.
Git refuses if there is uncommitted work:
fatal: '../project-feature' contains modified or untracked files, use --force to delete itThat refusal is the same protection as elsewhere: Git will not silently discard changes it has no copy of.
Commit or stash first, or use --force deliberately.
Removing a worktree does not delete its branch. Delete that separately if you are finished with it:
git branch -d feature/parserPruning stale metadata
Section titled “Pruning stale metadata”If a worktree’s directory is deleted by hand, the registration remains:
rm -rf ../project-hotfixgit worktree list/home/you/project 49062b4 [main]/home/you/project-hotfix 49062b4 [hotfix/1.2.1] prunableprunable flags it. Clean up with:
git worktree prune -vRemoving worktrees/project-hotfix: gitdir file points to non-existent locationGit also prunes automatically during maintenance, so stale entries are self-correcting over time — but
prune is there when you want it immediate.
Moving and locking
Section titled “Moving and locking”Moving relocates a worktree and updates its registration:
git worktree move ../project-hotfix ../hotfix-1.2.1Moving the directory with mv instead breaks the link, requiring git worktree repair to fix.
Locking marks a worktree as not prunable, which matters when it lives somewhere that may be temporarily unavailable — a removable drive or a network mount:
git worktree lock ../project-feature --reason "on external disk"A locked worktree resists removal:
fatal: cannot remove a locked working tree, lock reason: demouse 'remove -f -f' to override or unlock firstgit worktree unlock ../project-featuregit worktree remove ../project-featurePractical workflows
Section titled “Practical workflows”Hotfix while a feature is half-finished
Section titled “Hotfix while a feature is half-finished”The canonical case. Production breaks; your working tree has a partly-done refactor you do not want to stash.
git worktree add -b hotfix/1.2.1 ../project-hotfix maincd ../project-hotfix# fix, test, commit, pushcd ../projectgit worktree remove ../project-hotfixYour feature work never moved. No stash, no commit-in-progress, no context lost.
Reviewing a colleague’s branch
Section titled “Reviewing a colleague’s branch”git fetch origingit worktree add ../review origin/feature/their-workcd ../review# build it, run it, read itReviewing code you can actually execute is substantially better than reading a diff in a browser, and this costs one command.
Running a long build while continuing to work
Section titled “Running a long build while continuing to work”A test suite that takes twenty minutes blocks the directory it runs in. Give it its own worktree and carry on in yours.
Comparing two versions side by side
Section titled “Comparing two versions side by side”git worktree add --detach ../v1.0 v1.0.0git worktree add --detach ../v2.0 v2.0.0diff -r ../v1.0/src ../v2.0/srcTwo tagged releases, both on disk, comparable with ordinary tools.
Automated tools working in parallel
Section titled “Automated tools working in parallel”Increasingly, tooling operates on branches alongside people — code generators, migration scripts, automated refactoring, AI coding assistants. Giving each its own worktree keeps it out of your working directory while sharing one object database, so nothing is duplicated and every commit is immediately visible everywhere.
Worktrees and other Git features
Section titled “Worktrees and other Git features”Worktrees combine with the rest of this cluster in ways worth knowing.
Sparse checkout is per worktree. Each worktree has its own sparse-checkout configuration, so you can
have one worktree containing the whole tree and another containing only services/api. On a monorepo this
is a genuinely powerful combination — a focused worktree per service, all sharing one object database.
git worktree add ../api-only maincd ../api-onlygit sparse-checkout set services/apiPartial and shallow clones carry over. A worktree in a blobless clone is itself blobless; objects are fetched on demand as usual. The filter belongs to the repository, not the working tree.
Stashes are shared. git stash in one worktree creates an entry visible from all of them. That is
occasionally convenient and frequently surprising — a stash you made in one context appears in
git stash list in another.
The reflog is partly per worktree. Each has its own HEAD reflog, because each has its own HEAD.
Branch reflogs are shared, since branches are shared.
git maintenance covers all of them. Maintenance operates on the shared object database, so running it
once benefits every worktree.
Worktrees versus cloning again
Section titled “Worktrees versus cloning again”| Extra clone | Worktree | |
|---|---|---|
| Disk usage | Full copy of all objects | Working files only |
| Setup time | Full clone | A checkout |
| Object database | Separate | Shared |
| Commits visible in the other | Only after push and fetch | Immediately |
| Branches, tags, stashes | Separate | Shared |
| Configuration | Separate | Shared |
| Hooks | Separate | Shared |
| Same branch in both | Allowed | Refused |
| Independent remotes | Yes | No |
Worktrees win for nearly every everyday case. A second clone is genuinely better only when you want isolation — a different remote, a different configuration, or a repository state you can destroy without consequence.
Multiple Branches Without Multiple Clones works through the decision from the problem end.
Limitations and gotchas
Section titled “Limitations and gotchas”One branch, one worktree. Use --detach when you need the same commit twice.
Submodules need care. A worktree does not automatically populate submodules; run
git submodule update --init in each worktree that needs them.
Hooks are shared. All worktrees use the same hooks, so a hook that assumes it is running in the main
working tree may behave oddly. Use git rev-parse --git-common-dir rather than --git-dir in hooks that
need the shared repository.
Configuration is shared unless you enable per-worktree config:
git config extensions.worktreeConfig truegit config --worktree user.email "other@example.com"This is off by default and rarely needed.
Tooling may be surprised. Some editors and IDEs assume .git is a directory. Most handle worktrees
fine now; occasionally something does not.
Deleting the directory by hand leaves metadata. Use git worktree remove, or prune afterwards.
Ignored files are not copied. A new worktree has no node_modules, no .env, no build output. Each
one needs its own setup — which is a real cost for projects with heavy install steps.
Troubleshooting
Section titled “Troubleshooting”fatal: '<branch>' is already used by worktree at …
The branch is checked out elsewhere. Either work in that worktree, or add this one detached:
git worktree add --detach ../inspect <branch>fatal: '<path>' already exists
The target directory is not empty. Choose another path, or remove the existing directory if it is a stale
worktree — then git worktree prune.
A worktree shows as prunable
Its directory was deleted by hand. git worktree prune -v removes the registration.
The worktree directory was moved with mv
The registration now points at the old location. Repair it:
git worktree repair /new/pathRun it from the main working tree, or run git worktree repair inside the moved worktree — it fixes the
links in both directions.
fatal: not a git repository inside a worktree
Usually the main repository was moved or deleted. A linked worktree is useless without it; the .git file
points at a path that no longer exists.
A hook behaves oddly in a worktree
It is probably using git rev-parse --git-dir, which in a linked worktree returns that worktree’s private
directory rather than the shared one. Use --git-common-dir for anything shared.
Tooling does not recognise the worktree
Some tools assume .git is a directory. Most handle the file form now; if one does not, that is a
reason to use a second clone for that particular task.
Common mistakes
Section titled “Common mistakes”Deleting the directory with rm -rf. Use git worktree remove, or prune afterwards.
Expecting the branch to be deleted with the worktree. Removing a worktree removes a checkout, not a branch.
Trying to check out the same branch twice. Use --detach.
Assuming worktrees are isolated. They share objects, refs, config, hooks and stashes. A commit in one is instantly visible in all.
Forgetting per-worktree setup. Dependencies and environment files are not copied.
Using --git-dir in a hook. In a linked worktree that points at the worktree’s private directory, not
the shared one. Use --git-common-dir.
Cloning again out of habit. The reflex is understandable and usually the more expensive option.
Bare repositories and worktrees
Section titled “Bare repositories and worktrees”A pattern worth knowing for anyone who lives in worktrees: clone bare, and treat every branch as a worktree.
git clone --bare git@github.com:example/project.git project/.barecd projectecho "gitdir: ./.bare" > .gitgit config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'git fetch origingit worktree add maingit worktree add feature-x -b feature-x origin/mainThe result is a directory containing only worktrees, with the repository data in .bare:
project/├── .bare/ the repository├── .git a file pointing at .bare├── main/ worktree└── feature-x/ worktreeNothing is “the main working tree” — every branch is equal, and there is no privileged directory that must not be removed.
Mental Model
Section titled “Mental Model”A repository is a library; a worktree is a reading desk.
The library holds every book — every commit, every version. A desk is a place with one set of books open on it. You can have several desks around the same library, each with something different open, and anything anyone files goes into the one shared collection.
Cloning again builds a second library.
What You Learned
Section titled “What You Learned”- A worktree is an additional checked-out directory attached to one repository.
- Objects, refs, config, hooks and stashes are shared;
HEAD, index and working files are per worktree. git worktree addcreates one;-bcreates a branch at the same time;--detachavoids the one-branch rule.- A linked worktree’s
.gitis a file pointing into.git/worktrees/. removeis the correct cleanup;prunefixes metadata after a manual deletion.lockprotects a worktree on removable or network storage.- Worktrees beat a second clone for almost every everyday case, because commits are shared instantly.
- Ignored files and dependencies are not copied into a new worktree.
Try It Yourself
Section titled “Try It Yourself”-
Set up a repository with two branches.
Terminal window mkdir ~/worktree-lab && cd ~/worktree-lab && git initecho v1 > app.txt && git add . && git commit -m "Initial commit"git branch feature-x -
Add a worktree and confirm your original directory is untouched:
Terminal window git worktree add ../worktree-lab-feature feature-xgit status -
List them:
git worktree list. -
Look at the
.gitfile:cat ../worktree-lab-feature/.git. Is it a file or a directory? -
Commit in the new worktree, then — without pushing or fetching — check that the commit is visible from the original:
Terminal window cd ../worktree-lab-feature && echo v2 >> app.txt && git commit -am "Feature work"cd ../worktree-lab && git log --oneline feature-x -
Try to check out the same branch twice. Predict the result first:
Terminal window git worktree add ../dup feature-x -
Now do it detached:
git worktree add --detach ../dup feature-x. Why does this work? -
Break it deliberately:
rm -rf ../dup, thengit worktree list. What does the entry say? -
Fix it:
git worktree prune -v. -
Clean up:
git worktree remove ../worktree-lab-feature.
Step 5 is the one that shows why worktrees beat a second clone: no push, no fetch, the commit is simply there.
Next Lesson
Section titled “Next Lesson”The next lesson approaches the same feature from the problem end — you need two branches at once, and here are your options.