
Git worktrees, or how I stopped my parallel work colliding
On this page
I run a lot of work in parallel against a single repository. Several independent tasks, going at once, each on its own branch. The first time I tried it the naive way, they spent more time fighting each other than doing anything useful.
The collision storm#
A git repository has one working tree: one set of files on disk, one currently checked-out branch. If you kick off several tasks that each want their own branch, they all reach for that one checkout and start stepping on each other. One task switches the branch out from under another. A half-finished change from stream A is suddenly visible to stream B. You get a checkout collision storm, and the output is nonsense because none of the streams had a stable view of the code. It is the software version of several cooks sharing one chopping board: someone always sweeps your half-prepped ingredients aside to start their own, and nobody’s dish comes out right.
The mistake is treating “parallel branches” and “parallel working directories” as the same thing. They are not. Branches are cheap and shareable. The working tree is a single, contended resource.
One checkout per stream#
git worktree fixes exactly this. It lets one repository have several working
trees at once, each with its own checked-out branch, all sharing the same
underlying object store. Each stream of work gets its own directory, its own
checkout, its own branch, and none of them can disturb the others’ files.
git worktree add ../repo-task-01 -b task-01git worktree add ../repo-task-02 -b task-02Now two tasks can genuinely run at the same time, because “the files on disk” is no longer one thing they share. When a stream is done and merged, you remove its worktree and the branch goes with it.
Branches are cheap; the working tree is the contended resource. If you want real parallelism in one repo, give each stream its own working tree, not just its own branch.
Partition by the files, not just the tree#
Isolated checkouts stop the streams from corrupting each other mid-flight, but they do not stop two of them changing the same file and colliding at merge. So the other half of the discipline is scheduling: batches that run in parallel are chosen to touch disjoint files. Two tasks that both edit the same shared registration file are sequenced instead, not run together.
And a mechanical refactor gets a cheap acceptance gate to prove it actually finished everywhere. After a change that was supposed to leave a certain constructor in exactly one place, a grep asserting it appears in exactly one file catches any stray copy a parallel stream reintroduced.
Why this matters more every year#
This started as a git tip and turned into how I think about doing many things at once. As more of my work runs as parallel streams, isolation stops being a nicety and becomes the thing that makes parallelism produce correct output instead of a mess. Give each stream its own tree, partition the work by what it touches, and put a cheap gate at the merge to prove nothing collided. The same shape scales from two terminal tabs to a lot more than that.


