Git
Chapters ▾ 2nd Edition

7.9 Git Tools - Rerere

Rerere

De functionaliteit van git rerere is een beetje onbekend. De naam staat voor “reuse recorded resolution” (hergebruik opgenomen resoluties/oplossingen) en zoals de naam al aangeeft, stelt het je in staat om Git te vragen om te onthouden hoe je een bepaald deel van een conflict hebt opgelost zodat Git, de volgende keer als hetzelfde conflict ziet deze automatisch voor je kan oplossen.

Er zijn een aantal scenarios waarin deze functionaliteit erg handig zou kunnen zijn. Een van de voorbeelden dat in de documentatie wordt genoemd is dat je ervoor wilt zorgen dat een langlevende topic branch netjes zal mergen maar dat je niet een aantal tussenliggende merge commits hoeft te maken. Met rerere ingeschakeld kan je af en toe mergen, de conflicten oplossen en dan de merge terugdraaien. Als je dit vaker doet, zou de laatste merge eenvoudig moeten zijn omdat rerere alles gewoon automatisch voor je kan doen.

Deze zelfde taktiek kan gebruikt worden als je een branch rebased wilt houden zodat je niet elke keer met dezelfde rebasing conflicten te maken krijgt voor elke keer als je dit doet. Of als je een branch hebt die je hebt gemerged en daar een bergje conflicten hebt opgelost en dan besluit om deze toch maar te rebasen - je zult waarschijnlijk niet dezelfde conflicten willen doorlopen.

Een andere situatie is waar je af en toe een aantal lopende topic branches samen merged in een testbare head, zoals het Git project vaak zelf doet. Als de tests falen, kan je de merges terugdraaien en ze weer doen zonder de topic branch die de tests liet falen zonder de conflicten opnieuw te moeten oplossen.

Om de rerere functionaliteit in te schakelen, kan je eenvoudigweg de volgende configuratie setting aanroepen:

$ git config --global rerere.enabled true

Je kunt het ook inschakelen door de .git/rr-cache directory in een specifieke repository aan te maken, maar de configuratie setting is duidelijker en het kan globaal gedaan worden.

Laten we nu eens een eenvoudig voorbeeld bekijken, vergelijkbaar met de vorige. Laten we zeggen dat we een bestand hebben dat er als volgt uitziet:

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

In de ene branch hebben we het woord “hello” in “hola” gewijzigd, en daarna in de andere branch veranderen we “world” in “mundo”, net zoals eerder.

rerere1

Als we de twee branches mergen, zullen we een merge conflict krijgen:

$ git merge i18n-world
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Recorded preimage for 'hello.rb'
Automatic merge failed; fix conflicts and then commit the result.

Je zult de nieuwe regel Recorded preimage for FILE hier opmerken. Verder zou het er precies als een normale merge conflict uit moeten zien. Op dit moment kan rerere ons een aantal dingen vertellen. Normaalgesproken zou je een git status kunnen aanroepen om te zien waar de conflicten zitten:

$ git status
# On branch master
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add <file>..." to mark resolution)
#
#	both modified:      hello.rb
#

Echter, git rerere zal je ook vertellen wat het de pre-merge status voor heeft opgenomen met git rerere status:

$ git rerere status
hello.rb

En git rerere diff zal ons de huidige staat van de resolutie laten zien - waar je mee begonnen bent met oplossen en waar je het in hebt opgelost.

$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,11 @@
 #! /usr/bin/env ruby

 def hello
-<<<<<<<
-  puts 'hello mundo'
-=======
+<<<<<<< HEAD
   puts 'hola world'
->>>>>>>
+=======
+  puts 'hello mundo'
+>>>>>>> i18n-world
 end

Daarnaast (en dit is eigenlijk niet gerelateerd aan rerere), kan je ls-files -u gebruiken om de conflicterende bestanden en de voor, links en rechts versies te zien:

$ git ls-files -u
100644 39804c942a9c1f2c03dc7c5ebcd7f3e3a6b97519 1	hello.rb
100644 a440db6e8d1fd76ad438a49025a9ad9ce746f581 2	hello.rb
100644 54336ba847c3758ab604876419607e9443848474 3	hello.rb

Je kunt het oplossen zodat het alleen puts 'hola mundo' wordt en je kunt het rerere diff commando nog een keer aanroepen om te zien wat rerere heeft onthouden:

$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,7 @@
 #! /usr/bin/env ruby

 def hello
-<<<<<<<
-  puts 'hello mundo'
-=======
-  puts 'hola world'
->>>>>>>
+  puts 'hola mundo'
 end

Dit zegt eigenlijk dat, wanneer Git een conflict in een deel van een hello.rb bestand ziet dat “hello mundo” aan de ene en “hola world” aan de andere kant heeft staan, het zal oplossen naar “hola mundo”.

Nu kunnen we het als opgelost markeren en committen:

$ git add hello.rb
$ git commit
Recorded resolution for 'hello.rb'.
[master 68e16e5] Merge branch 'i18n'

Je kunt zien aan de boodschap "Recorded resolution for FILE" zien dat het de resolutie voor het bestand heeft opgeslagen.

rerere2

Laten we nu die merge eens ongedaan maken, en in plaats daarvan deze op onze master branch gaan rebasen. We kunnen onze branch terugzetten door reset te gebruiken zoals we zagen in Reset ontrafeld.

$ git reset --hard HEAD^
HEAD is now at ad63f15 i18n the hello

Onze merge is ongedaan gemaakt. Laten we de topic branch gaan rebasen.

$ git checkout i18n-world
Switched to branch 'i18n-world'

$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: i18n one word
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Resolved 'hello.rb' using previous resolution.
Failed to merge in the changes.
Patch failed at 0001 i18n one word

We hebben nu dezelfde merge conflict zoals verwacht, maar kijk eens naar de regel met Resolved FILE using previous resolution. Als we nu het bestand bekijken zullen we zien dat het al is opgelost, er staan geen merge conflict markeringen in.

$ cat hello.rb
#! /usr/bin/env ruby

def hello
  puts 'hola mundo'
end

Ook zal git diff je laten zien hoe het automatisch opnieuw was opgelost:

$ git diff
diff --cc hello.rb
index a440db6,54336ba..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
  #! /usr/bin/env ruby

  def hello
-   puts 'hola world'
 -  puts 'hello mundo'
++  puts 'hola mundo'
  end
rerere3

Je kunt ook de staat van het conflicterende bestand opnieuw creeëren met het checkout commando:

$ git checkout --conflict=merge hello.rb
$ cat hello.rb
#! /usr/bin/env ruby

def hello
<<<<<<< ours
  puts 'hola world'
=======
  puts 'hello mundo'
>>>>>>> theirs
end

We zagen hier eerder een voorbeeld van in Mergen voor gevorderden. Voor nu echter, laten we het opnieuw oplossen door eenvoudigweg weer rerere aan te roepen:

$ git rerere
Resolved 'hello.rb' using previous resolution.
$ cat hello.rb
#! /usr/bin/env ruby

def hello
  puts 'hola mundo'
end

We hebben het bestand automatisch her-opgelost door de opgeslagen rerere resolutie te gebruiken. Je kunt het nu toevoegen en de rebase vervolgen om het te voltooien.

$ git add hello.rb
$ git rebase --continue
Applying: i18n one word

Dus, als je vaak opnieuw merged, of je wilt een topic branch up-to-date houden met je master branch zonder talloze merges, of als je vaak rebased, kan je rerere aanzetten om je leven wat aangenamer te maken.