Articles

Een succesvol Git branching model

Posted on

Note van reflectie (5 maart 2020)

Dit model is bedacht in 2010, nu meer dan 10 jaar geleden, en niet erg lang nadat Git zelf ontstond. In die 10 jaar is git-flow (het vertakkingsmodel dat in dit artikel wordt beschreven) enorm populair geworden in veel softwareteams, tot het punt dat mensen het zijn gaan behandelen als een soort standaard – maar helaas ook als een dogma of wondermiddel.

In die 10 jaar heeft Git zelf de wereld stormenderhand veroverd, en het populairste type software dat met Git wordt ontwikkeld verschuift steeds meer naar web apps – in ieder geval in mijn filter bubble. Web apps worden meestal continu geleverd, niet teruggerold, en je hoeft niet meerdere versies van de software te ondersteunen die in het wild draaien.

Dit is niet de klasse software die ik in gedachten had toen ik de blogpost 10 jaar geleden schreef. Als je team aan continue levering van software doet, zou ik voorstellen om een veel simpelere workflow te adopteren (zoals GitHubflow) in plaats van te proberen Git-flow in je team te proppen.

Als je echter software bouwt die expliciet geversioneerd is, of als je meerdere versies van je software in het wild moet ondersteunen, dan is Git-flow misschien nog steeds net zo geschikt voor je team als het de afgelopen 10 jaar voor mensen is geweest. Lees in dat geval vooral verder.

Tot slot: onthoud altijd dat wondermiddelen niet bestaan. Denk aan je eigen context. Wees niet haatdragend. Beslis voor jezelf.

In dit bericht presenteer ik het ontwikkelmodel dat ik ongeveer een jaar geleden voor een aantal van mijn projecten (zowel op het werk als privé) heb ingevoerd, en dat zeer succesvol is gebleken. Ik wilde er al een tijdje over schrijven, maar ik heb nooit echt de tijd gevonden om dat grondig te doen, tot nu. Ik zal het niet hebben over de details van het project, maar alleen over de branching strategie en het release management.

Waarom git?

Voor een grondige discussie over de voors en tegens van Git in vergelijking met gecentraliseerde broncodecontrolesystemen, zie het web. Er zijn daar genoeg flamewars gaande. Als ontwikkelaar geef ik de voorkeur aan Git boven alle andere tools die er tegenwoordig zijn. Git heeft echt de manier veranderd waarop ontwikkelaars denken over samenvoegen en branchen.Vanuit de klassieke CVS/Subversion wereld waar ik vandaan kom, werd samenvoegen/branching altijd gezien als een beetje eng (“pas op voor samenvoeg conflicten, ze bijten je!”) en iets wat je maar af en toe moest doen.

Maar met Git, zijn deze acties extreem goedkoop en simpel, en ze worden beschouwd als een van de kernonderdelen van je dagelijkse werkproces, echt waar. Bijvoorbeeld, in CVS/Subversion boeken, wordt branchen en samenvoegen eerst besproken in de latere hoofdstukken (voor gevorderde gebruikers), terwijl het in elk Git boek al in hoofdstuk 3 (basis) wordt behandeld.

Als gevolg van de eenvoud en repetitieve aard, zijn branchen en samenvoegen niet langer iets om bang voor te zijn. Versie beheer tools worden verondersteld meer dan wat dan ook te assisteren bij het branchen/mergen.

Genoeg over de tools, laten we naar het ontwikkelmodel gaan. Het model dat ik hier ga presenteren is in essentie niet meer dan een set procedures die ieder teamlid moet volgen om tot een beheerst software-ontwikkelproces te komen.

Gedecentraliseerd maar gecentraliseerd ¶

De repository setup die wij gebruiken en die goed werkt met dit branching model, is die met een centrale “waarheid” repo. Merk op dat deze repo alleen wordt gezien als de centrale repo (omdat Git een DVCS is, bestaat er niet zoiets als een centrale repo op een technisch niveau). We zullen naar deze repo verwijzen als origin, omdat deze naam bekend is bij alle Git gebruikers.

Elke ontwikkelaar pullt en pusht naar de origin. Maar naast de gecentraliseerde push-pull relaties, kan iedere ontwikkelaar ook wijzigingen van andere collega’s naar zich toe trekken om zo subteams te vormen. Dit kan bijvoorbeeld handig zijn om met twee of meer ontwikkelaars samen te werken aan een grote nieuwe functie, voordat het werk in uitvoering naarorigin voortijdig wordt gepusht. In de figuur hierboven zijn er subteams van Alice en Bob,Alice en David, en Clair en David.

Technisch betekent dit niets meer dan dat Alice een Git remote heeft gedefinieerd, genaamd bob, die naar Bob’s repository wijst, en vice versa.

De hoofdtakken ¶

In de kern is het ontwikkelmodel sterk geïnspireerd op bestaande modellen die er al zijn. De centrale repo bevat twee hoofdtakken met een oneindige levensduur:

  • master
  • develop

De master branch op origin zou iedere Git gebruiker bekend moeten voorkomen. Parallel aan de master branch, bestaat er nog een andere branch genaamd develop.

We beschouwen origin/master als de hoofd branch waar de broncode vanHEAD altijd een productieklare staat weergeeft.

Wij beschouwen origin/develop als de hoofd branch waar de broncode vanHEAD altijd een staat weergeeft met de laatst geleverde ontwikkel wijzigingen voor de volgende release. Sommigen zouden dit de “integratie tak” noemen. Dit is waar alle automatische nachtelijke builds van worden gebouwd.

Wanneer de broncode in de develop tak een stabiel punt bereikt en klaar is om te worden vrijgegeven, moeten alle wijzigingen op de een of andere manier worden samengevoegd in master en dan worden voorzien van een vrijgavenummer. Hoe dit in detail wordt gedaan, zal verderop worden besproken.

Daarom is elke keer dat wijzigingen worden samengevoegd in master, dit per definitie een nieuwe productierelease. We hebben de neiging om hier erg strikt in te zijn, zodat we in theorie een Git hook script zouden kunnen gebruiken om onze software automatisch te bouwen en uit te rollen naar onze productie servers, iedere keer als er een commit was opmaster.

Ondersteunende branches ¶

Naast de hoofd branches master en develop, gebruikt ons ontwikkelmodel een variëteit aan ondersteunende branches om parallelle ontwikkeling tussen teamleden te helpen, het volgen van features te vergemakkelijken, productiereleases voor te bereiden en om te helpen bij het snel oplossen van live productie problemen. In tegenstelling tot de hoofd takken, hebben deze takken altijd een beperkte levensduur, omdat ze uiteindelijk verwijderd zullen worden.

De verschillende soorten branches die we kunnen gebruiken zijn:

  • Feature branches
  • Release branches
  • Hotfix branches

Elke van deze branches hebben een specifiek doel en zijn gebonden aan strikte regels wat betreft welke branches hun hoofdbranch mogen zijn en welke branches hun samenvoeg targets moeten zijn. We zullen ze zo doornemen.

Ze branches zijn in geen geval “speciaal” vanuit een technisch perspectief. Debranch types zijn gecategoriseerd door hoe we ze gebruiken. Het zijn natuurlijk gewone oudeGit branches.

Feature branches ¶

Mag aftakken van:developMoet terug fuseren in:developTak naamgevingsconventie: alles behalvemasterdeveloprelease-*, ofhotfix-*

Feature branches (of soms ook wel topic branches genoemd) worden gebruikt om nieuwefeatures te ontwikkelen voor de komende of een verre toekomstige release. Wanneer de ontwikkeling van een functie wordt gestart, is het goed mogelijk dat de doelrelease waarin deze functie zal worden opgenomen op dat moment nog onbekend is. De essentie van een feature branch is dat deze bestaat zolang de feature in ontwikkeling is, maar uiteindelijk weer zal worden samengevoegd in develop (om de nieuwe feature definitief toe te voegen aan de komende release) of zal worden verwijderd (in geval van een teleurstellend experiment).

Feature branches bestaan meestal alleen in developer repos, niet in origin.

Een feature branch maken ¶

Als je begint te werken aan een nieuwe feature, branch dan af van de develop branch.

$ git checkout -b myfeature developSwitched to a new branch "myfeature"

Een voltooide functie opnemen in develop ¶

Voltooide functies kunnen worden samengevoegd in de develop tak om ze definitief toe te voegen aan de komende release:

$ git checkout developSwitched to branch 'develop'$ git merge --no-ff myfeatureUpdating ea1b82a..05e9557(Summary of changes)$ git branch -d myfeatureDeleted branch myfeature (was 05e9557).$ git push origin develop

De --no-ff vlag zorgt ervoor dat de samenvoeging altijd een nieuw commit object aanmaakt, zelfs als de samenvoeging met een fast-forward uitgevoerd zou kunnen worden. Dit voorkomt dat er informatie verloren gaat over het historische bestaan van een eigenschap branch en groepeert alle commits die samen de eigenschap hebben toegevoegd. Vergelijk:

In het laatste geval is het onmogelijk om uit de Git historie te zien welke van de commit objecten samen een feature hebben geïmplementeerd-je zou handmatig alle log berichten moeten lezen. Het terugdraaien van een hele eigenschap (dus een groep commits), is een echte hoofdpijn in de laatste situatie, terwijl het makkelijk gedaan kan worden als de--no-ff vlag gebruikt werd.

Ja, het zal een paar meer (lege) commit objecten creëren, maar de winst is veel groter dan de kosten.

Release branches ¶

Mag aftakken van:developMoet terug fuseren in:developenmasterTak naamgeving conventie:release-*

Release branches ondersteunen de voorbereiding van een nieuwe productie-release. Ze maken het mogelijk om op het laatste moment de puntjes op de i te zetten. Verder maken ze het mogelijk om kleine bug fixes uit te voeren en meta-data voor een release voor te bereiden (versienummer, builddates, etc.). Door al dit werk op een release branch te doen, wordt de developbranch vrijgegeven om features te ontvangen voor de volgende grote release.

Het belangrijkste moment om een nieuwe release branch af te takken van develop is wanneerendevelop (bijna) de gewenste staat van de nieuwe release weergeeft. Tenminste alle features die gericht zijn op de te bouwen release moeten worden samengevoegd indevelop op dit punt in de tijd. Alle features die gericht zijn op toekomstige releases mogen dat niet – die moeten wachten tot nadat de release branch is afgetakt.

Het is precies aan het begin van een release branch dat de aankomende release een versienummer krijgt toegewezen – niet eerder. Tot dat moment geeft de developbranch wijzigingen weer voor de “volgende release”, maar het is onduidelijk of die “volgende release” uiteindelijk 0.3 of 1.0 zal worden, totdat de release branch is gestart. Die beslissing wordt gemaakt bij het starten van de release branch en wordt uitgevoerd door de regels van het project over versienummer bumpen.

Een release branch maken ¶

Release branches worden gemaakt van de develop branch. Bijvoorbeeld, stel dat versie 1.1.5 de huidige productie-release is en we hebben een grote release op komst. De staat van develop is klaar voor de “volgende release” en we hebben besloten dat dit versie 1.2 wordt (in plaats van 1.1.6 of 2.0). Dus webranch af en geef de release branch een naam die het nieuwe versienummer weergeeft:

$ git checkout -b release-1.2 developSwitched to a new branch "release-1.2"$ ./bump-version.sh 1.2Files modified successfully, version bumped to 1.2.$ git commit -a -m "Bumped version number to 1.2" Bumped version number to 1.21 files changed, 1 insertions(+), 1 deletions(-)

Nadat we een nieuwe branch hebben gemaakt en daarnaar zijn overgestapt, veranderen we het versienummer. Hier, bump-version.sh is een fictief shell script dat een aantal bestanden in de werkkopie verandert om de nieuwe versie weer te geven. (Dit kan natuurlijk een handmatige wijziging zijn-het punt is dat sommige bestanden veranderen.) Daarna wordt het aangepaste versienummer gecommit.

Deze nieuwe branch kan daar een tijdje bestaan, totdat de release definitief wordt uitgerold. Gedurende die tijd kunnen bug fixes worden toegepast in deze branch (in plaats van op de develop branch). Grote nieuwe functies hier toevoegen is strikt verboden. Ze moeten worden samengevoegd in develop, en dus wachten op de volgende grote release.

Een release branch afronden ¶

Wanneer de staat van de release branch klaar is om een echte release te worden, moeten er enkele acties worden uitgevoerd. Ten eerste, de vrijgave branch wordt samengevoegd inmaster (omdat elke commit op master per definitie een nieuwe release is, weet je nog). Vervolgens moet die commit op master getagged worden om in de toekomst gemakkelijk naar deze historische versie te kunnen verwijzen. Tenslotte moeten de wijzigingen die op de releasebranch zijn gemaakt weer in develop worden samengevoegd, zodat toekomstige releases ook deze bug fixes bevatten.

De eerste twee stappen in Git:

$ git checkout masterSwitched to branch 'master'$ git merge --no-ff release-1.2Merge made by recursive.(Summary of changes)$ git tag -a 1.2

De release is nu klaar, en getagged voor toekomstige referentie.

Edit: Je kunt net zo goed de -s of -u <key>-vlaggen gebruiken om je tag cryptografisch te ondertekenen.

Om de wijzigingen die in de release branch zijn gemaakt te behouden, moeten we die echter weer samenvoegen in develop. In Git:

$ git checkout developSwitched to branch 'develop'$ git merge --no-ff release-1.2Merge made by recursive.(Summary of changes)

Deze stap zou wel eens tot een samenvoeg conflict kunnen leiden (waarschijnlijk zelfs, aangezien we het versienummer veranderd hebben). Als dat zo is, repareer het dan en commit.

Nu zijn we echt klaar en kan de release branch verwijderd worden, omdat we die niet meer nodig hebben:

$ git branch -d release-1.2Deleted branch release-1.2 (was ff452fe).

Hotfix branches ¶

Mag aftakken van:masterMoet terug fuseren in:developenmasterTak-naamgevingsconventie:hotfix-*

Hotfix branches lijken erg op release branches in zoverre dat ze ook bedoeld zijn om een nieuwe productie-release voor te bereiden, zij het ongepland. Ze komen voort uit de noodzaak om onmiddellijk te reageren op een ongewenste toestand van een live productieversie. Als een kritieke bug in een productieversie onmiddellijk moet worden opgelost, kan een hotfix branch worden afgetakt van de overeenkomstige tag op de master branch die de productieversie markeert.

De essentie is dat het werk van teamleden (op de develop branch) door kan gaan, terwijl een ander een snelle productie fix voorbereidt.

Creëren van de hotfix branch ¶

Hotfix branches worden gecreëerd vanuit de master branch. Bijvoorbeeld, stel dat versie 1.2 de huidige productie-release is die live draait en problemen veroorzaakt door een ernstige bug. Maar veranderingen op develop zijn nog instabiel. We kunnen dan aftakken van een hotfix branch en het probleem gaan oplossen:

$ git checkout -b hotfix-1.2.1 masterSwitched to a new branch "hotfix-1.2.1"$ ./bump-version.sh 1.2.1Files modified successfully, version bumped to 1.2.1.$ git commit -a -m "Bumped version number to 1.2.1" Bumped version number to 1.2.11 files changed, 1 insertions(+), 1 deletions(-)

Vergeet niet het versienummer te verhogen na het aftakken!

Dan, repareer de bug en commit de fix in een of meer aparte commits.

$ git commit -m "Fixed severe production problem" Fixed severe production problem5 files changed, 32 insertions(+), 17 deletions(-)

Een hotfix branch afronden ¶

Als je klaar bent, moet de bugfix weer worden samengevoegd in master, maar moet ook weer worden samengevoegd in develop, om er zeker van te zijn dat de bugfix ook in de volgende release wordt opgenomen. Dit is geheel gelijk aan hoe releasebranches worden afgewerkt.

Werk eerst master bij en tag de release.

$ git checkout masterSwitched to branch 'master'$ git merge --no-ff hotfix-1.2.1Merge made by recursive.(Summary of changes)$ git tag -a 1.2.1

Edit: Je zou net zo goed de -s of -u <key> vlaggen kunnen gebruiken om je tag cryptografisch te ondertekenen.

Volgende, neem de bugfix ook op in develop:

$ git checkout developSwitched to branch 'develop'$ git merge --no-ff hotfix-1.2.1Merge made by recursive.(Summary of changes)

De enige uitzondering op de regel hier is dat, als er al een release branch bestaat, de hotfix wijzigingen moeten worden samengevoegd in die release branch, in plaats van develop. Back-mergen van de bugfix in de release branch zal uiteindelijk resulteren in het ook mergen van de bugfix in develop, wanneer de release branch klaar is. (Als werk in develop onmiddellijk deze bugfix vereist en niet kan wachten tot de vrijgave branch klaar is, kunt u veilig de bugfix in develop nu ook al samenvoegen.)

Eindig, verwijder de tijdelijke branch:

$ git branch -d hotfix-1.2.1Deleted branch hotfix-1.2.1 (was abbe5d6).

Samenvatting ¶

Hoewel er niets echt schokkends nieuws is aan dit branching model, is de “bigpicture” figuur waar deze post mee begon enorm bruikbaar gebleken in onze projecten. Het vormt een elegant mentaal model dat gemakkelijk te begrijpen is en teamleden in staat stelt een gedeeld begrip te ontwikkelen van de processen van vertakking en vrijgave.

Een PDF-versie van hoge kwaliteit van de figuur is hier beschikbaar.

Update: En voor iedereen die erom vroeg: hier is het gitflow-model.src.key van de afbeelding van het hoofddiagram (Apple Keynote).


Git-branching-model.pdf

Andere berichten op deze blog

  • Git power tools voor dagelijks gebruik
  • Een intro tot decoders
  • Technische schuld is echte schuld
  • Mooie code
  • Mooie kaart

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *