Multiple Branches Without Multiple Clones
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.
Four approaches
Section titled “Four approaches”| Approach | Cost | When it fits |
|---|---|---|
| Commit work in progress | A junk commit to clean up later | You were nearly done anyway |
| Stash | Fast, but context is lost | Interruption lasting minutes |
| Clone again | Disk, time, divergent state | You need genuine isolation |
| Worktree | A checkout of the files | Almost everything else |
Commit the work in progress
Section titled “Commit the work in progress”git commit -am "wip"git switch mainHonest 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.
git stash push -m "parser refactor in progress"git switch main# … deal with the interruption …git switch feature/parsergit stash popFaster 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 popdoes not drop the stash entry, which surprises people. - Untracked files are excluded by default. Use
-uto include them, or a new file you had not yet added will not be stashed.
Stashing is right when the interruption is genuinely brief.
Clone the repository again
Section titled “Clone the repository again”git clone git@github.com:example/project.git ../project-2It 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.
Worktree
Section titled “Worktree”git worktree add ../project-main mainWhat 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 commitYour unfinished work stays exactly as it was, untouched, in the original directory. The second branch is a
cd away.
The worktree solution, step by step
Section titled “The worktree solution, step by step”The production-hotfix scenario, end to end.
-
Do not touch your current work. No stash, no commit. Leave it.
-
Create a worktree for the fix, branching from current
main:Terminal window git fetch origingit worktree add -b hotfix/1.2.1 ../project-hotfix origin/mainPreparing worktree (new branch 'hotfix/1.2.1')HEAD is now at ff3c99c Update deployment config -
Move into it and work normally:
Terminal window cd ../project-hotfix# edit, test, commitgit commit -am "Fix crash when config file is empty"git push -u origin hotfix/1.2.1 -
Return to your feature work. It is exactly where you left it:
Terminal window cd ../projectgit status -
Clean up once the fix has merged:
Terminal window git worktree remove ../project-hotfixgit 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.
Where the disk savings come from
Section titled “Where the disk savings come from”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.
cat ../project-feature/.gitgitdir: /home/you/project/.git/worktrees/project-featureInside 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.
What you still have to set up
Section titled “What you still have to set up”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.
.envand 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:
# Symlink shared, expensive artefactsln -s ~/project/node_modules ../project-feature/node_modules
# Or script the setupcd ../project-feature && cp ../project/.env . && npm ciA worktree layout that scales
Section titled “A worktree layout that scales”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:
mkdir ~/code/project-new && mv ~/code/project ~/code/project-new/mainmv ~/code/project-new ~/code/projectcd ~/code/project/maingit worktree add ../feature-parser feature/parserEverything for one project lives under one directory, and git worktree list reads cleanly.
What about git stash on the other branch?
Section titled “What about git stash on the other branch?”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:
git stash push -m "parser refactor, feature/parser branch"Choosing
Section titled “Choosing”| Situation | Use |
|---|---|
| Two-minute interruption | Stash |
| Nearly finished anyway | Commit, amend later |
| Urgent fix while work is half-done | Worktree |
| Reviewing a colleague’s branch | Worktree |
| Running a long build while working | Worktree |
| Comparing two releases | Worktree, detached |
| Need a different remote or config | Second clone |
| Want a repository to experiment destructively in | Second clone |
| Automated tooling working on a branch | Worktree |
The default should be a worktree. Reach for a second clone only when you specifically want isolation.
Common mistakes
Section titled “Common mistakes”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.
Mental Model
Section titled “Mental Model”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.
What You Learned
Section titled “What You Learned”- 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,
HEADand 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.
Try It Yourself
Section titled “Try It Yourself”Simulate the interruption and see that nothing is disturbed.
-
Create a repository and start some work you do not want to lose:
Terminal window mkdir ~/wt-problem && cd ~/wt-problem && git initecho v1 > app.txt && git add . && git commit -m "Initial commit"git switch -c feature/big-refactorecho "half-finished refactor" >> app.txtecho "scratch notes" > notes.txtNote that
app.txtis modified andnotes.txtis untracked — deliberately messy. -
Confirm the mess:
git status --short. -
The interruption arrives. Without stashing or committing:
Terminal window git worktree add -b hotfix/urgent ../wt-problem-hotfix main -
Fix it in the new worktree:
Terminal window cd ../wt-problem-hotfixecho "urgent fix" >> app.txtgit commit -am "Fix the urgent thing" -
Go back and check nothing moved:
Terminal window cd ../wt-problemgit status --shortPredict this output before running it.
-
Confirm the hotfix commit is already visible, with no fetch:
Terminal window git log --oneline hotfix/urgent -
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.
Next Lesson
Section titled “Next Lesson”Worktrees give you more working trees. The next three lessons are about making each one smaller — starting with controlling which paths appear at all.