Immer wenn ich daran denke, was für ein praktisches Werkzeug Git doch ist, setzt es noch einen drauf. Als ob mir git bisect nicht schon genug Arbeit abnehmen würde, bin ich jetzt auch noch über git rerere gestolpert.

Was ist git rerere?

git rerere ist standardmäßig inaktiv und kann mit folgendem Befehl aktiviert werden:

$ git config --global rerere.enabled true

Sobald das getan ist, zeichnet Git auf, wie der Entwickler Zusammenführungs-Konflikte auflöst und verwendet diese Aufzeichnungen ggf. wieder.

Erklärung am Beispiel

Wir beginnen mit einem leeren Repository, das wir mit ein paar Commits füllen:

$ mkdir rerere-demo && cd $_
$ git init
Initialisierte leeres Git-Repository in /home/aklimov/rerere-demo/.git/
$ echo A >Buchstabensalat.txt
$ git add Buchstabensalat.txt
$ git commit -m A
[master (Basis-Commit) 4d1a53f] A
 1 file changed, 1 insertion(+)
 create mode 100644 Buchstabensalat.txt
$ echo B >>Buchstabensalat.txt
$ git commit -am B
[master fe71b2b] B
 1 file changed, 1 insertion(+)
$ git checkout HEAD^
$ echo C >>Buchstabensalat.txt
$ git commit -am C
[losgelöster HEAD 181ad2e] C
 1 file changed, 1 insertion(+)
$ git checkout -

Nun sollten wir so einen Baum vorfinden:

$ git --no-pager log --oneline --graph --decorate=full master 181ad2e
* 181ad2e C
| * fe71b2b (HEAD -> refs/heads/master) B
|/
* 4d1a53f A

Jetzt versuchen wir, die Commits B und C zusammenzuführen, was natürlich erfolgreich fehlschlagen wird. Wir müssen also den Konflikt manuell auflösen:

$ git merge 181ad2e
automatischer Merge von Buchstabensalat.txt
KONFLIKT (Inhalt): Merge-Konflikt in Buchstabensalat.txt
Recorded preimage for 'Buchstabensalat.txt'
Automatischer Merge fehlgeschlagen; beheben Sie die Konflikte und committen Sie dann das Ergebnis.
$ git --no-pager diff
diff --cc Buchstabensalat.txt
index 35d242b,8ec30d8..0000000
--- a/Buchstabensalat.txt
+++ b/Buchstabensalat.txt
@@@ -1,2 -1,2 +1,6 @@@
  A
++<<<<<<< HEAD
 +B
++=======
+ C
++>>>>>>> 181ad2e
$ git --no-pager diff
diff --cc Buchstabensalat.txt
index 35d242b,8ec30d8..0000000
--- a/Buchstabensalat.txt
+++ b/Buchstabensalat.txt
@@@ -1,2 -1,2 +1,3 @@@
  A
 +B
+ C
$ git add Buchstabensalat.txt
$ git commit --no-edit
Recorded resolution for 'Buchstabensalat.txt'.
[master 279715c] Merge commit '181ad2e'
$ git --no-pager log --oneline --graph --decorate=full
*   279715c (HEAD -> refs/heads/master) Merge commit '181ad2e'
|\
| * 181ad2e C
* | fe71b2b B
|/
* 4d1a53f A

Die Wirkung von git rerere macht sich bereits bei den rot hervorgehobenen Zeilen bemerkbar. An diesen Stellen wird aufgezeichnet, wie die Konflikt-Datei vorher bzw. nachher ausgesehen hat. Um zu sehen, was uns das bringt, verwerfen wir den Merge-Commit und führen erneut zusammen:

$ git reset --hard HEAD^
HEAD ist jetzt bei fe71b2b B
$ git --no-pager log --oneline --graph --decorate=full master 181ad2e
* 181ad2e C
| * fe71b2b (HEAD -> refs/heads/master) B
|/
* 4d1a53f A
$ git merge 181ad2e
automatischer Merge von Buchstabensalat.txt
KONFLIKT (Inhalt): Merge-Konflikt in Buchstabensalat.txt
Resolved 'Buchstabensalat.txt' using previous resolution.
Automatischer Merge fehlgeschlagen; beheben Sie die Konflikte und committen Sie dann das Ergebnis.
$ git --no-pager diff
diff --cc Buchstabensalat.txt
index 35d242b,8ec30d8..0000000
--- a/Buchstabensalat.txt
+++ b/Buchstabensalat.txt
@@@ -1,2 -1,2 +1,3 @@@
  A
 +B
+ C
$ git add Buchstabensalat.txt
$ git commit --no-edit
[master cb5c96b] Merge commit '181ad2e'
$ git --no-pager log --oneline --graph --decorate=full
*   cb5c96b (HEAD -> refs/heads/master) Merge commit '181ad2e'
|\
| * 181ad2e C
* | fe71b2b B
|/
* 4d1a53f A

Wie wir sehen können, hat Git, unmittelbar nachdem es einen Konflikt entdeckt hat, diesen automatisch mit Hilfe der vorher gespeicherten Aufzeichnung aufgelöst.

Da geht noch was

Selbstverständlich ist die Funktionalität von git rerere nicht auf so einfache Fälle beschränkt. Nehmen wir einmal an, dass wir nicht alleine an diesem Repository arbeiten und ein Kollege bereits etwas in den master-Zweig hochgeladen hat:

$ git checkout fe71b2b
$ vi Buchstabensalat.txt
$ git commit -am D
[losgelöster HEAD 779312b] D
 1 file changed, 1 insertion(+)
$ git checkout -
$ git --no-pager log --oneline --graph --decorate=full master 779312b
* 779312b D
| *   cb5c96b (HEAD -> refs/heads/master) Merge commit '181ad2e'
| |\
|/ /
| * 181ad2e C
* | fe71b2b B
|/
* 4d1a53f A

Wir müssen also unseren Merge-Commit über D legen, bevor wir pushen können:

$ git rebase --preserve-merges 779312b
Resolved 'Buchstabensalat.txt' using previous resolution.
automatischer Merge von Buchstabensalat.txt
KONFLIKT (Inhalt): Merge-Konflikt in Buchstabensalat.txt
Automatischer Merge fehlgeschlagen; beheben Sie die Konflikte und committen Sie dann das Ergebnis.
Error redoing merge cb5c96bcb52306dfbdcc8c728c6d356efa3ed9da
$ git --no-pager diff
diff --cc Buchstabensalat.txt
index f5a7bda,8ec30d8..0000000
--- a/Buchstabensalat.txt
+++ b/Buchstabensalat.txt
@@@ -1,3 -1,2 +1,4 @@@
 +D
  A
 +B
+ C
$ git add Buchstabensalat.txt
$ git rebase --continue
[losgelöster HEAD 5096395] Merge commit '181ad2e'
Successfully rebased and updated refs/heads/master.
$ git --no-pager log --oneline --graph --decorate=full
*   5096395 (HEAD -> refs/heads/master) Merge commit '181ad2e'
|\
| * 181ad2e C
* | 779312b D
* | fe71b2b B
|/
* 4d1a53f A

Fazit

Ich kann nicht für alle erdenklichen Teams sprechen, aber wir bei Icinga Web 2 werden uns jedenfalls nicht mehr absprechen müssen, wer gerade Konflikte auflöst und wer pushen darf. 🙂

Alexander Klimov

Autor: Alexander Klimov

Alexander hat 2017 seine Ausbildung zum Developer bei NETWAYS erfolgreich abgeschlossen. Als leidenschaftlicher Programmierer und begeisterter Anhänger der Idee freier Software, hat er sich dabei innerhalb kürzester Zeit in die Herzen seiner Kollegen im Development geschlichen. Wäre nicht ausgerechnet Gandhi sein Vorbild, würde er von dort aus daran arbeiten, seinen geheimen Plan, erst die Abteilung und dann die Weltherrschaft an sich zu reißen, zu realisieren - tut er aber nicht. Stattdessen beschreitet er mit der Arbeit an Icinga Web 2 bei uns friedliche Wege.