Git Worktrees
NOTES
Doel van de les¶
- Het probleem: één working directory per repo; uncommitted werk blokkeert
git switch- en waarom je soms op meerdere branches tegelijk wil werken. - De tussenoplossingen (meerdere clones,
git stash, WIP-commits) en waarom ze onhandig zijn. - Worktrees als nette oplossing:
git worktree add/list/remove. - Kern van de les: worktrees zijn vandaag de standaardmanier om met meerdere coding agents tegelijk aan één project te werken.
Kernboodschap¶
- Één repo, meerdere working directories: elke worktree heeft zijn eigen bestanden, maar deelt de Git-database (geschiedenis, branches, remotes).
- Branches isoleren commits; worktrees isoleren bestanden.
- Twee agents in dezelfde directory overschrijven elkaar zonder enige foutmelding. Één working directory per agent is dus geen luxe, maar noodzaak.
Voorbereiding¶
- Demo-repo (
hello) lokaal klaarzetten en de volledige demo op voorhand testen. - Twee terminaltabs voorbereiden; opencode geïnstalleerd en ingelogd.
- Commando's testen op het OS van de klas (op Windows:
git worktree add -b hotfix ..\hello-hotfix). - Worktrees bestaan sinds Git 2.5: check
git --version(elke moderne installatie kan het).
Verloop van de les¶
- Conceptueel lesgeven: studenten moeten worktrees vooral begrijpen en herkennen, niet blindelings kunnen opdreunen.
- De oefeningen zijn kort; het actieve moment zit in de demo op het einde.
- Laat de demo traag genoeg gaan om
git worktree listen de afwezigheid van conflicten echt te tonen.
Het probleem: op meerdere branches tegelijk werken¶
Tot nu toe werkten we altijd in één directory. Je kiest een branch, en Git past de bestanden in die directory aan. Dat werkt prima voor één taak, maar zodra je aan meerdere dingen tegelijk wil werken, wringt het.
Wanneer wil je dat eigenlijk?
- Een feature is half af, en plots is een dringende fix op
mainnodig (een hotfix). - Een pull request van iemand anders reviewen of uittesten zonder je eigen werk weg te leggen.
- Experimenteren of in de geschiedenis speuren zonder je huidige branch te bevuilen.
- Een (of meerdere!) coding agent een taak geven terwijl jij zelf verder werkt.
Waarom is dat zo lastig? Omdat een repo standaard één working directory heeft. Die directory hoort bij de branch die op dat moment open staat. Wil je naar een andere branch, dan moet die working directory mee... inclusief al je niet-gecommitte werk.
Je werkt bijvoorbeeld al een uur aan feature-search, als een collega om een dringende hotfix vraagt. Even snel naar main switchen? Git steekt daar een stokje voor:
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello.py
git switch mainerror: Your local changes to the following files would be overwritten by checkout:
hello.py
Please commit your changes or stash them before you switch branches.
Aborting
Git weigert omdat je aanpassing aan hello.py verloren zou gaan bij het switchen naar main.
Ook een geslaagde switch is riskant
Zelfs wanneer git switch wel lukt (omdat er geen overlap is), is het gedrag gevaarlijk: Git neemt je niet-gecommitte nieuwe bestanden dan stilletjes mee naar de andere branch.
Op het eerste gezicht lijkt dat handig, tot je daar iets commit: je half afgewerkte feature belandt dan in een commit op main (of omgekeerd). Je werk is dan niet verloren, maar je geschiedenis wordt er wel erg rommelig van.
De onhandige tussenoplossingen¶
Er bestaan een paar klassieke trucs om dit probleem te omzeilen. Ze werken, maar geen enkele is echt aangenaam.
Meerdere clones¶
git clonein een aparte directory per taak: elke directory werkt als een "eigen" repo.- Nadelen: elke clone heeft een eigen
.git(schijfruimte, traag bij grote repos), branches en remotes lopen uit elkaar, en je werkt zo snel in de verkeerde clone.
git stash / git stash pop¶
- Werk opzijzetten (
git stash), switchen, later terugzetten (git stash pop). - Nadelen:
popbotst als de andere branch intussen veranderde, de stash-stack wordt een rommel, stashes hangen aan geen enkele branch (makkelijk "verloren"), en je verliest alle context.
git switch mainSwitched to branch 'main'
git stash popOn branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello.py
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (dbcc7816404d03e3070050169fc4c8b56992973f)
Merk op wat er in dit voorbeeld eigenlijk gebeurt: de aanpassing hoorde inhoudelijk bij feature-search, maar staat nu als losse wijziging op main. Je moet zelf goed bijhouden waar welk werk thuishoort.
"WIP"-commits¶
- Een Work-in-Progress tussencommit maken zodat je kan switchen, later rechtzetten met
amend/rebase. - Nadelen: vervuilde geschiedenis, risico dat een WIP-commit per ongeluk gepusht wordt of in een PR terechtkomt, en CI die op een halve feature draait.
Alle oplossingen op een rij:
| Oplossing | Voordeel | Grootste nadeel |
|---|---|---|
| Meerdere clones | Volledige isolatie: eigen working directory én eigen Git-repo. | Dubbel beheer: aparte .git, aparte remotes, afwijkende branches - en je werkt snel in de verkeerde clone. |
git stash |
Snel, werkt altijd lokaal. | Stashes hangen aan geen enkele branch; vergeten stashes en conflicten bij pop. |
| WIP-commit | Eenvoudig: gewoon committen en later opkuisen. | Vervuilde geschiedenis; een WIP-commit glipt makkelijk mee in een push, PR of CI-run. |
| Worktree | Eigen working directory, gedeelde geschiedenis - geen extra repo. | Extra directory om te beheren; build-artefacten en tooling bestaan per worktree. |
Worktrees: de betere oplossing¶
Worktree
Een worktree is een extra working directory (working tree) die aan dezelfde Git repo hangt. Elke worktree heeft zijn eigen bestanden en uitgecheckte branch; geschiedenis, branches en remotes worden gedeeld via dezelfde .git-directory.
flowchart BT
G["Git database (.git)<br>geschiedenis · branches · remotes"]
W1["working directory 1<br>hello · branch main"] --> G
W2["working directory 2<br>hello-hotfix · branch hotfix"] --> G
W3["working directory 3<br>hello-agent · branch feature-x"] --> G
Extra: Een worktree herkennen
In een gewone repo is .git een directory. In een worktree is .git een bestand dat verwijst naar de administratie van die worktree in de hoofdrepo:
Handige regel dus: zie je een .git-bestand in plaats van een .git-directory, dan zit je in een worktree.
De belangrijkste commando's:
| Commando | Beschrijving |
|---|---|
git worktree add -b <branch> <pad> |
Nieuwe worktree aanmaken met een nieuwe branch. |
git worktree add <pad> <bestaande-branch> |
Nieuwe worktree voor een bestaande branch of commit. |
git worktree list |
Overzicht van alle worktrees, met hun branch en laatste commit. |
git worktree remove <pad> |
Worktree verwijderen (de branch blijft bestaan). |
git worktree prune |
Administratie opkuisen van worktrees waarvan de directory al verdwenen is. |
Paden op Windows
Gebruik in de commando's het pad naar de map voor de nieuwe worktree. Op Windows werkt zowel ..\hello-hotfix als ../hello-hotfix.
Een basisvoorbeeld:
HEAD is now at 22b1b17 Add module docstring in hello.py
cd ../hello-hotfixgit statusOn branch hotfix
nothing to commit, working tree clean
Je kan nu gewoon werken in ../hello-hotfix: bestanden aanpassen, tests draaien, committen. Het is een volwaardige working directory met een eigen HEAD en staging area. Na een commit:
22b1b17 Add module docstring in hello.py
cd ../hellogit worktree list/Users/bob/dev/hello 22b1b17 [main]
/Users/bob/dev/hello-hotfix 8e1c3a7 [hotfix]
git worktree list toont duidelijk dat elke worktree zijn eigen branch en laatste commit heeft. Enkele eigenschappen:
-
Eén branch per worktree: een branch kan maar in één worktree tegelijk bestaan. Probeer je het toch, dan weigert Git:
git switch hotfixfatal: 'hotfix' is already used by worktree at '/Users/bob/dev/hello-hotfix' -
Gedeelde geschiedenis: de commit
8e1c3a7staat onmiddellijk in de hele repo - in alle worktrees, zonder pushen of fetchen. Commandos alsgit log --allofgit branchtonen alles van alle worktrees samen. - Eigen toestand: elke worktree heeft zijn eigen working directory, staging area en
HEAD. Een commit in de ene worktree verandert niets aan de bestanden van de andere.
Vanuit de hoofdrepo kan je de hotfix nu mergen, net als elke andere branch:
Fast-forward
hello.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
En opruimen:
Worktree verwijderen met niet-gecommitte aanpassingen
git worktree remove weigert als de worktree aangepaste of untracked bestanden bevat:
Extra: opruimen na handmatig verwijderen
Verwijder je een worktree-directory handmatig (rm -rf hello-hotfix) in plaats van met git worktree remove, dan blijft er administratie achter. git worktree list toont de verdwenen worktree dan als prunable, en git worktree prune kuis de restanten op.
/Users/maria/dev/hello-hotfix 8e1c3a7 [hotfix] prunable
git worktree prunegit worktree list/Users/maria/dev/hello 22b1b17 [main]
Extra: een commit of pull request uittesten zonder branch
Je wil even rondkijken in een oude commit of de branch van een PR uittesten, zonder daar een eigen branch voor te maken. Dat kan met een detached HEAD worktree:
Het resultaat is een gewone directory waarin je vrij kan experimenteren; je gooit ze achteraf gewoon weg met git worktree remove.
Beperkingen¶
Worktrees zijn geen wondermiddel. Hou rekening met:
- Virtuele Python omgeving (uv) is lokaal in elke worktree en moet dus in nieuwe worktrees opnieuw worden aangemaakt (bvb
uv sync) - .gitignore bestanden (bvb.
.env) zitten niet in Git en dus ook niet in een nieuwe worktree. - Opkuis: worktrees en hun branches verdwijnen niet automatisch. Gebruik
git worktree listom te zien wat er rondslingert en ruim op wanneer een taak klaar is.
Oefeningen¶
Oefening 1: het probleem voelen¶
Werk in je eigen hello repo.
- Maak een nieuwe branch
feature-searchen switch ernaar. - Maak een aanpassing in
hello.py, maar commit ze niet. - Probeer naar
mainte switchen. Wat gebeurt er? - Zet je werk opzij met
git stash, switch naarmainen haal het terug metgit stash pop. Op welke branch staat je aanpassing nu? - Bespreek: wat zou er gebeuren als je hier per ongeluk commit? En wat als je de stash was vergeten?
- Ruim op:
git restore hello.py.
Oplossing
# pas hello.py aangit switch mainerror: Your local changes to the following files would be overwritten by checkout:
hello.py
Please commit your changes or stash them before you switch branches.
Aborting
Git beschermt je werk. Met git stash geraak je alsnog op main:
git switch mainSwitched to branch 'main'
git stash popOn branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello.py
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (dbcc7816404d03e3070050169fc4c8b56992973f)
De aanpassing die bij feature-search hoorde, staat nu op main. Precies daarom zijn stashes en WIP-commits zo foutgevoelig.
Oefening 2: een worktree in de praktijk¶
- Voer
git worktree listuit in jehellorepo. Hoeveel worktrees zijn er? - Maak een hotfix-worktree aan:
git worktree add -b hotfix ../hello-hotfix. - Open
../hello-hotfixin VS Code en pashello.pyaan. Commit je wijziging. - Keer terug naar je hoofdrepo. Vraag jezelf af: zonder te pushen of te pullen, is de commit van de hotfix hier al bekend? Controleer met
git log --all --onelineengit worktree list. - Merge de hotfix in
main. - Ruim op: verwijder de worktree en daarna de branch. Controleer met
git worktree listengit branchwat er overblijft. - Extra: probeer
git switch hotfixterwijl de worktree nog bestaat. Wat gebeurt er?
Oplossing
git worktree add -b hotfix ../hello-hotfixPreparing worktree (new branch 'hotfix')
HEAD is now at 22b1b17 Add module docstring in hello.py
# open../hello-hotfix# pas hello.py aan en commitgit commit -a -m "Fix urgent bug in hello.py"[hotfix 8e1c3a7] Fix urgent bug in hello.py
1 file changed, 1 insertion(+), 1 deletion(-)
# open../hellogit log --all --oneline8e1c3a7 (hotfix) Fix urgent bug in hello.py
22b1b17 (HEAD -> main) Add module docstring in hello.py
De commit is onmiddellijk zichtbaar in de hoofdrepo: worktrees delen dezelfde Git-database. Mergen en opruimen:
Fast-forward
hello.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
git worktree remove ../hello-hotfixgit branch -d hotfixDeleted branch hotfix (was 8e1c3a7).
git worktree list/Users/maria/dev/hello 8e1c3a7 [main]
git branch* main
De extra opdracht geeft opnieuw fatal: 'hotfix' is already used by worktree at ...: zolang de worktree bestaat, is de branch bezet.
Worktrees en coding agents¶
Tot hier leek een worktree vooral een handigheidje voor wie vaak van branch wisselt. Maar er is één ontwikkeling die worktrees van handig naar noodzakelijk opwaarderen: coding agents.
Waarom één working directory niet werkt voor agents¶
Een coding agent (OpenCode, Claude Code, Codex, ...) werkt in de working directory. Hij leest en bewerkt bestanden, voert commando's uit, draait tests en maakt commits. Dat is precies wat een agent nuttig maakt, maar ook waar het misgaat zodra er twee dingen tegelijk in die directory gebeuren:
- Twee agents, één directory: beide bewerken dezelfde bestanden. Er is geen enkele foutmelding wanneer agent B het werk van agent A overschrijft; de laatste schrijver wint.
- Jij en een agent: terwijl de agent werkt, staat jouw directory op elk moment in beweging. Je eigen half afgewerkte wijzigingen en die van de agent belanden in dezelfde working tree en dus in dezelfde diff.
- Agents en branches: een branch isoleert je commits, maar niet je bestanden. Twee agents op twee verschillende branches kunnen dus nog steeds niet tegelijk in dezelfde working directory werken: elke
git switchverandert de bestanden onder de voeten van de andere agent.
De regel is simpel: één working directory per agent. Precies de isolatie die we krijgen met worktrees.
| Branch | Worktree | |
|---|---|---|
| Isoleert | commits | commits en bestanden |
| Eigen working directory | nee, gedeeld | ja, één per worktree |
| Geschiedenis, remotes | gedeeld | gedeeld |
Het resultaat:
- Echte isolatie: agents kunnen elkaar niet overschrijven.
- Jij blijft verder werken: je eigen working directory blijft gewoon op
mainstaan, zonder stash of WIP-commits. Wat de agents doen, raakt jou niet. - Parallel werk: je kan twee, drie of meer agents tegelijk verschillende taken laten uitvoeren, elk op een eigen branch + worktree.
- Controle op het einde: elke agent levert een eigen branch op. Je kan dan kiezen wat er in
mainkomt, in welke volgorde.
Worktrees in de praktijk van agent-tools¶
Je zal worktrees steeds vaker tegenkomen, ook als je er zelf geen aanmaakt. Sommige agents maken zelf een worktree per sessie. Claude Code doet dat bijvoorbeeld met claude --worktree feature-x: je krijgt een geïsoleerde directory onder .claude/worktrees/ met een eigen branch.
Tools zoals wt (Worktrunk) beheren volledige worktree-workflows: aanmaken, switchen, opruimen en automatische stappen zoals dependencies installeren. Agent-orchestratoren gebruiken hetzelfde patroon: één taak = één werkdirectory = één branch.
Als developer hoef je dus niet elke worktree zelf aan te maken. Waar het op aankomt:
- Begrijpen dat een worktree een gewone working directory is, met een eigen branch, binnen dezelfde repo.
- Herkennen wanneer een tool of agent er een gebruikt: een
.git-bestand, een pad buiten je gewone repo-directory, ofgit worktree list. - Beoordelen wat er in die branch gebeurt voor je mergt:
git log --all, de diff bekijken, tests draaien. - Opruimen wanneer de taak klaar is:
git worktree removeen daarna de branch verwijderen.
Een agent-instructie die worktrees respecteert¶
Werkt een agent in zijn eigen worktree, dan moet hij daar ook blijven. Dat leg je vast in de projectinstructies (AGENTS.md) of in de prompt:
## Git-regels
- Je werkt in een eigen worktree: werk uitsluitend in de huidige directory.
- Voer geen `git switch`, `git checkout -b` of `git worktree`-commando's uit.
- Commit je wijzigingen op de branch die hier uitgecheckt is; raak `main` niet aan.
- Draai de tests lokaal voor je commit.
En bij het starten geef je de agent een concrete taak, in de worktree waar hij hoort:
$ cd ../hello-feature-logging
$ opencode run "Voeg logging toe aan hello.py en commit je wijziging op deze branch."
Demo: twee opencode-sessies in aparte worktrees¶
We sluiten af met de workflow waar het in deze les echt om draait: twee agents, één project, twee worktrees.
NOTES
Laat de demo traag genoeg gaan zodat cursisten dit expliciet zien:
git worktree listis de hele tijd de "vloot" aan werkplekken.- In de hoofdrepo verandert niets zolang de agents werken.
- Beide agents maken onafhankelijke commits; er is geen enkele fout of overschrijving, want ze delen geen bestanden.
- Jij beslist op het einde wat er in
mainkomt (review + merge). - Opruimen is twee commando's per worktree.
Variant om de controle te benadrukken: laat beide agents in hetzelfde bestand (hello.py) werken. De worktrees blijven perfect geïsoleerd; het conflict verschijnt pas bij de merge - waar jij het oplost.
Samenvatting¶
Samenvatting
- Een Git repo heeft standaard één working directory. Uncommitted werk blokkeert
git switch(of reist stiekem mee naar de andere branch), en twee agents in dezelfde directory overschrijven elkaars werk zonder foutmelding. - Klassieke tussenoplossingen zijn meerdere clones (dubbel beheer),
git stash(hangt aan geen enkele branch, conflicten bijpop) en WIP-commits (vervuilde geschiedenis). - Een worktree is een extra working directory voor dezelfde repo: eigen bestanden en branch, gedeelde geschiedenis via dezelfde
.git. Je herkent er een aan het.git-bestand;git worktree listtoont ze allemaal. - Worktrees maak je met
git worktree add -b <branch> <pad>, je verwijdert ze metgit worktree remove <pad>- de branch blijft daarna bestaan. Eén branch kan maar in één worktree tegelijk uitgecheckt zijn. - Build-artefacten, virtuele omgevingen en genegeerde bestanden (
.env, ...) bestaan per worktree: reken op een extra setup per worktree. - Coding agents maken worktrees noodzakelijk: één working directory per agent vermijdt overschreven werk, verrassende branch-switches en verloren wijzigingen.
- Elke agent levert een eigen branch op; jij reviewt en mergt op het moment dat het jou past. Experimenten gooi je weg met één
git worktree remove. - Sommige agents maken zelf worktrees aan (bv. Claude Code met
claude --worktree); tools alswtbeheren de volledige workflow. Als developer moet je worktrees vooral herkennen, begrijpen en controleren.
Belangrijke nieuwe commando's:
Wat moet je kunnen?
- Je kan uitleggen waarom één working directory beperkend is wanneer je op meerdere branches tegelijk wil werken.
- Je kan de tussenoplossingen (meerdere clones,
git stash, WIP-commits) benoemen met hun grootste nadeel. - Je kan uitleggen wat een worktree is: een extra working directory met een eigen branch, maar met een gedeelde Git-database.
- Je herkent een worktree aan het
.git-bestand of metgit worktree list. - Je kan in eigen woorden uitleggen waarom worktrees cruciaal zijn bij parallelle coding agents: eigen bestanden, eigen branch, geen overschrijvingen, controle bij de merge.
- Je weet hoe je een worktree opruimt (
git worktree remove) en dat je de branch daarna zelf nog verwijdert.