Werken met Git Worktrees in Magit

Wat me uiteindelijk introduceerde bij worktrees waren, van alle dingen, AI-coding agents. Tools zoals Claude Code maken een worktree aan voor elke taak, zodat meerdere agents (of meerdere sessies van dezelfde agent) parallel aan hetzelfde repo kunnen werken zonder elkaar — of mij — in de weg te zitten. Plotseling stond mijn projectmap vol met 'cider-dit' en 'cider-dat' mappen, en ik besloot uit te zoeken wat daar precies aan de hand was.

Worktrees versus Branches

Een branch is simpelweg een verplaatsbare pointer naar een commit; het aanmaken ervan is in feite gratis. Het addertje onder het gras is dat een repository slechts één werkmap (working directory) heeft. Werken op twee verschillende branches betekent dus dat je constant tussen die directories moet schakelen. Je kent het proces: stash je halfvoltooide werk (of commit het), check de andere branch uit, doe wat je moet doen, check de eerste branch weer uit en haal de stash op. Het werkt, maar het is tedious. Dit wordt erger wanneer de twee branches je project in verschillende build-states achterlaten, waardoor elke switch betekent dat de helft van de wereld opnieuw gecompileerd moet worden.

Een worktree geeft je een extra werkmap die gekoppeld is aan dezelfde repository:

$ git worktree add ../cider-smart-targeting -b smart-form-targeting

Nu is ~/projects/cider-smart-targeting een volledige checkout van die branch, terwijl je hoofdcheckout precies blijft staan waar deze stond. De object-database, refs, stashes en remotes worden gedeeld. Een worktree is dus geen kloon; fetchen in de ene is fetchen in alle anderen, en het aanmaken ervan gaat bijna direct. Elke worktree heeft zijn eigen HEAD en index, en git hanteert één eenvoudige regel: een branch kan slechts in één worktree tegelijk worden uitchecked.

Wanneer zijn ze nuttig? Wanneer er twee dingen tegelijkertijd moeten gebeuren: een lange testrun op de ene branch terwijl je op een andere werkt, het reviewen van een PR zonder je halfvoltooide werk te verstoren, of — de reden waarom iedereen er tegenwoordig over praat — AI-agents die in isolatie hun gang gaan.

De prijs is bescheiden: je werkbestanden staan vaker dan één keer op de schijf, en alles wat niet door git wordt bijgehouden (dependencies, build-caches, node_modules en aanverwanten) moet in elke worktree opnieuw worden ingesteld. Als branches nooit beperkend voor je voelden, is dat prima; dat was voor mij in tien jaar ook niet het geval. Worktrees zijn zo'n feature die je pas mist zodra je workflow verandert.

Hoe zit het met Jujutsu?

Nu we het over werkkopieën hebben: het interessantste dat er momenteel gebeurt in versiebeheer is Jujutsu (jj), een git-compatibel VCS dat het probleem dat worktrees oplossen grotendeels doet verdwijnen. In jj is de werkkopie een commit die automatisch wordt gesnapshotted terwijl je werkt. Er is geen staging-area en geen stash, omdat er geen ongecommitteerde staat is die verloren kan gaan of in de weg kan zitten; het wisselen van context is altijd veilig. Het heeft bovendien proper support voor parallelle werkmappen (jj workspace), plus een operatielog waardoor vrijwel alles ongedaan gemaakt kan worden. Dat laatste is mede reden waarom de AI-agent community interesse heeft in dit project.

Je kunt jj gebruiken bovenop een bestaande git-repository (dit noemen ze een colocated repo), waardoor je collega's — en Magit — gewoon een normale git-repo blijven zien. Ik ben vooralsnog slechts een waarnemer, maar het is duidelijk een project om in de gaten te houden.

Worktrees in Magit

Terug naar Emacs. Magit ondersteunt worktrees al jaren, verscholen achter de Z-toets:

ToetsCommandoBeschrijving
Z bmagit-worktree-checkoutCheck een bestaande branch uit in een nieuwe worktree
Z cmagit-worktree-branchMaak in één keer een nieuwe branch en worktree aan
Z gmagit-worktree-statusSpring naar de status-buffer van een andere worktree
Z mmagit-worktree-moveVerplaats een worktree
Z kmagit-worktree-deleteVerwijder een worktree

Het beste is dat er verder niets nieuws te leren is. Elke worktree krijgt zijn eigen Magit status-buffer, en elk Magit-commando werkt op de worktree waartoe de huidige buffer behoort. Z g is het enige schakelmechanisme dat je nodig hebt, en zelfs dat is slechts een shortcut om een andere status-buffer te bezoeken.

Enkele praktische tips uit mijn ervaring:

  • Zichtbaarheid: Standaard worden worktrees niet vermeld in de status-buffer. Dit kun je oplossen met:

``elisp (magit-add-section-hook 'magit-status-sections-hook #'magit-insert-worktrees nil t) ` Nu toont elke status-buffer alle worktrees van de repo, en kun je op RET` drukken om er direct naartoe te springen.

  • Locatie: Maak worktrees aan als 'siblings' van de hoofdcheckout met beschrijvende namen (bijv. cider-smart-targeting naast cider), en niet genest daarbinnen. Nesting verwart tools zoals grep en find.
  • Branch-selectie: De branch-selectie van Magit annoteert branches die in een andere worktree zijn uitgecheckt met het pad van die worktree, en weigert ze voor een tweede keer uit te checken (dat is een regel van git, niet van Magit). Als je je ooit hebt afgevraagd waarom een branch "niet wil uitchecken", is dat meestal de reden.
  • Projectintegratie: Voor zover project.el (of Projectile) het betreft, is elke worktree een eigen project. Project-switching, per-project buffers en zoeken werken dus op natuurlijke wijze.
  • Dependencies: Alles wat niet door git wordt bijgehouden, gaat niet automatisch mee. Je moet dependencies per worktree installeren en beginnen met een lege build-cache. Voor Elisp kost dit niets; voor een groot JVM- of JS-project is dit het belangrijkste nadeel.
  • Opschonen: Wanneer je klaar bent met een worktree, verwijder deze dan met Z k (of via git worktree remove in de command line). Als je de map handmatig verwijdert, zal git worktree prune de overgebleven administratie opruimen.

Slotgedachten

Tegenwoordig ziet mijn workflow met agents er meestal zo uit: een agent doet zijn werk in een worktree, ik review de wijzigingen daar in Magit (vaak terwijl een andere taak draait in een tweede worktree), en de worktree verdwijnt zodra de branch is gemerged. Misschien vind ik in de toekomst nog andere toepassingen voor worktrees — we zullen zien.

***

Voetnoot: Worktrees werden geïntroduceerd in git 2.5, uitgebracht in juli 2015.