Git
Chapters ▾ 2nd Edition

7.9 Git Tools - Rerere

Rerere

Der Befehl git rerere ist eine eher versteckte Funktion. Der Name steht für „reuse recorded resolution“ (dt. „gespeicherte Ergebnisse wiederverwenden“). Der Name bedeutet, dass du Git auffordern kannst sich zu erinnern, wie du einen bestimmten Konflikt in der Vergangenheit gelöst hast. Wenn Git das nächste Mal den gleichen Konflikt sieht, kann es ihn automatisch 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 Feature-Branch am Ende sauber gemerged wird. Dabei willst du jedoch nicht, dass eine Menge zwischenzeitlicher Merge-Commits deine Commit-Historie durcheinander bringt. Wenn rerere aktiviert ist, kannst du ab und zu einen Merge starten, die Konflikte lösen und dann den Merge-Prozess stoppen. Falls du das kontinuierlich tust, sollte der finale Merge recht unkompliziert sein, denn rerere kann alles für dich automatisch erledigen.

Dieselbe Vorgehensweise kann angewendet werden, wenn du einen Branch rebased, damit du dich nicht jedes Mal mit denselben Konflikten beim Rebase auseinandersetzen musst. Oder wenn du einen Branch, den du schon gemerged und eine Reihe von Konflikten behoben hast dich dann jedoch ein Rebase entscheidest. Dann musst du wahrscheinlich nicht alle Konflikte nochmal lösen.

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

Um die Funktion rerere zu aktivieren, musst du nur die folgende Config-Einstellung verwenden:

$ git config --global rerere.enabled true

Du kannst sie auch einschalten, indem du das Verzeichnis .git/rr-cache in einem konkreten Repository erstellst. Die Konfigurationseinstellung ist allerdings eindeutiger und aktiviert diese Funktion global.

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 dem einen Branch ändern wir das Wort „hello“ in „hola“, in dem anderen Branch ändern wir „world“ in „mundo“, wie gehabt.

Zwei Branches ändern die selbe Stelle unterschiedlich
Abbildung 160. Zwei Branches ändern die selbe Stelle unterschiedlich

Wenn wir beiden Branches mergen, 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.

Beachte die neue Zeile Recorded preimage for FILE. Der Rest sollte genauso wie bei ein normaler Merge-Konflikt aussehen. An dieser Stelle kann rerere uns ein paar Dinge sagen. Normalerweise kannst du 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 dir git rerere auch mitteilen, was es im Pre-Merge Status mit git rerere status aufgezeichnet hat:

$ git rerere status
hello.rb

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

$ 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) kannst du git ls-files -u verwenden, um dir die in Konflikt stehenden Dateien anzusehen (inklusive der vorherigen, linken und rechten Version):

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

Jetzt kannst du es einfach zu puts 'hola mundo' auflösen. Dann kannst du 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 Konflikt erkennt und ihn zu „hola mundo“ auflöst.

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'

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

Aufgezeichnete Auflösung für FILE
Abbildung 161. Aufgezeichnete Auflösung für FILE

Machen wir jetzt diesen Merge rückgängig und legen ihn stattdessen dann auf unseren Branch master. Wir können unseren Branch zurücksetzen, 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 wurde rückgängig gemacht. Lass uns jetzt den Feature-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 schaue dir 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 dir git diff zeigen, wie es erneut automatisch 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
Automatisch aufgelöster merge Konflikt
Abbildung 162. Automatisch aufgelöster merge Konflikt, der eine vorherige Auflösung nutzt

Du kannst 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 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. Du kannst es nun hinzufügen und den Rebase fortsetzen, um ihn fertigzustellen.

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

Wenn du also viele Re-Merges machst oder einen Topic-Branch mit deinem Branch master aktuell halten willst, ohne dass eine Unmenge von Merges durchgeführt werden sollen. Oder wenn du häufig einen Rebase machst, solltest du rerere aktivieren, um dir das Leben ein wenig leichter zu machen.

scroll-to-top