This is an in-progress translation.
To help translate the book, please fork the book at GitHub and push your contributions.

Rebasen

In Git zijn er twee hoofdmanieren om wijzigingen te integreren van één branch in een andere: de samenvoeging en de rebase. In dit gedeelte zul je leren wat rebasen is, hoe je het moet doen, waarom het een zeer bijzondere toepassing is, en in welke gevallen je het niet wilt gebruiken.

De eenvoudige rebase

Als je teruggaat naar een eerder voorbeeld van de Samenvoegen sectie (zie Figuur 3-27), dan kun je zien dat je werk is afgeweken en dat je commits hebt gedaan op de twee verschillende branches.


Figuur 3-27. Je initiële afgeweken historie.

De eenvoudigste weg om de branches te integreren, zoals we al hebben besproken, is het samenvoeg commando. Het voert een drieweg samenvoeging uit tussen de twee laatste snapshots van de branches (C3 en C4), en de meest recente gezamenlijke voorouder van die twee (C2), creëert een nieuw snapshot (en commit), zoals getoond in Figuur 3-28.


Figuur 3-28. Een branch samenvoegen om de afgeweken werk historie te integreren.

Maar, er is een andere manier: je kunt de patch van de wijziging die werd geïntroduceerd in C3 pakken en die opnieuw toepassen bovenop C4. In Git, wordt dit rebasen genoemd. Met het rebase commando, kun je alle wijzigingen pakken die zijn gecommit op een branch, en ze opnieuw afspelen op een andere.

In dit voorbeeld, zou je het volgende uitvoeren:

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

Het werkt door naar de gezamenlijke voorouder van de twee branches te gaan (degene waar je op zit en degene waar je naar rebased), de verschillen pakken die geïntroduceerd is bij iedere commit op de branch waar je op zit, die diffs in tijdelijke bestanden bewaren, de huidige branch terugzetten naar dezelfde commit als de branch waar je op rebased, en uiteindelijk iedere wijziging om de beurt toepassen, Figuur 3-29 toont dit proces.


Figuur 3-29. De wijzigingen die geïntroduceerd zijn in C3 rebasen op C4.

Op dit punt kun je terug gaan naar de master branch en een fast-forward samenvoeging doen (zie Figuur 3-30).


Figuur 3-30. De master branch Fast-forwarden.

Nu is het snapshot waar C3’ naar wijst precies hetzelfde als degene waar C5 naar wees in het samenvoeg voorbeeld. Er zit geen verschil in het eindproduct van de integratie, maar rebasen zorgt voor een schonere historie. Als je de log van een gerebasete branch bekijkt, ziet het eruit als een lineaire historie: het lijkt alsof al het werk in serie is gebeurt, zelfs als het in werkelijkheid in parallel gedaan is.

Vaak zul je dit doen om er zeker van te zijn dat je commits netjes aansluiten op een remote branch — misschien in een project waar je op probeert bij te dragen, maar dat je niet onderhoudt. In dit geval zou je je werk in een branch doen en dan je werk rebasen op origin/master als je klaar ben om je patches in te sturen naar het hoofd project. Op die manier hoeft de beheerder geen integratie werk te doen — gewoon een fast-forward of een schone toepassing.

Let op het snapshot waar de laatste commit naar wijst waar je mee eindigt, of het de laatste van de gerebasete commits voor een rebase is, of de laatste samenvoeg commit na een samenvoeging, het is hetzelfde snapshot — alleen de historie is verschillend. Rebasen speelt veranderingen van een werklijn opnieuw af op een andere in de volgorde waarin ze geïntroduceerd waren, en samenvoegen pakt de eindpunten en voegt die samen.

Interessantere rebases

Je kunt je rebase ook opnieuw laten afspelen op iets anders dan de rebase branch. Pak een historie zoals in Figuur 3-31, bijvoorbeeld. Je hebt een onderwerp branch afgesplitst (server) om wat server-kant functionaliteit toe te voegen aan je project, en toen een commit gedaan. Daarna, heb je daar vanaf gebranched om de client-kant wijzigingen te doen (client) en een paar keer gecommit. Als laatste, ben je teruggegaan naar je server branch en hebt nog een paar commits gedaan.


Figuur 3-31. Een historie met een onderwerp branch vanaf een andere onderwerp branch.

Stel dat je beslist dat je je client-kant wijzigingen wilt samenvoegen in je hoofdlijn voor een vrijgave, maar je wilt de server-kant wijzigingen nog laten wachten totdat het verder getest is. Je kunt de wijzigingen van client pakken, die nog niet op server zitten (C8 en C9) en die opnieuw afspelen op je master branch door de --onto optie te gebruiken van git rebase:

$ git rebase --onto master server client

Dit zegt in feite, “Check de client branch uit, vogel de patches van de gezamenlijke voorouder van de client en de server branches uit, en speel die opnieuw af op master.” Het is een beetje complex; maar het resultaat, getoond in Figuur 3-32, is erg vet.


Figuur 3-32. Een onderwerp branch rebasen vanaf een andere onderwerp branch.

Nu kun je een fast-forward doen van je master branch (zie Figuur 3-33):

$ git checkout master
$ git merge client


Figuur 3-33. Je master branch fast-forwarden om de client branch wijzigingen mee te nemen.

Stel dat je beslist om je server branch ook binnen te halen. Je kunt de server branch rebasen op de master branch zonder het eerst te moeten uitchecken door git rebase [basisbranch] [onderwerpbranch] uit te voeren — wat de onderwerp branch uitchecked (in dit geval, server) voor je en het opnieuw afspeelt om de basis branch (master):

$ git rebase master server

Dit speelt je server werk opnieuw af bovenop je master werk, zoals getoond in Figuur 3-34.


Figuur 3-34. Je server branch bovenop je master branch rebasen.

Daarna, kun je de basis branch (master) fast-forwarden:

$ git checkout master
$ git merge server

Je kunt de client en server branches verwijderen, omdat al het werk geïntegreerd is en je ze niet meer nodig hebt, waarbij je historie voor het hele proces er uit ziet zoals Figuur 3-35:

$ git branch -d client
$ git branch -d server


Figuur 3-35. Uiteindelijke commit historie.

De gevaren van rebasen

Ahh, maar de zegen van rebasen is niet zonder nadelen, wat samengevat kan worden in een enkele regel:

Rebase geen commits die je teruggezet hebt naar een publiek repository.

Als je die richtlijn volgt, dan gebeurt je niets. Als je dat niet doet, zullen mensen je haten, en je zult door vrienden en familie uitgehoond worden.

Als je spullen rebaset, laat je bestaande commits achter en maak je nieuwe aan die vergelijkbaar zijn maar anders. Als je commits ergens naartoe zet en andere halen ze binnen en baseren daar werk op, en vervolgens herschrijf je die commits met git rebase en zet ze opnieuw terug, dan zullen je medewerkers hun werk opnieuw moeten samenvoegen en zullen de dingen vervelend worden als je hun werk probeert binnen te halen in het jouwe.

Laten we eens kijken naar een voorbeeld of hoe werk rebasen dat je publiekelijk gemaakt hebt problemen kan veroorzaken. Stel dat je van een centrale server cloned en dan daar wat werk vanaf doet. Je commit historie ziet er uit als Figuur 3-36.


Figuur 3-36. Clone een repository, en baseer wat werk daarop.

Nu, doet iemand anders wat meer werk dat een samenvoeging bevat, en zet dat werk terug naar de centrale server. Je pakt dat en voegt de nieuwe remote branch in jouw werk, zodat je historie er uit ziet zoals Figuur 3-37.


Figuur 3-37. Haal meer commits op, en voeg ze samen in je werk.

Daarna, beslist de persoon die het werk teruggezet heeft om terug te gaan en hun werk in plaats daarvan te rebasen; ze voeren een git push --force uit om de historie op de server te herschrijven. Je haalt dan van die server op, waarbij je de nieuwe commits omlaag haalt.


Figuur 3-38. Iemand zet gerebasete commits terug, daarbij commits achterlatend waar jij werk op gebaseerd hebt.

Op dit punt, moet je dit werk opnieuw samenvoegen, alhoewel je dat al gedaan hebt. Rebasen verandert de SHA-1 hashes van deze commits, dus voor Git zien ze er uit als nieuwe commits, terwijl je in feite al het C4 werk in je historie hebt (zie Figuur 3-39).


Figuur 3-39. Je voegt hetzelfde werk opnieuw samen in een nieuwe samenvoegingscommit.

Je moet dat werk op een bepaald punt samenvoegen, zodat je bij kunt blijven met de andere ontwikkelaar in de toekomst. Nadat je dat doet, zal je history zowel de C4 als de C4’ commits bevatten, die verschillende SHA-1 hashes hebben, maar hetzelfde werk introduceren en hetzelfde commit bericht hebben. Als je een git log uitvoert als je historie er zo uitziet, dan zul je twee commits zien die dezelfde auteur en bericht hebben, wat verwarrend zal zijn. Daarnaast, als je deze historie terugzet naar de server, dan zul je al die gerebasete commits opnieuw introduceren op de centrale server, wat mensen nog meer kan verwarren.

Als je rebasen behandelt als een manier om op te ruimen en met commits werkt voordat je ze terugzet, en als je alleen commits rebaset die nog nooit publiekelijk beschikbaar zijn geweest, dan zal alles in orde zijn. Als je commits rebaset die al publiekelijk teruggezet zijn, en mensen kunnen werk gebaseerd hebben op die commits, dan bereid je maar voor op wat frustrerende problemen.