Verbranched, vermerged und zugenäht – Git-Branching Modelle kurz vorgestellt


de KaffeeKlatsch git postgresql

Erschienen im KaffeeKlatsch, September/Oktober 2019


Dank der Flexibilität von Git gibt es nicht “das Verfahren”, wie Feature-Branches, Releases und die Pflege mehrerer Alt-Versionen im Repository abgebildet werden. Daher haben sich einige Standard-Workflows herausgebildet. Der hier vorliegende Vergleich von drei sehr unterschiedlichen Workflows hilft dabei, die passende Wahl für sein eigenes Projekt zu treffen.

Warum gibt es nicht eine einzige Lösung für das Problem? Weil es in jedem Projekt neben dem reinen Code sehr unterschiedliche nicht-technische Rahmenbedingungen gibt. Viele davon beeinflussen direkt den Umgang mit der Versionskontrolle, werden aber bei der Darstellungen vom “idealen Workflow” meist nicht erklärt.

Das markanteste Beispiel für eine solche Rahmenbedingung ist folgendes:

Liefert das Projekt eine Web-Anwendung aus, eine “traditionelle”, auf dem PC installierte Anwendung oder eine Library. Bei einer Web- oder Mobil-Anwendung ist immer nur eine Major-Version produktiv – es gibt also keine Pflege von älteren Versionen. Bei einem Produkt, das der Kunde selbst installiert, gibt es in der Regel neben der aktuellsten Version noch eine oder mehrere ältere Major-Versionen für Bugfixes oder zumindest Security-Fixes. Und schon hat man in Git mehrere parallele Haupt-Entwicklungslinien, die dennoch synchronisiert werden müssen. Bei einer Library ist es ebenso üblich, mehrere Major-Versionen über einen relativ langen Zeitraum zu unterstützen.

Weitere Rahmenbedingungen sind:

Dies ist bei weitem keine vollständige Aufzählung aller Einflüsse auf das Branching-Modell oder – umgekehrt – auf die das Branching-Modell Einfluss hat. Aber klar ist, dass die rein technischen Aspekte der Versionsverwaltung nicht den Ausschlag geben dürfen, sondern die sozialen/politischen Aspekte des Projektes – auf der Grundlage der technischen Möglichkeiten. Es gibt daher keine Patentlösung, sondern nur Bausteine und Muster, aus denen eine passende Lösung entsteht.

In den folgenden Abschnitten werden vier Branching-Modelle bzw. Projekte vorgestellt, bei denen diese Rahmenbedingungen sehr unterschiedlich sind.

PostgreSQL

PostgreSQL1 ist eine OpenSource-Datenbank mit einer BSD/MIT ähnlichen Lizenz. Als Versionsverwaltung diente bis 2010 CVS, erst seit September 2010 kommt Git zum Einsatz. Der Artikel zur Umstellung2 enthält die treffende Bemerkung […]sticking with our “conservative approach” to source control[…]. Das GitHub-Repository dient nur als Mirror – es gibt keine Pull-Requests, Issues etc. Die Entwicklung erfolgt vollständig über eine E-Mail Liste, auf der die Patches verbreitet und diskutiert werden. Obwohl PostgreSQL fünf Support-Versionen pflegt, werden Features und Bugfixes nicht gemerged, sondern in die Support-Branches mit git cherry-pick übernommen (siehe Abbildung 1).

Abbildung 1: PostgreSQL – Branches

Außer diesen Haupt-Branches enthält das Hauptrepository keine Feature-Branches. Es gibt – gemessen an der Größe des Projektes und am Alter der Codebasis – nur relative wenige Committer (etwa 50, davon 28 aktive im letzten Jahr). Die tatsächlichen Urheber der Patches werden – entgegen den Git-Konventionen – nicht in den Feldern Author und Committer im Commit sondern nur im Commit-Kommentar vermerkt. Je nach Committer geschieht das unformal (z.B. “Patch by Name ”) oder halb-formal als “Trailer” (z.B. “Author: Name”) am Ende des Kommentars. Dadurch ist die Anzahl der Contributoren schwer zu bestimmen.

Der Release-Prozess ist aus Sicht des Branching-Modells auch sehr einfach: In einem Commit werden ein paar Versionsnummern angepasst und direkt committed – fertig (siehe Abbildung 2).

Abbildung 2: PostgreSQL – Releases

PostgreSQL ist also bewusst völlig Git-Agnostisch – selbst so einfache Dinge wie die Unterscheidung “Author” und “Committer” werden nicht verwendet. Die Entwicklung ist streng linear und extrem simpel. Allerdings erfordert das Back-Patching von Bugfixes Disziplin.

Git-Flow

“Git-Flow”3 ist kein Projekt sondern der Name eines Branching-Modells, das erstmals von Vincent Driessen beschrieben wurde.

Im Kern gibt es die Unterscheidung von permanenten Integrationsbranches (master, develop, ggf. auch support/* als Erweiterung) einerseits und temporären Branches (feature/*, bugfix/*, release/*, hotfix/*) andererseits

Die Entwicklung eines Features findet in einem Feature-Branch statt (z.B. feature/xyz), der von develop ausgeht und nach Abschluss des Features wieder in develop integriert wird. Die Integration kann durch einfaches Mergen, Rebasen oder Rebase + Merge stattfinden (siehe Abbildung 3).

Abbildung 3: Git-Flow – Feature-Branches

Insbesondere die letzte Methode erzeugt eine übersichtliche Historie, in der die Features gut sichtbar bleiben und ggf. durch einen Revert des Merge-Commits wieder entfernt werden können.

Sind die Features für einen Release fertig, so wird aus develop ein temporärer release/1.2.3 Branch abgezweigt. Der Branch dient zur Stabilisierung des Release, ohne die Arbeit in develop zu behindern. Nach der letzten Änderung wird die Versionsnummer angepasst und das Ergebnis sowohl nach master als nach develop gemerged Der Merge-Commit auf master erhält noch ein Versions-Tag (siehe Abbildung 4). Der Merge nach develop garantiert, dass die Bugfixes, die den Release stabilisieren, auch in die normale Entwicklung zurückfließen. Der Release-Branch wird zum Abschluss gelöscht.

Abbildung 4: Git-Flow – Release und Hotfix

Eine Variante des Release-Branches ist der Hotfix-Branch: Wird im letzten Release ein schwerwiegender Fehler entdeckt, kann man nicht auf den nächsten Release warten. Daher zweigt der Hotfix-Branch nicht aus develop ab, sondern aus master, dem letzten Release (siehe Abbildung 4). Ansonsten ist das Verfahren dasselbe wie beim Release-Branch.

Es gibt für Git-Flow Tool-Unterstützung: Die Eclipse-IDE stellt Kontext-Menüs für das Anlegen der Branches bereit. Für die Kommandozeile gibt es das Git-Plugin gitflow-avh4, welches in “Git for Windows” schon integriert ist.

Das Ziel von Git-Flow ist die Entkoppelung auf mehreren Ebenen:

Die Stärken von Git-Flow sind ein überschaubarer Commit-Graph (bei der “rebase + merge” Variante), wenige Blockaden der Entwickler und die Tool-Unterstützung. Aber das nachträgliche Entfernen von Features ist nur schwer möglich, da nach dem Mergen des Features in develop die nachfolgenden Features implizit darauf aufbauen.

OneFlow

“Git-Flow” zieht dennoch Kritik auf sich. In “GitFlow considered harmful”5 kritisiert Adam Ruka vor allem zwei Dinge:

Daraus entwickelte er das “OneFlow” Branching Modell6. Darin beschreibt er die drei schon erwähnten Methoden, einen Feature-Branch wieder in den Hauptzweig zu integrieren, der aber master heißt. Allerdings können dieselben Mechanismen auch in Git-Flow verwendet werden. Interessanter ist das Erstellen von Releases: zwar gibt es auch einen release/x.y.z Branch, der wie in Git-Flow nach Abschluss des Releases in den Haupt-Zweig gemerged wird. Im Unterschied zu Git-Flow wird der letzte Commit des Release-Branch mit der Versionsnummer getagged (siehe Abbildung 5). Hotfixes setzen ggf. auf diesem Tag auf wie im Bild zu sehen ist.

Abbildung 5: OneFlow – Release und Master

Allerdings gibt es nun keinen Branch mehr, der den aktuellsten stabilen Stand kennzeichnet. Dafür schlägt Adam Ruka als Variante vor, den Hauptentwicklungszweig doch wieder wie bei Git-Flow develop zu nennen und den letzten Release mit dem master Branch zu markieren. OneFlow unterscheidet sich dann aber nicht mehr viel von Git-Flow – es ist eher eine Variante des mit starken Worten kritisierten Git-Flow.

OneFlow ist etwas, aber nicht entscheidend einfacher als Git-Flow. Es verliert aber im Gegensatz zu Git-Flow die Möglichkeit, anhand eines durchgehenden master Branches die Release-Stände einfach nacheinander aufzulisten. Wie bei Git-Flow können einmal integrierte Features nur schwer selektiv entfernt werden.

git.git

Der letzte vorgestellte Workflow stammt vom Git-Projekt selbst. Git ist in vielen Punkten mit PostgreSQL vergleichbar: Die Entwicklung des OpenSource- Projektes erfolgt ausschließlich über eine E-Mail Liste. Die Entwickler arbeiten räumlich verteilt und gehören keiner gemeinsamen Organisation oder Firma an. In der E-Mail Liste werden Patches geposted, diskutiert und verbessert. Das danach folgende Branching-Modell verfolgt allerdings einen grundlegend anderen Ansatz – eine vereinfachte Form ist im Manual unter git help workflows dokumentiert7.

Neue Topics (so nennt dieser Workflow Features und Bugfixes) basieren auf dem letzten stabilen Branch (meist “master”, s.u.). Zuerst werden sie nur als Patches auf der Mailing-Liste diskutiert und verbessert. Erst nach einer Konsens-Findung greift der Maintainer die Patches eines Topics auf, stellt sie als Branches zur Verfügung und merged sie – nach und nach – in verschiedene Integrationsbranches. Die Integrationsbranches sind “gestaffelt” – jeder Integrationsbranch enthält neben den eigenen Topics indirekt alle Commits der jeweils stabileren Stufe(n). Es bildet sich also eine Teilmengen-Relation zwischen den Branches:

(maint) ⊂ master ⊂ next ⊂ pu

Das Git-Branching Modell arbeitet intensiv mit Merge-Commits. In den folgenden Bildern sind Merge-Commits von “anderen” Features mit einem kleinen Pfeil gekennzeichnet.

Wie schon erwähnt, basiert ein neues Feature (im Gegensatz zu einem Bugfix) auf dem jeweils aktuellen master Branch (siehe Abbildung 6).

Da die Patches per E-Mail übertragen werden, können Sender und Empfänger (der Git-Maintainer) durchaus einen anderen Stand haben – implizit findet also eine Art “Rebase” statt. Nach der Diskussion auf der E-Mail Liste wird das Feature zusammen mit anderen Features in den pu Branch gemerged. Der pu Branch wird nicht nur erweitert, sondern auch regelmäßig weggeworfen (siehe graue Lebenslinien im Bild) und aus next neu erstellt. Dabei können Features bewusst wieder entfernt werden, weil sie z.B. nicht mehr weiter verfolgt werden oder selbst für pu zu unreif sind. Wird ein Feature in pu verbessert, so wird der gesamte bisherige Feature-Branch weggeworfen, neu eingespielt und in den nächsten neu erstellten pu Branch gemerged. Auch hier findet implizit ein Rebase statt.

Abbildung 6: Git – Feature in pu

Die nächste Stufe erreicht das Feature, wenn es in den next Branch gemerged wird (siehe Abbildung 7 oberer Teil). In dieser Phase werden weitere Änderungen am Feature nicht mehr durch Wegwerfen des gesamten Branches durchgeführt, sondern als Patches im Branch, die dann auch wieder nach next gemerged werden. In jedem Fall wird der pu Branch – aufbauend auf dem aktualisierten next – neu erstellt.

Abbildung 7: Git – Feature in next und master

Wenn das Feature abgeschlossen und stabil ist, erfolgt der Merge nach master (siehe Abbildung 7 unterer Teil). Anschließend erfolgt ein “Sync” Merge von master nach next. Jetzt ist das Feature sicher im nächsten Release enthalten.

Dies ist der Workflow, wie er in 7 beschrieben ist, aber Git selbst weicht davon in Details ab. Für das Verständnis des Verfahrens an sich ist daher der nächste Absatz und das nächste Bild nicht wichtig und kann übersprungen werden. Sie sind aber z.B. nützlich, wenn Sie selbst bei Git einen Patch einbringen und den Ablauf verfolgen möchten.

Ein neuer pu Branch wird tatsächlich nicht direkt aus next erstellt, sondern aufbauend auf dem jeweils aktuellen master. Die Feature-Branches, die in next aber noch nicht in master sind, werden also nochmal gemerged (siehe Abbildung 8). Das heißt, dass die meisten “gereiften” Features immer in zwei Branches gemerged wurden: Entweder in pu und next oder in master und next.

Abbildung 8: Git – next Real

Bugfix Branches müssen – im Gegensatz zu neuen Features, auf dem Branch der ältesten Version erstellt werden, die unterstützt werden soll (siehe Abbildung 9). Sie werden zuerst nach next gemerged, um im aktuellen Entwicklungszweig eventuelle negative Auswirkungen festzustellen. Danach kommt der Bugfix in master und erst zum Schluss in die Maintenance Branches.

Abbildung 9: Git – Bugfixes

Schon die schematische Darstellung des Git Workflow zeigt, dass er schwer zu durchschauen ist. Die Branch-Pflege ist aufwendig, unübersichtlich und eine visuelle Kontrolle mittels graphischer Darstellung ist de-facto unmöglich. Durch das neu Bauen des pu Integrations-Branches werden zwar textuelle Konflikte gefunden. Logische Konflikte (z.B. benennt Feature A eine Variable um, Feature B erstellt eine neue Funktion/Klasse/Datei, in der noch der alte Name verwendet wird) benötigen zusätzliches Tooling, um bei jedem Neubau diese Änderungen mitzuziehen. All das erfordert wasserdichtes, narrensicheres Tooling.

Der Git-Workflow erlaubt allerdings eine bessere Feature-Selektion als die anderen Verfahren: Features in pu können einfach so gelöscht werden, auch Features in next können noch leicht entfernt werden.

Fazit

Branching-Modelle sind manchmal ein hitziges Diskussionsthema. Aber “den” Workflow, der für jedes Projekt passt, gibt es nicht. Da aber – bis auf Git selbst – der Zweck des Projektes nicht Versionskontrolle sondern etwas anderes ist, sollte das Branching Modell wenig mentale Kosten verursachen und möglichst einfach sein. PostgreSQL zeigt das als Extrembeispiel sehr deutlich. Im typischen Enterprise-Projektumfeld empfiehlt sich der Git-Ansatz nur, wenn extrem viele Leute am Projekt arbeiten und Features regelmäßig bei Sprint-Abnahmen so durchfallen, dass sie spät im Entwicklungszyklus wieder entfernt werden müssen. In diesem Fall ist es aber vermutlich für alle Beteiligten einfacher, das Umfeld zu ändern. “Git-Flow” und “OneFlow” sind letztlich sehr ähnlich und – bei kurzem Nachdenken – das, was viele Projekte im Enterprise Umfeld prinzipiell auch schon tun – nur ohne die Terminologie zu verwenden.

In allen Fällen darf man Branching-Modelle nicht als “das letzte Wort” betrachten, sondern eher als Baukasten oder Pattern, die für den jeweiligen Fall angepasst werden.

Kurzbiografie

Andreas Heiduk ist als Senior Consultant für MATHEMA Software GmbH tätig. Seine Themenschwerpunkte umfassen Domänenspezifische Sprachen, die Java Standard Edition (JSE) und die Java Enterprise Edition (JEE). Daneben findet er die unterschiedlichsten Themen von hardwarenaher Programmierung bis hin zu verteilten Anwendungen interessant.