Konflikte bei der Versionsverwaltung mit Git Git-Merge-Strategien im Detail
Bei der Versionsverwaltung mit Git liefert ein normaler Merge schon genug Konfliktpotenzial. Darüber hinaus gibt es aber natürlich auch noch Sonderfälle, samt verwirrender Namensgebung. Ein Überblick über verschiedene Merge-Möglichkeiten.

Die Einstiegshürden in Git sind relativ hoch, aber wenn die Basics einmal sitzen, geht die grundsätzliche Arbeit – also vor allem Speichern und Hochladen – ziemlich flott von der Hand. Doch dann kommen weitere Mitstreiter, weitere Branches und fast zwangsläufig auch die ersten Konflikte.
Ein normaler, alltäglicher Merge sieht dabei meist in etwa so aus: Auf einem Branch namens etwa „feature-branch-01“ wird ein Feature entwickelt und dann aus dem Hauptzweig heraus in diesen überführt/verschmolzen. Dabei werden Änderungen automatisch akzeptiert, sofern sie nicht im Konflikt zum Bestand stehen. Beispiel: Eine neu angelegte Datei würde schlicht übernommen. Eine Änderung einer bestehenden Zeile einer bestehenden Datei würde hingegen zu einem Konflikt führen, der manuell gelöst werden muss.
Was aber, wenn mehrere Feature- oder sonstige Branches übernommen werden sollen? Was, wenn im Zweifelsfall doch lieber die eigenen Änderungen bevorzugt werden sollen? So etwas in 200 Merge-Konflikten manuell zu beheben, kostet immens Zeit und Nerven. Oder wie sieht es aus, wenn ein Mitarbeiter Mist gebaut, Dutzende Commits plötzlich überflüssig sind, der Branch aber später noch benötigt wird?
Für derlei Fälle bietet Git unterschiedliche Merge-Strategien – einige häufiger, andere seltener sinnvoll. Die Standardstrategie hat sich übrigens vor nicht einmal zwei Jahren geändert, von „recursive“ zu „ort“ – einer gemäß typischem Entwicklerhumor kreierten Abkürzung für „Ostensibly Recursive‘s Twin“. Was also tut dieser angebliche Zwilling?
ort
Der Standard-3-Wege-Merge funktioniert im Konfliktfall ziemlich simpel: Wurde eine Zeile im fremden Branch sowie im aktuellen Branch unterschiedlich editiert, schreibt git beide Varianten in die Datei, markiert sie deutlich als Konflikt und wartet. Nutzer müssen diese Markierungen dann entfernen, sich für den gewünschten Inhalt entscheiden und die Datei dann wieder hinzufügen.
Nun kann es aber sein, dass im fremden Branch andere Regeln beispielsweise für Zeilenenden gelten, etwa andere Umbrüche. Derartige Dinge sollten freilich keine Konflikte verursachen. Git kann solche Whitespace-Problemchen ignorieren:
git merge --strategy=ort --ignore-cr-at-eol feature-branch-01
Die Angabe der ort-Strategie ist im Übrigen überflüssig, da Standard, hier nur zu Verdeutlichung. Die ort-Option „ignore-cr-at-eol“ sorgt dafür, dass cr-Zeilenenden nicht für Konflikte sorgen. Weitere Optionen: ignore-space-change, ignore-all-space, ignore-space-at-eol.
Solche Eigenschaften werden häufig über die Git-Funktion „gitattributes“ festgelegt. Damit lassen sich Pfad-basiert Check-in- und Check-out-Regeln für Dateien im Repo bestimmen. Sofern es hier Unterschiede gibt, lassen sich diese während eines Merge komplett übernehmen beziehungsweise ignorieren:
git merge --strategy=ort --renormalize feature-branch-01
git merge --strategy=ort --no-renormalize feature-branch-01
Soweit zu „belangloseren“ Standards. Spannender: Bisweilen ist beim Merge auch klar, dass bei etwaigen Konflikten immer die eigenen (ours) den fremden (theirs) Änderungen vorzuziehen sind. Und auch das muss nicht etliche Male manuell erledigt werden:
git merge --strategy=ort -Xours feature-branch-01
Und umgekehrt ginge entsprechend …
git merge --strategy=ort -Xtheirs feature-branch-01
…, um jeweils die fremden Änderungen zu übernehmen und eigene zu überschreiben. Dass mit diesen Optionen schnell viel Unheil angerichtet werden kann, sollte klar sein.
Recursive
Der frühere Standard „recursive“ funktioniert fast genauso wie „ort“ und verträgt auch dieselben Optionen. „ort“ ist lediglich eine komplette Neuauflage dieser Strategie, arbeitet intern allerdings präziser und vor allem deutlich schneller. Dennoch verträgt „recursive“ weitere Optionen: Über „diff-algorithm=“ lassen sich andere Diff-Algorithmen wählen und über „no-renames“ lässt sich die Umbenennungserkennung deaktivieren. Die ort-Strategie nutzt hier die Git-Konfiguration und andere Defaults.
Resolve
Die resolve-Strategie arbeitet auf den ersten Blick im Grunde wie auch die vorigen Strategien. Ohne eine langwierige Abhandlung über die intern genutzten Algorithmen zur Konfliktlösung beziehungsweise -bestimmung zu verfassen, lässt sich hier eigentlich nur etwas vereinfachend beschreiben: Wenn ein Commit beim Merge mehrere mögliche Vorgänger hat, über unterschiedliche Branches, würde „recursive“ intern wieder und wieder mergen, bis ein Konflikt zum Lösen übrigbleibt. „resolve“ hingegen würde im Grunde einfach auf gut Glück die (vermeintlich) beste Variante wählen.
Diese Strategie könnte gegebenenfalls für weniger Arbeit sorgen, ist aber abhängig von der jeweiligen Git-Version und schlicht ein wenig mehr „Black Box“. Und ja, „resolve“ ist ein wenig verwirrend, zumal es eher selten explizit genutzt wird und die Suche nach Details extrem schwierig ist – schlicht, weil auch alle anderen Strategien „resolven“ … Hier prallen Eigenname und Standard-Verb aufeinander.
Octopus
Der schöne Name Octopus meint natürlich, dass mehrere Branches verschmolzen werden, nicht bloß zwei:
git merge --strategy=octopus feature-branch-01 feature-branch-02
Gedacht ist diese Variante allerdings nur für das Zusammenführen gänzlich unterschiedlicher Branches – bei Konflikten streikt dieses Prozedere.
Ours
Manchmal möchte man meinen, Git verwirre absichtlich. Die „ours“-Strategie ist etwas völlig anderes als die „ours“-Option der „ort“-Strategie. Ein …
git merge --strategy=ours nutzloser-branch-1
… ignoriert alle Änderungen auf „nutzloser-branch-1“ und behauptet einfach, der Merge sei vollzogen. Dazu mal ein Beispiel, das ein Entwickler hier eingebracht hat: Zwei Kollegen arbeiten (vielleicht unwissentlich) an ein und demselben Feature oder ein Kollege baut auf einem Branch schlicht und ergreifend Mist.
Erfolgte diese Entwicklung auf einem eigens angelegten Branch, würde man diesen einfach löschen oder ignorieren. Wird der Branch aber noch benötigt, ließe sich die unerwünschte Arbeit mit dieser Strategie fix „weg-ignorieren“. Werden dann wieder sinnvolle Änderungen auf diesem Branch durchgeführt, kann er wieder gemergt werden – die ignorierten Änderungen bemerkt der Ziel-Branch dann gar nicht mehr.
Subtree
Die „subtree“-Strategie ist eine „ort“-Variante, bei der der zu mergende Git-Baum in ein Unterverzeichnis geschmolzen werden kann. Auf diese Weise ließe sich beispielsweise eine Bibliothek von Dritten in das eigene Repo bringen. Sinnvoll ist solch ein „Repo im Repo“ vor allem dann, wenn Sie selbst nicht in diese Code-Basis pushen.
Das Prozedere für die initiale Einrichtung ist etwas komplexer. Um es ein wenig simpler zu gestalten, wird hier ein ganz konkretes, existierendes GitHub-Repo (yarfORk) als Subtree eingebunden. Genauer: Zunächst wird das yarfORk-Repo als eigner Branch namens „meinsubtree“ angelegt und dann in ein Unterverzeichnis „meinsubtreedir“ im Master-Branch eingelesen – dann werden Änderungen im yarfORk-Repo gepullt, bevor dann endlich die Subtree-Merge-Strategie ausgeführt wird:
1 git remote add meinsubtree https://github.com/bili123/yarfORk.git
2 git fetch meinsubtree
3 git checkout -b meinsubtree meinsubtree/master
4 git checkout master
5 git read-tree --prefix=meinsubtreedir/ -u meinsubtree
6 git commit -m "subtree-krämpel"
7 git checkout meinsubtree
8 git pull
9 git checkout master
10 git merge --squash -s subtree --no-commit --allow-unrelated-histories meinsubtree
Der „read-tree“-Befehl bugsiert den meinsubtree-Branch in den per „prefix“ angegebenen Unterordner im Master-Branch – mit dem folgenden Commit wird die Einrichtung abgeschlossen.
In den Zeilen 7 bis 9 wird lediglich in den subtree-Branch gewechselt, eine Aktualisierung durchgeführt und wieder zurück in den Master-Branch gewechselt.
Nun sind die Änderungen im Sub-Projekt zwar im zugehörigen Branch vorhanden, nicht aber im Unterverzeichnis im Master-Branch – und genau das erledigt natürlich der Merge-Befehl. Wichtig ist hierbei, per „squash“ zu verhindern, dass die gesamte Historie des Sub-Projekts ebenfalls germergt wird – in der Regel dürfte das zumindest nicht erwünscht sein. Und da Haupt- und Subprojekt keine gemeinsame Historie haben, muss dies über „allow-unrelated-histories“ explizit genehmigt werden.
Wenn Sie von all diesen Merge-Strategien und -Optionen nur eine Sache mitnehmen können, wäre die Empfehlung wohl die X-Option „ours“ für ganz normale ort-Merges – vermutlich die häufigste Arbeitserleichterung. Übrigens kennen Sie diese Variante vermutlich von größeren Linux-Updates: Auch hier wird oft gefragt, wie bestehende (Config-)Dateien gehandhabt werden sollen, wenn es lokale/eigene Änderungen gibt – und vermutlich werden Sie dort etwas wie „Yes to all“ auswählen. Und genau das macht auch „merge -s ort -Xours“.
(ID:49050674)