P
imvdmolen.nl
Blog

Git subtree voor subprojecten beheren in Laravel

Gisteren stuitte ik tijdens het refactoren van een Laravel-applicatie op een situatie die waarschijnlijk bekend voorkomt. Ik had een custom payment gateway pakket geschreven dat ik oorspronkelijk als onderdeel van het hoofdproject had ontwikkeld, maar nu wilde hergebruiken in andere projecten. Het probleem: hoe splits je zoiets netjes af zonder de Git-geschiedenis te verliezen? Git subtree bleek precies de oplossing die ik zocht.

Veel developers kennen Git submodules, maar ik merk dat Git subtree vaak onbekend terrein blijft. Beide tools helpen bij het beheren van subprojecten, maar subtree heeft een cruciaal voordeel: het integreert de code volledig in je hoofdrepository. Geen losse referenties naar externe repositories die kunnen breken, geen complexe checkout-procedures voor nieuwe teamleden. Alles staat gewoon in je repository, maar je kunt het nog steeds als separaat project onderhouden.

Subtree toevoegen aan je Laravel-project

Het toevoegen van een subtree begint altijd met het identificeren van de map die je als subtree wilt beheren. Stel je hebt in je Laravel-project een packages/payment-gateway directory die je als apart project wilt gaan onderhouden. Eerst creëer je een nieuwe repository voor dit pakket op GitHub of GitLab. Daarna gebruik je het git subtree add commando om de connectie te leggen:

git subtree add --prefix=packages/payment-gateway [email protected]:jouwusername/payment-gateway.git main --squash

De --prefix parameter vertelt Git waar de subtree zich bevindt in je hoofdproject. De --squash optie zorgt ervoor dat alle commits uit de externe repository worden samengevoegd tot één commit in je hoofdrepository. Dit houdt je Git-geschiedenis schoon en voorkomt dat je hoofdproject vervuild raakt met irrelevante commit-berichten uit het subproject.

Wat ik altijd doe na het toevoegen van een subtree is controleren of de remote correct is toegevoegd. Git subtree slaat geen permanente referenties op naar externe repositories, dus je moet bij elke push/pull-operatie expliciet de URL opgeven. Een handige truc is het aanmaken van aliases in je .git/config:

[alias]
    subtree-push-payment = subtree push --prefix=packages/payment-gateway [email protected]:jouwusername/payment-gateway.git main
    subtree-pull-payment = subtree pull --prefix=packages/payment-gateway [email protected]:jouwusername/payment-gateway.git main

Deze aliases maken het werken met subtrees veel minder foutgevoelig. Je hoeft niet telkens de volledige URL en prefix te onthouden.

Synchronisatie tussen hoofd- en subproject

Zodra je subtree is geconfigureerd, begint het eigenlijke werk: het synchroniseren van wijzigingen tussen je hoofdproject en het subtree-project. Dit proces verschilt fundamenteel van submodules omdat de code daadwerkelijk bestaat in beide repositories.

Wijzigingen die je maakt in de subtree-directory van je hoofdproject kun je doorsturen naar het subtree-project met git subtree push. Dit commando pakt alle commits die betrekking hebben op de subtree-directory en repliceert ze in het externe project:

git subtree push --prefix=packages/payment-gateway [email protected]:jouwusername/payment-gateway.git main

Andersom werkt het ook: wijzigingen die direct in het subtree-project worden gemaakt, kun je binnenhalen met git subtree pull:

git subtree pull --prefix=packages/payment-gateway [email protected]:jouwusername/payment-gateway.git main --squash

Een belangrijk punt waar ik tegenaan liep: Git subtree kan soms verwarrend zijn met merge-conflicts. Omdat de code in beide repositories bestaat, kunnen wijzigingen botsen op manieren die je niet verwacht. Mijn strategie hiervoor is altijd om één repository als 'leading' te beschouwen. Meestal is dat het subtree-project zelf, waar ik features ontwikkel, en gebruik ik het hoofdproject vooral voor integratie en testing.

Workflow-integratie in teamverband

Werken met subtrees in een team vereist duidelijke afspraken. Git subtree-operaties zijn niet altijd voor iedereen zichtbaar, en het kan verwarrend zijn wanneer collega's plotseling files zien verschijnen of verdwijnen na een git pull.

Mijn aanpak hiervoor is het documenteren van subtree-operaties in commit-berichten. Wanneer ik een subtree-push doe, vermeld ik altijd expliciet in de commit-boodschap dat het een subtree-operatie betreft. Dit helpt teamleden om te begrijpen waarom bepaalde wijzigingen zijn gemaakt.

Voor deployment-processen gebruik ik vaak een script dat automatisch controleert of subtrees up-to-date zijn. Dit voorkomt situaties waarbij lokale wijzigingen in subtree-directories niet zijn gesynchroniseerd naar de externe repositories:

#!/bin/bash
echo "Checking subtree synchronization..."
git subtree pull --prefix=packages/payment-gateway [email protected]:jouwusername/payment-gateway.git main --squash
git subtree push --prefix=packages/payment-gateway [email protected]:jouwusername/payment-gateway.git main

Dit script draai ik als onderdeel van mijn pre-deployment checks. Het haalt eerst eventuele externe wijzigingen binnen en pusht vervolgens lokale wijzigingen terug. Zo weet ik zeker dat beide repositories gesynchroniseerd zijn voordat ik deploy.

Praktische overwegingen en valkuilen

Sinds ik Git subtree intensief gebruik, ben ik tegen enkele specifieke uitdagingen aangelopen die de documentatie niet altijd duidelijk maakt. Een daarvan is dat subtree-operaties relatief traag kunnen zijn in grote repositories. Git moet de volledige geschiedenis doorzoeken naar commits die betrekking hebben op de subtree-directory, wat tijd kost.

Directory-herstructureringen zijn een ander aandachtspunt. Wanneer je een subtree-directory hernoemt of verplaatst, verlies je de connectie met het externe project. Git subtree gebruikt de prefix-path om te bepalen welke commits relevant zijn. Verander je deze path, dan kan Git niet meer automatisch bepalen welke wijzigingen gesynchroniseerd moeten worden.

Branch-management wordt ook complexer met subtrees. Wanneer je in je hoofdproject op een feature-branch werkt en wijzigingen maakt in een subtree-directory, moet je bewust beslissen of deze wijzigingen ook naar het subtree-project moeten. Niet alle wijzigingen die je maakt in de context van je hoofdproject zijn relevant voor het subtree-project.

Mijn ervaring heeft geleerd dat Git subtree het beste werkt voor stabiele, goed gedefinieerde componenten. Voor experimentele code of tijdelijke features is het vaak overkill. Het setup-proces heeft overhead, en die investering moet je terugverdienen door hergebruik en betere organisatie.

Ondanks deze uitdagingen blijf ik Git subtree gebruiken voor Laravel-pakketten die ik in meerdere projecten hergebruik. Het geeft me de flexibiliteit om packages zowel in isolatie te ontwikkelen als geïntegreerd te testen, zonder de complexiteit van package-versioning en Composer-dependencies tijdens de ontwikkelfase.