Git
Chapters ▾ 2nd Edition

7.9 Git Tools - Rerere

Rerere

Die Funktion git rerere ist eine eher versteckte Komponente. Der Name steht für „reuse recorded resolution“ (dt. „gespeicherte Ergebnisse wiederverwenden“). Der Name bedeutet, dass Sie Git auffordern können, sich zu erinnern, wie Sie einen bestimmten Konflikt gelöst haben. Wenn Git das nächste Mal den gleichen Konflikt sieht, kann es ihn automatisch für Sie lösen.

Es gibt eine Reihe von Szenarien, in denen diese Funktionalität wirklich nützlich sein kann. Eines der Beispiele, das in der Dokumentation erwähnt wird, ist, sicher zu stellen, dass ein langlebiger Topic-Branch am Ende sauber gemergt wird; aber Sie wollen nicht, dass eine Menge zwischenzeitlicher Merge-Commits Ihre Commit-Historie durcheinander bringen. Wenn rerere aktiviert ist, können Sie ab und zu einen Merge starten, die Konflikte lösen und dann den Merge wieder abbrechen. Falls Sie das kontinuierlich tun, dann sollte der endgültige Merge ganz unkompliziert sein, denn rerere kann einfach alles für Sie automatisch erledigen.

Dieselbe Taktik kann angewendet werden, wenn Sie einen Branch reorganisiert (engl. rebase) halten wollen, damit Sie sich nicht jedes Mal mit denselben Konflikten beim Rebase auseinandersetzen müssen. Auch wenn Sie einen Branch, den Sie schon gemergt und eine Reihe von Konflikten behoben haben; ihn statt zu verwenden, sich für einen Rebase entscheiden – dann müssen Sie wahrscheinlich nicht alle Konflikte erneut lösen.

Eine weitere Einsatzmöglichkeit von `rerere` ist, wenn man eine Reihe von sich fortentwickelnden Topic-Branches gelegentlich zu einem überprüfbaren Head zusammenfügt, so wie es das Git-Projekt oft selbst praktiziert.
Wenn diese Prüfungen fehlschlagen, können Sie die Merges rückgängig machen und sie ohne den fehlerhaften Topic-Branch, erneut starten, ohne die Konflikte erneut auflösen zu müssen.

Um die Funktion rerere zu aktivieren, müssen Sie nur die folgende Config-Einstellung verwenden:

$ git config --global rerere.enabled true

Sie können sie auch einschalten, indem Sie das Verzeichnis .git/rr-cache in einem konkreten Repository erstellen. Die Konfigurationseinstellung ist allerdings eindeutiger und aktiviert diese Funktion global für Sie.

Sehen wir uns nun ein einfaches Beispiel an, das unserem vorherigen ähnlich ist. Nehmen wir an, wir haben eine Datei namens hello.rb, die so aussieht:

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

In der einen Branch ändern wir das Wort „hello“ in „hola“, in der anderen Branch ändern wir die „world“ in „mundo“, wie gehabt.

rerere1

Wenn wir die beiden Branches verschmelzen, bekommen wir einen Merge-Konflikt:

$ 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.

Sie müssen dort die neue Zeile Recorded preimage for FILE beachten. Das Übrige sollte genau wie ein normaler Merge-Konflikt aussehen. An dieser Stelle kann rerere uns ein paar Dinge sagen. Normalerweise könnten Sie an diesem Punkt git status ausführen, um alle Konflikte zu sehen:

$ 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
#

Allerdings wird Ihnen git rerere auch mitteilen, wozu es den Status vor dem Merge (engl. pre-merge state) mit git rerere status aufgezeichnet hat:

$ git rerere status
hello.rb

Ein git rerere diff zeigt den aktuellen Status der Lösung – womit Sie angefangen haben und welche Lösung Sie gefunden haben.

$ 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

Außerdem (und das hat nicht wirklich etwas mit rerere zu tun) können Sie git ls-files -u verwenden, um sich die in Konflikt stehenden Dateien und die vorherigen, verbliebenen und richtigen Versionen anzusehen:

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

Jetzt können Sie auflösen, indem Sie einfach puts 'hola mundo' eingeben. Dann können Sie noch einmal git rerere diff starten, um zu sehen, woran rerere sich erinnern wird:

$ 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

Das heißt im Grunde genommen: wenn Git in einer hello.rb Datei, die „hello mundo“ auf der einen Seite und „hola world“ auf der anderen Seite enthält, einen komplizierten Konflikt erkennt, wird es ihn zu „hola mundo“ auflösen.

Jetzt können wir ihn als gelöst markieren und committen:

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

Sie können sehen, dass es die „Lösung für DATEI gespeichert hat“ (Recorded resolution for FILE).

rerere2

Machen wir jetzt diesen Merge rückgängig und legen ihn stattdessen dann auf unseren Branch master. Wir können unseren Branch zurückversetzen, indem wir git reset anwenden, wie wir es in Reset entzaubert beschrieben haben.

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

Unser Merge ist aufgehoben. Lassen Sie uns jetzt den Topic-Branch 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

Nun haben wir den erwarteten Merge-Konflikt, aber schauen Sie sich die Zeile Resolved FILE using previous resolution an. Wenn wir die Datei betrachten, sehen wir, dass der Konflikt bereits gelöst ist, es gibt keine Marker für den Merge-Konflikt in der Datei.

#! /usr/bin/env ruby

def hello
  puts 'hola mundo'
end

Zudem wird Ihnen git diff zeigen, wie es automatisch erneut gelöst wurde:

$ 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

Sie können den Status der Konfliktdatei auch mit git checkout wiederherstellen:

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

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

Ein Beispiel dafür haben wir in Fortgeschrittenes Merging kennengelernt. Vorerst sollten wir das Problem allerdings dadurch lösen, dass wir git rerere noch einmal starten:

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

def hello
  puts 'hola mundo'
end

Wir haben die Datei automatisch mit der mit rerere zwischengespeicherten Lösung erneut gelöst. Sie können das nun hinzufügen und den Rebase fortsetzen, um ihn fertigzustellen.

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

Wenn Sie also viele Re-Merges machen oder einen Topic-Branch mit Ihrem Branch master aktuell halten wollen, ohne dass eine Unmenge von Merges durchgeführt wird oder wenn Sie häufig einen Rebase machen, sollten Sie rerere aktivieren, um sich das Leben ein wenig leichter zu machen.