Nie wieder Merge-Konflikte im Changelog
Wer schon einmal im Team an einem Projekt gearbeitet hat, kennt das Problem:
Hat ein Kollege einen richtig guten Tag, schließt er gleich fünf Merge Requests ab.
Jeder davon bringt, wie es sich gehört, einen Eintrag im CHANGELOG.md mit – am besten nach Keep a Changelog.
Und im Zweifel landet jeder dieser Einträge unter derselben Überschrift.
Sobald der erste Merge Request gemergt ist, stehen die anderen vier vor einem Konflikt.
Dabei gibt es hier überhaupt nichts zu entscheiden. Beide Einträge sollen rein und selbst die Reihenfolge hat hier keine Bedeutung. Trotzdem müssen die Konfliktmarkierungen jedes Mal von Hand entfernt werden.
Die Lösung ist eine einzige Zeile.
Wie der Konflikt entsteht
Nach Keep a Changelog landen neue Einträge im Abschnitt ## [Unreleased], und zwar meist ganz am Ende einer Gruppe:
## [Unreleased]
### Added
- Dark mode.
Follows the system setting and can be switched manually in the profile.
Branch A hängt darunter einen Eintrag zum CSV-Export an, Branch B einen zur Volltextsuche. Git jedoch vergleicht Zeilenblöcke. Es sieht, dass beide Seiten an derselben Stelle etwas eingefügt haben, und kann nicht wissen, ob die Reihenfolge eine Rolle spielt oder ob überhaupt nur eine der beiden Zeilen übernommen werden soll. Also stellt es dir diese Frage:
- Dark mode.
Follows the system setting and can be switched manually in the profile.
<<<<<<< HEAD
- Export search results as CSV.
The new button downloads the current list, including the active filters.
=======
- Full-text search.
The search field is available in the header on every page.
>>>>>>> feature/search
Gelöst wird das, indem du beide Einträge stehen lässt und die Markierungen löschst. Das ist keine Denkarbeit, sondern reine Routine und die lässt sich zum Glück automatisieren.
Der Union-Merge-Treiber
Git hat für genau diesen Fall einen eingebauten Merge-Treiber namens union.
Statt Konfliktmarkierungen zu schreiben, übernimmt er die Zeilen beider Seiten.
Aktiviert wird er über die Datei .gitattributes im Wurzelverzeichnis des Repositorys:
CHANGELOG.md merge=union
Das war’s. Die Datei gehört, wie jede andere, ins Repository, damit sie für das ganze Team gilt.
Ich habe es mit dem Beispiel von oben nachgestellt. Beide Branches ergänzen einen Eintrag, und der Merge läuft ohne Rückfrage durch:
### Added
- Dark mode.
Follows the system setting and can be switched manually in the profile.
- Export search results as CSV.
The new button downloads the current list, including the active filters.
- Full-text search.
The search field is available in the header on every page.
Keine Markierungen, kein manuelles Aufräumen. Auch Einträge, die über mehrere Zeilen gehen, bleiben als Block zusammen und werden nicht ineinander verschachtelt.
Ob das Attribut greift, prüfst du mit:
git check-attr merge CHANGELOG.md
# CHANGELOG.md: merge: union
Das Muster CHANGELOG.md ohne Schrägstrich passt übrigens auf jede Datei dieses Namens in jedem Unterverzeichnis.
Das ist praktisch in Monorepos, in dem jedes Projekt sein eigenes Changelog führt.
Brauchst du es nur für eine spzeielle Datei, schreibst du den Pfad aus, zum Beispiel /CHANGELOG.md merge=union.
Der Treiber wirkt nicht nur bei git merge, sondern bei allem, was intern auf einen Dreiwege-Merge zurückgreift, also auch bei git rebase, git cherry-pick und git stash pop.
Wo die Grenzen liegen
Der Union-Treiber ist bewusst simpel. Er kennt weder Markdown noch die Struktur eines Changelogs, sondern fügt Zeilen zusammen. Das hat ein paar Folgen, die du kennen solltest.
Die Reihenfolge ist nicht garantiert sinnvoll
Bei zwei Einträgen an derselben Stelle steht die Zeile des aktuellen Branches zuerst, die des gemergten danach. Für ein Changelog ist das in aller Regel egal. Wenn du die Einträge sortiert haben willst, musst du das selbst tun.
Geänderte Zeilen verdoppeln sich
Der Treiber löst nur das Problem zweier Ergänzungen. Ändern beide Branches dieselbe vorhandene Zeile unterschiedlich, behält er beide Varianten. Die Folge ist kein Konflikt, sondern ein doppelter Eintrag.
Passieren könnte das zum Beispiel am Dateiende. Keep a Changelog sammelt dort die Vergleichslinks:
[Unreleased]: https://github.com/example/project/compare/v1.2.0...HEAD
Wird beim Release des einen Branches diese Zeile angepasst und gleichzeitig vom anderen Branch, stehen danach zwei [Unreleased]:-Zeilen in der Datei.
Das merkst du nur, wenn du hinschaust.
Konflikte fallen nicht mehr auf
Das ist die größte Kehrseite.
Ein Konflikt ist lästig, aber er zwingt dich, die Stelle anzusehen.
Mit merge=union geht der Merge still durch, auch wenn das Ergebnis nicht stimmt.
Vor einem Release solltest du also immer das Changelog gut prüfen. Auch CI-Jobs können dabei helfen, Fehler im Changelog zu erkennen.
Und andere Dateien?
Weniger Konflikte klingt verlockend, aber der Union-Treiber ist für die wenigsten anderen Dateien geeignet. Beide Varianten einer Zeile im Quellcode zu behalten, ist fast immer falsch und das Ergebnis lässt sich nicht mehr bauen oder parsen.
Der Union-Merge taugt für Dateien, die wachsen, bei denen jede Zeile für sich steht und die Reihenfolge nebensächlich ist.
Neben dem Changelog sind das etwa .gitignore Dateien.
Aber ehrlicherweise gibt es da auch selten Konflikte.
Fazit
Eine Zeile in der .gitattributes kann dir viel Mühe sparen und deinen Entwicklungsprozess beschleunigen.
Du musst nur bedenken, dass Git ab jetzt nicht mehr nachfragt und gelegentlich prüfen, ob sich doppelte Zeilen eingeschlichen haben.
Dafür bleiben die Merge Requests, in denen der Changelog-Eintrag direkt mitgeliefert wird, von nun an konfliktfrei.
Kommentare
Es gibt noch keinen Kommentar zu diesem Beitrag.