The scenario is familiar to anyone who has worked on a shared codebase for more than a fortnight. A developer is mid-refactor, with a dozen files touched, half of them staged, a couple of speculative commits on top, and a test suite partway through being rewritten. Then a colleague asks for a quick review on an unrelated branch, or a production alert fires and a hotfix needs to go out against main, or continuous integration finds a flake in a pull request that only reproduces locally.
The standard options are all unsatisfying. git stash promises to tuck the work aside, but it has a long history of awkward interactions with partial staging, untracked files, and merge conflicts on restore. Cloning the repository a second time into another directory avoids the stash problem, but it duplicates the object database, drifts out of sync with the original's remotes and hooks, and multiplies the per-directory tooling cost—another node_modules, another virtualenv, another Docker stack, another copy of the .env file. Asking the colleague to wait is the honest answer, and it is the answer that does not scale.
Git has had a first-class solution to this for more than seven years, and most developers still reach for the stash. The solution is git worktree, and it deserves a proper look.
What a worktree is
A worktree is a second working directory for the same Git repository. Instead of one folder containing the files for one branch, a developer can have several folders—one for main, one for a feature branch, one for a colleague's pull request—each sitting beside the others on disk. Every one of those folders is a full working copy, and all of them share a single history underneath.
The simplest way to see it is to run it. From inside an existing repository:
cd my-project
git worktree add ../pr-review origin/pr-1234
cd ../pr-review
After those three commands, my-project/ still has the work-in-progress exactly as it was left—unstaged edits, half-typed commit messages, the lot—and ../pr-review/ has a clean checkout of origin/pr-1234. The developer can ls, npm install, run tests, open an editor, make commits—all in the new directory—without touching anything in the original. The GitHub announcement post at the time described a linked worktree as a pseudo-repository with its own checked-out working copy1, which is the right mental picture: it walks and quacks like a repository of its own, even though the heavy machinery is shared.
Both directories resolve git log, git diff, and git branch against the same repository. Commits made in one worktree are visible to the other as soon as they land; pushing from either worktree reaches the same remote. The only thing that differs between them is the files currently checked out on disk. A second worktree adds the new working-copy files to disk but reuses the repository's existing history, which is normally the larger piece.
This is where worktrees earn their keep: switching branches no longer means git checkout. It means cd to a different directory. That is the whole trick. Instead of reshuffling the files in a single folder every time the developer changes branches, Git lays out each branch as its own folder. The administrative bookkeeping for each linked worktree lives under $GIT_DIR/worktrees/<name>/2, and the .git entry in a linked worktree is a text file pointing at the real repository directory—a mechanism the Git docs call the gitfile3—so every tool that understands Git still sees a proper repository when it looks.
One restriction trips people up. A branch checked out in one worktree cannot be checked out in a second one. If a feature branch is live in ../feat-payments/, adding it again fails:
$ git worktree add ../another feat-payments
fatal: 'feat-payments' is already checked out at '/Users/dev/my-project/feat-payments'
This exists because otherwise two folders would race to own the same branch's "current state". The workaround is either a separate branch or a detached-HEAD worktree pointing at a specific commit.
The shared machinery is worth a line each. Hooks live in the shared repository, so a pre-commit linter installed once applies to every worktree. Remotes are shared, so git fetch in one worktree updates all of them. Tags and branch refs are shared. Stashes are shared too: git stash writes to refs/stash, so a stash created in one worktree is visible from the others.
Worktrees have been in the stock git binary since Git 2.5, released on 27 July 20154. The feature emerged from the Git mailing list, primarily from Nguyễn Thái Ngọc Duy with Eric Sunshine shaping the user-facing git worktree add command56. Later releases rounded out the edges: list arrived in 2.77, remove and move in 2.178, and shell completion in 2.269. It has been a quiet feature—no banner on the Git homepage, no prompt from git status suggesting the alternative—which partly explains why so many developers still reach for git stash without knowing there is a better-shaped tool.
The three canonical workflows
Three patterns justify adopting worktrees on their own; anything beyond these is a bonus.
The first is the emergency hotfix during a refactor. The developer is halfway through reshaping a module, with staged and unstaged changes scattered across a dozen files, when a production alert fires against main. The refactor stays untouched in the main working tree. A hotfix worktree is created against the canonical branch:
git worktree add ../hotfix origin/main
cd ../hotfix
# fix the bug, commit, push, open PR
Once the fix is merged, the worktree is removed and the refactor resumes without a stash pop. Nothing about the in-progress work has been touched. Seth Kenlon's walk-through of the pattern runs through the same shape—git worktree add against the canonical branch, commit and push the fix, then retire the worktree once it has served its purpose—and also shows the git worktree move subcommand for cases where the hotfix tree ends up in the wrong place10.
The second pattern is reviewing a colleague's pull request while the reviewer's own feature branch stays live. The developer has a working tree full of in-progress changes and needs to build, run, and poke at somebody else's code without disturbing their own. A separate worktree is the clean answer:
git fetch origin pull/1234/head:review/1234
git worktree add ../pr-review review/1234
cd ../pr-review
# build, run tests, exercise the change
The reviewer leaves notes, the worktree is removed, and the original feature branch is exactly where it was left. The secondary benefit is social: a review that actually runs the code turns up a different class of problems from one that reads the diff only, and the friction cost of running the code is the difference between reviewing carefully and reviewing politely. Phelipe Teles' writeup of the workflow makes the case plainly: a separate worktree means the reviewer can build, run, and poke at the PR branch without blowing away the state of their current work11.
The third pattern is running a long build or test suite in one worktree while editing continues in another. A single test run can lock up a terminal for ten or twenty minutes; with two worktrees, the suite runs in one, and the editor keeps typing in the other. This pairs naturally with a terminal multiplexer such as tmux or screen: one window per worktree keeps the mental model obvious, with tmux new-window -c ../pr-review opening a fresh pane already cd'd into the right directory. The multiplexer is convenience, not requirement. The same shape applies to any slow task that the developer wants to run against a known-good state while experimenting on top: a full production build, a migration rehearsal, a profiler run whose output is compared against an edited version. The value is not parallelism in the CPU sense; it is that the slow task stops blocking the fast one. Brian Vanderwal's early practitioner piece makes exactly this case for using worktrees to parallelise development12.
Worktrees reveal their value quietly. The first pull-request review in a fresh checkout, the first uninterrupted long test run, the first hotfix that slips in without disturbing a week of half-built refactor—each one makes the old stash-and-switch ceremony feel slightly absurd after the fact.
The bare-repo pattern
Ad-hoc worktrees sprayed across the filesystem work fine for a week. For a repository a developer lives in, a more deliberate layout pays off. The convention that has emerged, documented in several 2021 practitioner posts, puts the administrative bits in a hidden .bare/ directory and turns every branch, including main, into a sibling worktree13.
The layout looks like this:
my-project/
.bare/ # bare clone: objects, refs, config, hooks
.git # text file: gitdir: ./.bare
main/ # worktree tracking origin/main
feat-payments/ # worktree for the feature branch
hotfix-auth/ # worktree for the hotfix
The big conceptual win for most developers is that every branch becomes a folder you can cd into, which matches the file-and-folder model every developer already uses for everything else. There is no mental translation between "branch" and "thing on disk"—they are the same thing.
The setup is three commands:
git clone --bare git@github.com:example/repo.git my-project/.bare
cd my-project
echo "gitdir: ./.bare" > .git
git worktree add main main
The .git file ensures that tools run from my-project/ itself still resolve a repository, which matters for IDE launchers and for the occasional git log typed at the top of the tree. From there, every branch becomes a real directory:
git worktree add feat-payments -b feat-payments origin/main
git worktree add hotfix-auth origin/main
Morgan Cugerone's walkthrough of the same pattern emphasises the hygiene benefit: cleanup is straightforward, because no single worktree holds the authoritative .git directory14. Removing main/ does not wreck the repository, because main/ is just another checkout. The administrative state lives in .bare/, and it survives the coming and going of individual branches.
A subtler benefit is that tools which scan upward for a .git directory behave predictably. Editors, language servers, and CI helpers routinely walk the directory tree looking for a repository root; the .git gitfile at my-project/ gives them an answer even when no branch is currently checked out at that level. Git's repository-layout documentation describes this gitfile form as a plain text pointer to the real repository directory3, which is exactly the small piece of indirection the .bare/ pattern relies on.
Cleanup hygiene
Day-to-day use is easy. git worktree remove <path> does the right thing for a clean linked worktree: it unregisters the worktree and deletes the directory in one step. Dirty worktrees, and worktrees containing submodules, require --force, so removal still deserves a quick git status rather than blind muscle memory2. The trouble comes when a worktree disappears outside Git's awareness: a quick rm -rf, a removable drive unmounted, a directory renamed by a file manager. In those cases the administrative record under $GIT_DIR/worktrees/<name>/ stays behind, and Git needs a small nudge to clean up.
git worktree list enumerates the current state:
$ git worktree list
/Users/dev/my-project/main abc1234 [main]
/Users/dev/my-project/feat-x def5678 [feat-x]
/Users/dev/my-project/hotfix-auth 0987abc [hotfix-auth]
For worktrees that were lost to an rm -rf or a disk that went missing, git worktree prune sweeps up the stale metadata. After a manual deletion, git worktree list flags the orphan with a (prunable) marker:
$ git worktree list
/Users/dev/my-project/main abc1234 [main]
/Users/dev/my-project/hotfix-auth 0987abc (prunable)
The blog post that catalogued this gotcha is explicit about the failure mode: manual directory removal without git worktree remove leaves the .git/worktrees/<name>/ registration behind, and Git treats the missing path as a prunable relic rather than a valid linked checkout15. Setting gc.worktreePruneExpire in config—whose default value of three months is documented in git-gc(1)—lets garbage collection reclaim the stale entries automatically16.
Two other subcommands round out the toolkit. git worktree lock marks a worktree as immune to prune, which is useful for checkouts on removable media or network drives that come and go. git worktree move relocates a worktree without breaking its registration. git worktree repair rewrites the bidirectional pointers after a directory rename done outside Git.
When worktrees aren't the answer
The feature has real limitations, and it is worth knowing them before leaning on it.
Submodules remain the most visible caveat. The git-worktree manpage's BUGS section is blunt: multiple-checkout support "for submodules is incomplete"2. In practice this means that submodule state can drift between worktrees in ways that range from cosmetic (a stale modules/ directory) to destructive (a submodule update touching the wrong tree's index). Repositories that use submodules heavily should test worktree workflows carefully before adopting them wholesale.
The most underrated cost is per-worktree tooling. Every new checkout is a fresh directory as far as Node, Python, Docker, and every other ecosystem is concerned. npm install runs again. virtualenv is recreated. .env files are copied by hand or scripted into place. docker compose up starts a parallel stack, which may or may not clash on ports. For a short-lived worktree—a hotfix that lives for thirty minutes—this tax can easily exceed the ceremony of a well-behaved git stash. For a long-lived parallel feature branch, the tax amortises and the worktree wins. The decision is situational, and worth thinking about at the level of individual projects rather than committing to one strategy across a career. Some repositories have npm ci times measured in seconds, and worktrees are nearly free there. Others spin up a hefty local environment on first checkout, and the calculus changes.
None of this is a reason to avoid the feature. It is a reason to use it deliberately. The five-second peek at a colleague's commit is still a job for git stash && git checkout && git stash pop. The three-hour review of a complex pull request, or the live debugging of a production bug mid-refactor, is a job for a worktree.
git worktree trades one kind of friction for another. The cost is the boilerplate of bringing a second directory to life: a fresh npm install, a fresh Docker stack, a fresh copy of the secrets that never belonged in source control anyway. The benefit is the absence of the branch-switching tax—the pause before every git checkout to ask whether the working tree is clean, the mental cost of holding two parallel contexts in one directory and crossing them at the wrong moment.
A sensible adoption path is narrow. Pick one workflow that is causing real pain—the pull-request review is usually the easiest sell—and try it once this week: git worktree add ../pr-review origin/pr-1234, review, remove. If three or more concurrent checkouts start showing up in git worktree list, the .bare/ pattern pays for its setup quickly. For anyone who still reaches for git stash pop with a flinch, the feature is a quiet upgrade.
Your branches do not have to take turns.
Footnotes
-
Haggerty, Michael. (2015). 'Git 2.5, including multiple worktrees and triangular workflows.' The GitHub Blog. https://github.blog/open-source/git/git-2-5-including-multiple-worktrees-and-triangular-workflows/ ↩
-
Git Project. (2022). 'git-worktree(1) manual page, Git v2.39.0.' git-scm.com. https://git-scm.com/docs/git-worktree/2.39.0 ↩ ↩2 ↩3
-
Git Project. (2022). 'gitrepository-layout(5) manual page, Git v2.39.0.' git-scm.com. https://git-scm.com/docs/gitrepository-layout/2.39.0 ↩ ↩2
-
Git Project. (2015). 'Git 2.5.0 Release Notes.' Git source repository. https://raw.githubusercontent.com/git/git/v2.5.0/Documentation/RelNotes/2.5.0.txt (release tag: https://github.com/git/git/releases/tag/v2.5.0) ↩
-
Sunshine, Eric. (2015). 'PATCH v2 00/23 replace "checkout --to" with "worktree add".' Git mailing list. https://lore.kernel.org/all/1435969052-540-1-git-send-email-sunshine@sunshineco.com/T/ ↩
-
Git Rev News editorial team. (2015). 'Git Rev News Edition 5.' git.github.io. https://git.github.io/rev_news/2015/07/08/edition-5/ ↩
-
Haggerty, Michael. (2016). 'New Year, new Git release.' The GitHub Blog. https://github.blog/news-insights/new-year-new-git-release/ ↩
-
Git Project. (2018). 'git-worktree(1) manual page, Git v2.17.0.' Git source repository. https://raw.githubusercontent.com/git/git/v2.17.0/Documentation/git-worktree.txt ↩
-
Blau, Taylor. (2020). 'Highlights from Git 2.26.' The GitHub Blog. https://github.blog/open-source/git/highlights-from-git-2-26/ ↩
-
Kenlon, Seth. (2021). 'Experiment on your code freely with Git worktree.' opensource.com. https://opensource.com/article/21/4/git-worktree ↩
-
Teles, Phelipe. (2022). 'Git worktrees are great for code reviews.' phelipetls.github.io. https://phelipetls.github.io/posts/git-worktrees-are-great-for-code-reviews/ ↩
-
Vanderwal, Brian. (2016). 'Parallelize Development Using Git Worktrees.' Atomic Spin. https://spin.atomicobject.com/parallelize-development-git-worktrees/ ↩
-
Russell, Alex. (2021). 'Git Worktrees Step-By-Step.' Infrequently Noted. https://infrequently.org/2021/07/worktrees-step-by-step/ ↩
-
Cugerone, Morgan. (2021). 'How to use git worktree and in a clean way.' morgan.cugerone.com. https://morgan.cugerone.com/blog/how-to-use-git-worktree-and-in-a-clean-way/ ↩
-
musteresel. (2018). 'Gotcha with git worktree and removing the worktree directory.' musteresel.com. https://www.musteresel.com/posts/2018/01/git-worktree-gotcha-removed-directory.html ↩
-
Git Project. (2022). 'git-gc(1) manual page, Git v2.38.0.' git-scm.com. https://git-scm.com/docs/git-gc/2.38.0 ↩
