Skip to content

Git Worktrees: Work on Multiple Branches at Once

Lesson 1 of 11Intermediate10 min readModern Git Workflows · Modern Git ProductivityVerified: Git 2.43.0 on Ubuntu 24.04; every command and output in this lesson was run

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.

One repository, several working trees

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.

Repository (.git)objects · refs · config — sharedmain working tree~/project[main]linked worktree~/project-feature[feature/parser]linked worktree~/project-hotfix[hotfix/1.2.1]Commits made in any worktree are immediately visible in all of them.

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.

Terminal window
git worktree add ../project-feature feature/parser

What 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 commit

Your original directory is untouched. ../project-feature now contains the branch’s files.

To create a new branch at the same time:

Terminal window
git worktree add -b hotfix/1.2.1 ../project-hotfix
Preparing worktree (new branch 'hotfix/1.2.1')
HEAD is now at 49062b4 Initial commit

Without a branch argument, git worktree add ../path creates a branch named after the directory.

Terminal window
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:

Terminal window
git worktree list --porcelain
worktree /home/you/project
HEAD 49062b4a90db9aa4bd5059752c7656090848cf33
branch refs/heads/main
worktree /home/you/project-feature
HEAD 49062b4a90db9aa4bd5059752c7656090848cf33
branch refs/heads/feature/parser

The main working tree keeps its .git directory. A linked worktree gets a .git file instead:

Terminal window
cat ../project-feature/.git
gitdir: /home/you/project/.git/worktrees/project-feature

That 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.

Git refuses to check out the same branch in two worktrees:

Terminal window
git worktree add ../dup feature/parser
Preparing 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:

Terminal window
git worktree add --detach ../build-test feature/parser
HEAD is now at 49062b4 Initial commit

A 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.

Terminal window
git worktree remove ../project-feature

What 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 it

That 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:

Terminal window
git branch -d feature/parser

If a worktree’s directory is deleted by hand, the registration remains:

Terminal window
rm -rf ../project-hotfix
git worktree list
/home/you/project 49062b4 [main]
/home/you/project-hotfix 49062b4 [hotfix/1.2.1] prunable

prunable flags it. Clean up with:

Terminal window
git worktree prune -v
Removing worktrees/project-hotfix: gitdir file points to non-existent location

Git also prunes automatically during maintenance, so stale entries are self-correcting over time — but prune is there when you want it immediate.

Moving relocates a worktree and updates its registration:

Terminal window
git worktree move ../project-hotfix ../hotfix-1.2.1

Moving 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:

Terminal window
git worktree lock ../project-feature --reason "on external disk"

A locked worktree resists removal:

fatal: cannot remove a locked working tree, lock reason: demo
use 'remove -f -f' to override or unlock first
Terminal window
git worktree unlock ../project-feature
git worktree remove ../project-feature

The canonical case. Production breaks; your working tree has a partly-done refactor you do not want to stash.

Terminal window
git worktree add -b hotfix/1.2.1 ../project-hotfix main
cd ../project-hotfix
# fix, test, commit, push
cd ../project
git worktree remove ../project-hotfix

Your feature work never moved. No stash, no commit-in-progress, no context lost.

Terminal window
git fetch origin
git worktree add ../review origin/feature/their-work
cd ../review
# build it, run it, read it

Reviewing 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.

Terminal window
git worktree add --detach ../v1.0 v1.0.0
git worktree add --detach ../v2.0 v2.0.0
diff -r ../v1.0/src ../v2.0/src

Two tagged releases, both on disk, comparable with ordinary tools.

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 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.

Terminal window
git worktree add ../api-only main
cd ../api-only
git sparse-checkout set services/api

Partial 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.

Extra cloneWorktree
Disk usageFull copy of all objectsWorking files only
Setup timeFull cloneA checkout
Object databaseSeparateShared
Commits visible in the otherOnly after push and fetchImmediately
Branches, tags, stashesSeparateShared
ConfigurationSeparateShared
HooksSeparateShared
Same branch in bothAllowedRefused
Independent remotesYesNo

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.

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:

Terminal window
git config extensions.worktreeConfig true
git 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.

fatal: '<branch>' is already used by worktree at … The branch is checked out elsewhere. Either work in that worktree, or add this one detached:

Terminal window
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:

Terminal window
git worktree repair /new/path

Run 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.

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.

A pattern worth knowing for anyone who lives in worktrees: clone bare, and treat every branch as a worktree.

Terminal window
git clone --bare git@github.com:example/project.git project/.bare
cd project
echo "gitdir: ./.bare" > .git
git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
git fetch origin
git worktree add main
git worktree add feature-x -b feature-x origin/main

The 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/ worktree

Nothing is “the main working tree” — every branch is equal, and there is no privileged directory that must not be removed.

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.

  • 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 add creates one; -b creates a branch at the same time; --detach avoids the one-branch rule.
  • A linked worktree’s .git is a file pointing into .git/worktrees/.
  • remove is the correct cleanup; prune fixes metadata after a manual deletion.
  • lock protects 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.
  1. Set up a repository with two branches.

    Terminal window
    mkdir ~/worktree-lab && cd ~/worktree-lab && git init
    echo v1 > app.txt && git add . && git commit -m "Initial commit"
    git branch feature-x
  2. Add a worktree and confirm your original directory is untouched:

    Terminal window
    git worktree add ../worktree-lab-feature feature-x
    git status
  3. List them: git worktree list.

  4. Look at the .git file: cat ../worktree-lab-feature/.git. Is it a file or a directory?

  5. 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
  6. Try to check out the same branch twice. Predict the result first:

    Terminal window
    git worktree add ../dup feature-x
  7. Now do it detached: git worktree add --detach ../dup feature-x. Why does this work?

  8. Break it deliberately: rm -rf ../dup, then git worktree list. What does the entry say?

  9. Fix it: git worktree prune -v.

  10. 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.

The next lesson approaches the same feature from the problem end — you need two branches at once, and here are your options.