Parallelle ontwikkeling zonder hoofdpijn met Git worktree

Een eenvoudige visualisatie:

~/Herd/
├── my-project/          # hoofdworktree, branch `main`
│   └── .git/            # hoofd-git directory
├── my-project-feature/  # gekoppelde worktree, branch `feature/login-form`
└── my-project-hotfix/   # gekoppelde worktree, branch `hotfix/payment-bug`

Alle bovenstaande directories delen dezelfde commit-geschiedenis en zijn gekoppeld aan dezelfde .git-objectdatabase, ook al heeft elke directory zijn eigen werkstatus (working directory state).

Elke directory gedraagt zich als een normale checkout: je bewerkt bestanden, commit en push zoals gebruikelijk, maar je voorkomt dat je constant van branch moet wisselen binnen één enkele working tree.

Het verschil tussen een Git branch en een worktree

Traditioneel betekende werken aan meerdere branches dat je constant git checkout en git stash gebruikte om je voortgang op te slaan, van context te wisselen en te hopen dat je niets belangrijks was verloren. Het is makkelijk om het overzicht te verliezen, vooral wanneer een bug in productie je flow onderbreekt.

Met git worktree kun je een nieuwe working directory toevoegen voor elke branch (bestaand of nieuw) en je werkstromen gescheiden houden. Bijvoorbeeld:

# Voeg een bestaande branch toe als een worktree
git worktree add ../my-project-feature feature-branch

# Of maak in één keer een nieuwe branch en worktree aan
git worktree add -b new-feature ../my-project-new-feature

Dit maakt nieuwe directories aan op hetzelfde niveau als je hoofdproject, uitgecheckt op de branches die je opgeeft. Je kunt nu in elke directory onafhankelijk bestanden bewerken, committen en pushen zonder je hoofd-working directory aan te raken.

Je setup zou er dan als volgt uitzien:

my-project/              # hoofdworktree, branch `main`
my-project-feature/      # worktree voor `feature-branch`
my-project-new-feature/  # worktree voor `new-feature`

Een belangrijke beperking is dat dezelfde branch niet in meer dan één worktree tegelijk kan worden uitgecheckt. Elke worktree moet een unieke branch hebben. In de praktijk stimuleert dit een nette mapping van "één taak, één branch, één directory", wat het makkelijker maakt om mentaal het overzicht te bewaren.

Praktijkvoorbeeld: jongleren met een feature en een hotfix

Hier is een realistisch scenario: je werkt aan een checkout-feature wanneer er een bug in productie optreedt.

Initiële layout:

~/Herd/
└── shop/           # hoofdworktree, branch `main`
    └── .git/

Maak een feature-worktree aan:

cd ~/Herd/shop
git worktree add -b feature/checkout ../shop-checkout

Nieuwe layout:

~/Herd/
├── shop/           # hoofdworktree, branch `main`
│   └── .git/
└── shop-checkout/  # gekoppelde worktree, branch `feature/checkout`

Je kunt nu eenvoudig de checkout-feature ontwikkelen in shop-checkout, terwijl je shop op main houdt voor snelle reviews.

Er verschijnt een productiebug, maak een hotfix-worktree aan:

cd ~/Herd/shop
git worktree add -b hotfix/payment-fail ../shop-payment-hotfix

Je layout wordt nu:

~/Herd/
├── shop/                 # hoofdworktree, branch `main`
├── shop-checkout/        # feature-worktree, `feature/checkout`
└── shop-payment-hotfix/  # hotfix-worktree, `hotfix/payment-fail`

Op dit punt kun je:

  • De productiebug oplossen en testen in de shop-payment-hotfix worktree.
  • Doorgaan met het itereren aan feature/checkout in de shop-checkout worktree.
  • De shop directory vrijhouden op main voor merges en code reviews.

Hoe voeg je een worktree samen met een andere branch?

Het samenvoegen van wijzigingen uit een worktree-branch werkt precies hetzelfde als elke andere Git-merge, maar de context is duidelijker omdat elke branch in zijn eigen directory leeft. Hier is een typische workflow voor het mergen van de feature-branch naar main:

  1. Rond je werk in de feature-worktree af en commit je wijzigingen.
  2. Ga naar je hoofd-worktree directory:

cd ../my-project && git checkout main

  1. Merge de feature-branch:

git merge feature-branch

  1. Los eventuele conflicten op en push vervolgens.

Omdat elke worktree is toegewezen aan één specifieke branch, is het veel moeilijker om per ongeluk naar de verkeerde branch te committen of je plek te verliezen wanneer een hotfix je feature-werk onderbreekt... in theorie dan. 😉

Je huidige worktrees inspecteren

Voordat je begint met het verwijderen of opschonen van worktrees, is het nuttig om te zien welke worktrees Git momenteel kent:

git worktree list

Voorbeeld output:

/Users/barrd/Herd/shop                 66c16256 [main]
/Users/barrd/Herd/shop-checkout        0c8ba118 [feature/checkout]
/Users/barrd/Herd/shop-payment-hotfix  a16e4be2 [hotfix/payment-fail]

Je kunt dit mentaal als volgt mappen:

  • [main]/home/user/Herd/shop
  • [feature/checkout]/home/user/Herd/shop-checkout
  • [hotfix/payment-fail]/home/user/Herd/shop-payment-hotfix

Het is nu duidelijk welke branches zijn uitgecheckt en waar, wat helpt om te voorkomen dat je probeert een branch te hergebruiken die al aan een andere worktree is gekoppeld.

Een worktree verwijderen

Wanneer je klaar bent met een worktree, is het goede praktijk om op te ruimen. Doe dit veilig nadat je alle wijzigingen hebt gecommitt of gestashed:

git worktree remove ../my-project-feature

Dit werkt alleen bij "schone" worktrees (geen ongecommitte wijzigingen of ontracked bestanden), tenzij je --force gebruikt. De hoofd-worktree kan niet worden verwijderd op deze manier.

Onthoud: Het verwijderen van een worktree verwijdert alleen de working directory; het verwijdert niet de branch zelf. Controleer altijd met git status in die worktree voordat je deze verwijdert om te voorkomen dat je ongecommitte werk verliest.

Verouderde worktrees opschonen met prune

Als je een worktree-directory handmatig hebt verwijderd, bewaart Git nog steeds metadata hiervoor in de worktrees-directory van de hoofdrepository. In dat geval zal git worktree list vermeldingen tonen die als "missing" zijn gemarkeerd.

Je kunt deze verouderde vermeldingen opschonen met:

git worktree prune

Om alleen vermeldingen op te schonen die al een tijdje niet zijn gebruikt, kun je een vervaldatum toevoegen:

git worktree prune --expire 7.days.ago

Het gebruik van --expire verwijdert nu onmiddellijk alle verouderde worktree-metadata, wat handig kan zijn in een lokale development-omgeving waar je vaak oude, onnodige directories verwijdert.

Slotbeschouwingen

Deze functie bestaat al sinds versie 2.5, ongeveer 10 jaar geleden, en ik had hem nooit gebruikt... Maar het heeft mijn workflow nu getransformeerd, vooral bij het jongleren met parallelle features en urgente hotfixes. Het houdt alle elementen gescheiden, vermindert context-switching en zorgt ervoor dat stashing een zeldzame uitzondering is geworden.

Voor simpel, sequentieel werk is traditioneel branching nog steeds de juiste weg, maar als je er ooit naar verlangt om op twee plaatsen tegelijk te zijn, probeer dan git worktree.

Verder lezen

  • Git’s Database Internals (github.blog)
  • Git Worktree Docs (git-scm.com)