Git --fast-version-control
Chapters ▾ 2nd Edition

3.2 Branchen in Git - Eenvoudig branchen en mergen

Eenvoudig branchen en mergen

Laten we eens door een eenvoudig voorbeeld van branchen en mergen stappen met een workflow die je zou kunnen gebruiken in de echte wereld. Je zult deze stappen volgen:

  1. Werken aan een website.

  2. Een branch aanmaken voor een nieuw verhaal waar je aan werkt.

  3. Wat werk doen in die branch.

Dan ontvang je een telefoontje dat je een ander probleem direct moet repareren; je moet een snelle reparatie (hotfix) maken. Je zult het volgende doen:

  1. Switchen naar je productie-branch.

  2. Een branch aanmaken om de hotfix toe te voegen.

  3. Nadat het getest is de hotfix-branch mergen, en dat naar productie pushen.

  4. Terugswitchen naar je originele verhaal en doorgaan met werken.

Eenvoudig branchen

Als eerste, laten we zeggen dat je aan je project werkt en al een paar commits hebt gemaakt.

Een eenvoudige commit historie.
Een eenvoudige commit historie

Je hebt besloten dat je gaat werken aan probleem #53 in wat voor systeem je bedrijf ook gebruikt om problemen te registreren. Om een branch aan te maken en er meteen naartoe te schakelen, kun je het git checkout commando uitvoeren met de -b optie:

$ git checkout -b iss53
Switched to a new branch "iss53"

Dit is een afkorting voor:

$ git branch iss53
$ git checkout iss53
Een nieuwe branch pointer maken.
Een nieuwe branch pointer maken

Je doet wat werk aan je website en doet wat commits. Door dat te doen beweegt de iss53 branch vooruit, omdat je het uitgecheckt hebt (dat wil zeggen, je HEAD wijst ernaar):

$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'
De iss53 branch is naar voren verplaatst met je werk.
De iss53 branch is naar voren verplaatst met je werk

Nu krijg je het telefoontje dat er een probleem is met de website, en je moet het meteen repareren. Met Git hoef je de reparatie niet tegelijk uit te leveren met de iss53 wijzigingen die je gemaakt hebt, en je hoeft ook niet veel moeite te doen om die wijzigingen terug te draaien voordat je kunt werken aan het toepassen van je reparatie in productie. Het enige wat je moet doen is terugschakelen naar je master-branch.

Maar voordat je dat doet, weet dat als je werk-directory of staging area wijzigingen bevatten die nog niet gecommit zijn en conflicteren met de branch die je gaat uitchecken, Git je niet laat omschakelen. Het beste is om een schone werkstatus te hebben als je tussen branches gaat schakelen. Er zijn manieren om hier mee om te gaan (te weten, stashen en commit ammending) die we later gaan behandelen in “Stashen en opschonen”. Voor nu laten we aannemen dat je alle wijzigingen gecommit hebt, zodat je kunt switchen naar je master-branch:

$ git checkout master
Switched to branch 'master'

Hierna is je project-werk-directory precies zoals het was voordat je begon te werken aan probleem #53, en je kunt je concentreren op je hotfix. Dit is een belangrijk punt om te onthouden: Git herstelt je werk-directory zodanig dat deze eruit ziet als het snapshot van de commit waar de branch die je uitcheckt naar wijst. Het voegt automatisch bestanden toe, verwijdert en wijzigt ze om er zeker van te zijn dat je werkkopie eruit ziet zoals de branch eruit zag toen je er voor het laatst op committe.

Vervolgens heb je een hotfix te doen. Laten we een hotfix-branch maken om op te werken totdat het af is:

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix 1fb7853] fixed the broken email address
 1 file changed, 2 insertions(+)
Hotfix branch gebaseerd op `master`.
Hotfix branch gebaseerd op master

Je kunt je tests draaien, jezelf ervan verzekeren dat de hotfix is wat je wilt, en het mergen in je master-branch en het naar productie uitrollen. Je doet dit met het git merge commando:

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

Je zult de uitdrukking “Fast forward” zien in die merge. Omdat de commit van de branch waar je mee mergede direct stroomopwaarts is van de commit waar je op zit, zal Git de pointer vooruit verplaatsen. Om het op een andere manier te zeggen: als je een commit probeert te mergen met een commit die bereikt kan worden door de historie van eerste commit te volgen, zal Git de dingen vereenvoudigen door de verwijzing vooruit te verplaatsen omdat er geen afwijkend werk is om te mergen; dit wordt een “fast forward” genoemd.

Je wijziging zit nu in de snapshot van de commit waar de master branch naar wijst, en je kunt je wijziging uitrollen.

`master` is fast-forwarded naar `hotfix`.
master is fast-forwarded naar hotfix

Nadat je super-belangrijke reparatie uitgerold is, ben je klaar om terug te schakelen naar het werk dat je deed voordat je onderbroken werd. Maar, eerst ga je de hotfix branch verwijderen, omdat je die niet langer nodig hebt - de master branch wijst naar dezelfde plek. Je kunt het verwijderen met de -d optie op git branch:

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

Nu kun je switchen naar je werk-in-uitvoering-branch voor probleem #53 en doorgaan met daaraan te werken.

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)
Werk gaat door op `iss53`.
Werk gaat door op iss53

Het is nuttig om hier op te merken dat het werk dat je in de hotfix-branch gedaan hebt, niet in de bestanden van je iss53 branch zit. Als je dat binnen moet halen, dan kun je de master-branch in de iss53-branch mergen door git merge master uit te voeren, of je kunt wachten met die wijzigingen te integreren tot het moment dat je het besluit neemt de iss53-branch in de master te trekken.

Eenvoudig mergen (samenvoegen)

Stel dat je besloten hebt dat je probleem #53 werk gereed is en klaar bent om het te mergen in de master-branch. Om dat te doen, zul je de iss53-branch mergen zoals je die hotfix-branch eerder hebt gemerged. Het enige dat je hoeft te doen is de branch uit te checken waar je in wenst te mergen en dan het git merge commando uit te voeren:

$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

Dit ziet er iets anders uit dan de hotfix merge die je eerder gedaan hebt. In dit geval is je ontwikkelhistorie afgeweken van een eerder punt. Omdat de commit op de branch waar je op zit geen directe voorouder is van de branch waar je in merget, moet Git wat werk doen. In dit geval doet Git een eenvoudige drieweg-merge, gebruikmakend van de twee snapshots waarnaar gewezen wordt door de uiteinden van de branch en de gezamenlijke voorouder van die twee.

Drie snapshots gebruikt in een typische merge.
Drie snapshots gebruikt in een typische merge

In plaats van de branch-pointer alleen maar vooruit te verplaatsen, maakt Git een nieuw snapshot dat het resultaat is van deze drieweg-merge en maakt automatisch een nieuwe commit die daarnaar wijst. Dit wordt een merge-commit genoemd, en is bijzonder in die zin dat het meer dan één ouder heeft.

Een merge commit.
Een merge commit

Het is belangrijk om erop te wijzen dat Git de meest geschikte gezamenlijke voorouder bepaalt om te gebruiken als merge basis; dit is anders dan CVS of Subversion (voor versie 1.5), waar de ontwikkelaar die de merge doet ook degene is die de beste merge-basis moest uitzoeken. Dit maakt het mergen in Git een behoorlijk stuk eenvoudiger in vergelijking met deze andere systemen.

Nu dat je werk gemerged is, is er geen verdere noodzaak meer voor de iss53-branch. Je kunt deze verwijderen en daarna handmatig de ticket in het ticket-volgsysteem sluiten:

$ git branch -d iss53

Eenvoudige merge conflicten

Af en toe verloopt dit proces niet zo soepel. Als je hetzelfde gedeelte van hetzelfde bestand op een andere manier hebt gewijzigd in twee branches die je merget, dan zal Git niet in staat zijn om ze netjes te mergen. Als je reparatie voor probleem #53 hetzelfde gedeelte van een bestand heeft gewijzigd als de hotfix, dan krijg je een merge conflict dat er ongeveer zo uit ziet:

$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

Git heeft niet automatisch een nieuwe merge-commit gemaakt. Het heeft het proces gepauzeerd zolang jij het conflict aan het oplossen bent. Als je wilt zien welke bestanden nog niet zijn gemerged op enig punt na een merge conflict, dan kun je git status uitvoeren:

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

Alles wat merge-conflicten heeft en wat nog niet is opgelost wordt getoond als unmerged. Git voegt standaard conflict-oplossingsmarkeringen toe aan de bestanden die conflicten hebben, zodat je ze handmatig kunt openen en die conflicten kunt oplossen. Je bestand bevat een sectie die er zo ongeveer uit ziet:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

Dit betekent dat de versie in HEAD (jouw master-branch, omdat dat degene was die je uitgecheckt had toen je het merge-commando uitvoerde) is het bovenste gedeelte van dat blok (alles boven de ======), terwijl de versie in je iss53-branch eruit ziet zoals alles in het onderste gedeelte. Om het conflict op te lossen, moet je één van de twee gedeeltes kiezen of de inhoud zelf mergen. Je zou bijvoorbeeld dit conflict op kunnen lossen door het hele blok met dit te vervangen:

<div id="footer">
please contact us at email.support@github.com
</div>

Deze oplossing bevat een stukje uit beide secties, en de <<<<<<<, =======, en >>>>>>> regels zijn volledig verwijderd. Nadat je elk van deze secties opgelost hebt in elk conflicterend bestand, voer dan git add uit voor elk van die bestanden om het als opgelost te markeren. Het bestand stagen markeert het als opgelost in Git.

Als je een grafische applicatie wil gebruiken om deze problemen op te lossen, kun je git mergetool uitvoeren, wat een toepasselijk grafische merge-applicatie opstart dat je door de conflicten heen leidt:

$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

Als je een andere dan de standaard merge-tool wilt gebruiken (Git koos opendiff in dit geval, omdat het commando uitgevoerd werd op een Mac), dan kun je alle ondersteunde applicaties opgenoemd zien na “one of the following tools.” Type de naam van de applicatie die je liever gebruikt.

Als je een meer geavanceerde applicatie wilt gebruiken om ingewikkelde merge conflicten op te lossen: we behandelen merging uitgebreider in “Mergen voor gevorderden”.

Nadat je de merge applicatie afsluit, vraagt Git je of de merge succesvol was. Als je het script vertelt dat dit het geval is, dan staget dit script het bestand voor je om het als opgelost te markeren. Je kunt git status nogmaals uitvoeren om je ervan te verzekeren dat alle conflicten opgelost zijn:

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

Als je het daar mee eens bent, en je gecontroleerd hebt dat alles waar conflicten in zaten gestaged is, dan kun je git commit typen om de merge-commit af te ronden. Het commit-bericht ziet er standaard ongeveer zo uit:

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#	.git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#	modified:   index.html
#

Je kunt dat bericht aanpassen met details over hoe je het conflict opgelost hebt, als je denkt dat dat behulpzaam zal zijn voor anderen die in de toekomst naar deze merge kijken - waarom je hebt gedaan wat je gedaan hebt, als dat niet vanzelfsprekend is.