Skip to content

Multiple Branches Without Multiple Clones

Lesson 2 of 11Intermediate8 min readModern Git Workflows · Modern Git ProductivityVerified: Git 2.43.0 on Ubuntu 24.04

You are mid-way through a change. Something else needs your attention on a different branch — a production bug, a review request, a comparison you want to run. Your working tree is not in a state you want to disturb.

Git checks out one branch per working directory. So the question is how to get a second working directory without the costs of a second repository.

ApproachCostWhen it fits
Commit work in progressA junk commit to clean up laterYou were nearly done anyway
StashFast, but context is lostInterruption lasting minutes
Clone againDisk, time, divergent stateYou need genuine isolation
WorktreeA checkout of the filesAlmost everything else
Terminal window
git commit -am "wip"
git switch main

Honest and simple. The costs are a commit you must remember to amend or drop, and a branch that now has a commit that does not build — which matters if CI runs on push, or if anyone else looks at the branch.

Fine for a two-minute detour. Not a habit.

Terminal window
git stash push -m "parser refactor in progress"
git switch main
# … deal with the interruption …
git switch feature/parser
git stash pop

Faster than committing and leaves no junk in history. The real costs are less obvious:

  • Your context is gone. Editor state, which files you had open, where you were.
  • Stashes are easy to forget. A stash from three weeks ago is nearly worthless because you no longer remember what it was for.
  • Popping can conflict, and a conflicted stash pop does not drop the stash entry, which surprises people.
  • Untracked files are excluded by default. Use -u to include them, or a new file you had not yet added will not be stashed.

Stashing is right when the interruption is genuinely brief.

Terminal window
git clone git@github.com:example/project.git ../project-2

It works, and for a small repository the cost is low. For a large one:

  • Disk. A full second copy of every object.
  • Time. A fresh clone, plus dependency installation and any project setup.
  • Divergence. Two independent repositories. A commit in one is invisible to the other until you push and fetch — through the network, even though both are on your machine.
  • Configuration drift. Separate local config, separate hooks, separate remotes.

There is one thing this buys that worktrees do not: genuine isolation. If you want a copy to experiment destructively in, or a different remote configured, a second clone is correct.

Terminal window
git worktree add ../project-main main

What it doesCreates a second working directory attached to the same repository, with the named branch checked out.

Why we run itIt gives you the second branch on disk without copying the object database or disturbing your current work.

Expected resultA preparation message and the checked-out commit. Your original directory is completely unaffected.

Preparing worktree (checking out 'main')
HEAD is now at 49062b4 Initial commit

Your unfinished work stays exactly as it was, untouched, in the original directory. The second branch is a cd away.

The production-hotfix scenario, end to end.

  1. Do not touch your current work. No stash, no commit. Leave it.

  2. Create a worktree for the fix, branching from current main:

    Terminal window
    git fetch origin
    git worktree add -b hotfix/1.2.1 ../project-hotfix origin/main
    Preparing worktree (new branch 'hotfix/1.2.1')
    HEAD is now at ff3c99c Update deployment config
  3. Move into it and work normally:

    Terminal window
    cd ../project-hotfix
    # edit, test, commit
    git commit -am "Fix crash when config file is empty"
    git push -u origin hotfix/1.2.1
  4. Return to your feature work. It is exactly where you left it:

    Terminal window
    cd ../project
    git status
  5. Clean up once the fix has merged:

    Terminal window
    git worktree remove ../project-hotfix
    git branch -d hotfix/1.2.1

At no point did your feature branch change, and at no point did you have to remember what you had been doing.

A repository has two parts: the object database in .git, and the checked-out files.

Cloning again copies both. A worktree copies only the second — the object database is shared through a pointer.

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

Inside that directory Git keeps only what must be per-worktree — HEAD, index, logs — and a commondir file pointing back to the shared repository. There is no objects directory.

The practical effect scales with history. On a repository whose .git is 2 GB and whose checkout is 200 MB, a second clone costs 2.2 GB and a worktree costs 200 MB.

A worktree is a fresh checkout, so anything not tracked by Git is absent:

  • Dependencies. No node_modules, no .venv, no vendored packages.
  • Environment files. .env and similar are usually ignored, so they do not appear.
  • Build output and caches. Empty.
  • Editor and IDE state. A new directory as far as your tools are concerned.

For a project with a five-second install this is nothing. For one with a fifteen-minute build, it is a real cost and worth planning for.

Two common mitigations:

Terminal window
# Symlink shared, expensive artefacts
ln -s ~/project/node_modules ../project-feature/node_modules
# Or script the setup
cd ../project-feature && cp ../project/.env . && npm ci

Once you use worktrees regularly, where you put them starts to matter. Two layouts work well.

Siblings, which is what the examples above use:

~/code/
├── project/ [main]
├── project-feature/ [feature/parser]
└── project-hotfix/ [hotfix/1.2.1]

Simple, and obvious from a file listing. It clutters the parent directory once you have several projects doing this.

A dedicated parent per project, which scales better:

~/code/project/
├── main/ ← the main working tree
├── feature-parser/
└── hotfix-1.2.1/

To set this up on an existing clone, move it down a level first:

Terminal window
mkdir ~/code/project-new && mv ~/code/project ~/code/project-new/main
mv ~/code/project-new ~/code/project
cd ~/code/project/main
git worktree add ../feature-parser feature/parser

Everything for one project lives under one directory, and git worktree list reads cleanly.

A question that comes up: if I have stashed work, can I pop it in a different worktree?

Yes — stashes are stored in the shared repository, so git stash list shows the same entries everywhere. That is occasionally useful: stash in one worktree, pop in another.

It is more often a source of confusion. A stash made against one branch may not apply cleanly to another, and there is nothing in git stash list indicating which worktree or branch it came from beyond the autogenerated message. If you use stashes across worktrees, name them:

Terminal window
git stash push -m "parser refactor, feature/parser branch"
SituationUse
Two-minute interruptionStash
Nearly finished anywayCommit, amend later
Urgent fix while work is half-doneWorktree
Reviewing a colleague’s branchWorktree
Running a long build while workingWorktree
Comparing two releasesWorktree, detached
Need a different remote or configSecond clone
Want a repository to experiment destructively inSecond clone
Automated tooling working on a branchWorktree

The default should be a worktree. Reach for a second clone only when you specifically want isolation.

Cloning again by reflex. The most common and most expensive answer.

Stashing for a long interruption. A stash from last month is a mystery.

Forgetting git stash pop conflicts leave the stash in place. Resolve, stage, then git stash drop.

Trying to check out the same branch in two worktrees. Git refuses. Use --detach.

Deleting a worktree directory with rm -rf. Use git worktree remove, or prune afterwards.

Assuming the worktree is isolated. Refs, config, hooks and stashes are shared. Only the checkout and HEAD are separate.

Not planning for setup cost. A worktree needs its own dependencies.

Switching branches is changing what is on your desk. A worktree is a second desk.

Stashing is sweeping the desk into a drawer — fast, and you may not remember what is in there. Cloning again is renting a second office, with its own copy of every file. A worktree is a second desk in the same room, drawing on the same filing cabinet.

  • Git checks out one branch per working directory; a worktree adds directories, not repositories.
  • Worktrees share the object database, refs, config and hooks; only the checkout, HEAD and index differ.
  • Commits made in one worktree are visible in all of them instantly, with no push or fetch.
  • Disk cost is the checked-out files only, which matters as history grows.
  • Stashing suits brief interruptions; its costs are lost context and forgotten entries.
  • A second clone is right only when you want genuine isolation.
  • New worktrees need their own dependencies and environment files.

Simulate the interruption and see that nothing is disturbed.

  1. Create a repository and start some work you do not want to lose:

    Terminal window
    mkdir ~/wt-problem && cd ~/wt-problem && git init
    echo v1 > app.txt && git add . && git commit -m "Initial commit"
    git switch -c feature/big-refactor
    echo "half-finished refactor" >> app.txt
    echo "scratch notes" > notes.txt

    Note that app.txt is modified and notes.txt is untracked — deliberately messy.

  2. Confirm the mess: git status --short.

  3. The interruption arrives. Without stashing or committing:

    Terminal window
    git worktree add -b hotfix/urgent ../wt-problem-hotfix main
  4. Fix it in the new worktree:

    Terminal window
    cd ../wt-problem-hotfix
    echo "urgent fix" >> app.txt
    git commit -am "Fix the urgent thing"
  5. Go back and check nothing moved:

    Terminal window
    cd ../wt-problem
    git status --short

    Predict this output before running it.

  6. Confirm the hotfix commit is already visible, with no fetch:

    Terminal window
    git log --oneline hotfix/urgent
  7. Clean up:

    Terminal window
    git worktree remove ../wt-problem-hotfix

Step 5 should show exactly what step 2 showed — the modified file and the untracked one, untouched. Step 6 is the shared-object-database benefit that a second clone cannot give you.

Worktrees give you more working trees. The next three lessons are about making each one smaller — starting with controlling which paths appear at all.