Kapitel ▾ 2. Auflage

7.9 Git-Tools - Rerere

Rerere

Die git rerere Funktionalität ist ein wenig versteckt. Der Name steht für „reuse recorded resolution“ (wiederverwendete aufgezeichnete Auflösung) und, wie der Name schon sagt, erlaubt sie Ihnen, Git anzuweisen, sich zu merken, wie Sie einen Hunk-Konflikt gelöst haben, damit Git ihn beim nächsten Mal, wenn derselbe Konflikt auftritt, automatisch für Sie lösen kann.

Es gibt eine Reihe von Szenarien, in denen diese Funktionalität sehr nützlich sein kann. Eines der Beispiele, das in der Dokumentation erwähnt wird, ist, wenn Sie sicherstellen möchten, dass ein langlebiger Topic-Branch letztendlich sauber zusammengeführt werden kann, Sie aber nicht möchten, dass viele Zwischen-Merge-Commits Ihre Commit-Historie unübersichtlich machen. Mit aktiviertem rerere können Sie gelegentliche Merges versuchen, die Konflikte lösen und sich dann vom Merge zurückziehen. Wenn Sie dies kontinuierlich tun, sollte der finale Merge einfach sein, da rerere alles automatisch für Sie erledigen kann.

Diese gleiche Taktik kann angewendet werden, wenn Sie einen Branch rebased halten möchten, damit Sie sich nicht jedes Mal mit denselben Rebase-Konflikten auseinandersetzen müssen. Oder wenn Sie einen Branch, den Sie zusammengeführt und bei dem Sie eine Reihe von Konflikten behoben haben, dann doch lieber rebasen möchten – wahrscheinlich müssen Sie nicht alle dieselben Konflikte erneut lösen.

Eine weitere Anwendung von rerere ist, wenn Sie eine Reihe von sich entwickelnden Topic-Branches gelegentlich zu einem testbaren Head zusammenführen, so wie es das Git-Projekt selbst oft tut. Wenn die Tests fehlschlagen, können Sie die Merges zurückrollen und sie neu durchführen, ohne den Topic-Branch, der die Tests fehlschlagen ließ, und ohne die Konflikte erneut lösen zu müssen.

Um die rerere Funktionalität zu aktivieren, müssen Sie lediglich diesen Konfigurationsbefehl ausführen

$ git config --global rerere.enabled true

Sie können es auch aktivieren, indem Sie das Verzeichnis .git/rr-cache in einem bestimmten Repository erstellen, aber die Konfigurationseinstellung ist klarer und aktiviert diese Funktion global für Sie.

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

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

In einem Branch ändern wir das Wort „hello“ in „hola“, dann in einem anderen Branch ändern wir „world“ in „mundo“, genau wie zuvor.

Two branches changing the same part of the same file differently
Abbildung 160. Zwei Branches, die denselben Teil derselben Datei unterschiedlich ändern

Wenn wir die beiden Branches zusammenführen, erhalten 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 sollten die neue Zeile Recorded preimage for FILE darin bemerken. Ansonsten sollte es genau wie ein normaler Merge-Konflikt aussehen. An diesem Punkt kann uns rerere einige Dinge sagen. Normalerweise würden Sie an diesem Punkt git status ausführen, um zu sehen, was alles kollidierte.

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

git rerere wird Ihnen jedoch auch mitteilen, wofür es den Pre-Merge-Zustand aufgezeichnet hat, mit git rerere status.

$ git rerere status
hello.rb

Und git rerere diff zeigt den aktuellen Zustand der Auflösung an – womit Sie begonnen haben zu lösen und wozu Sie es gelöst 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 nichts wirklich mit rerere zu tun) können Sie git ls-files -u verwenden, um die konfliktrelevanten Dateien und die Versionen davor, links und rechts anzuzeigen.

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

Jetzt können Sie es zu nur puts 'hola mundo' auflösen und erneut git rerere diff ausführen, um zu sehen, was rerere sich merken 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 besagt im Grunde, dass Git, wenn es einen Hunk-Konflikt in einer hello.rb-Datei sieht, die auf der einen Seite „hello mundo“ und auf der anderen „hola world“ hat, ihn zu „hola mundo“ auflöst.

Jetzt können wir es 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 "Recorded resolution for FILE" aufgezeichnet hat.

Recorded resolution for FILE
Abbildung 161. Aufgezeichnete Auflösung für FILE

Nun machen wir diesen Merge rückgängig und rebasen ihn stattdessen auf unseren master-Branch. Wir können unseren Branch mit git reset zurücksetzen, wie wir in Reset Demystified gesehen haben.

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

Unser Merge ist rückgängig gemacht. Nun rebasen wir den Topic-Branch.

$ 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

Jetzt haben wir denselben Merge-Konflikt wie erwartet, aber schauen Sie sich die Zeile Resolved FILE using previous resolution an. Wenn wir uns die Datei ansehen, werden wir sehen, dass sie bereits aufgelöst wurde, es gibt keine Merge-Konfliktmarker darin.

#! /usr/bin/env ruby

def hello
  puts 'hola mundo'
end

Außerdem zeigt Ihnen git diff, wie es automatisch wieder aufgelö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
Automatically resolved merge conflict using previous resolution
Abbildung 162. Automatisch aufgelöster Merge-Konflikt unter Verwendung der vorherigen Auflösung

Sie können den konfliktrelevanten Dateizustand 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 Advanced Merging gesehen. Vorerst lösen wir es jedoch erneut auf, indem wir einfach git rerere erneut ausführen.

$ 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 zwischengespeicherten rerere-Auflösung wieder aufgelöst. Sie können jetzt hinzufügen und mit dem Rebase fortfahren, um ihn abzuschließen.

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

Wenn Sie also viele Re-Merges durchführen, einen Topic-Branch ohne viele Merges mit Ihrem master-Branch aktuell halten möchten oder oft rebasen, können Sie rerere aktivieren, um Ihr Leben ein wenig zu erleichtern.