Verbesserter Git-Flow erklärt
Veröffentlicht: 2022-03-11Mit Git unbeabsichtigt Schaden anzurichten, kann allzu einfach sein. Die beste Art, Git zu verwenden, wird jedoch immer umstritten sein.
Das liegt daran, dass Git selbst nur grundlegende Verzweigungsoperationen detailliert beschreibt, wodurch seine Verwendungsmuster – dh Verzweigungsmodelle – eine Frage der Benutzermeinung bleiben. Git-Branching-Modelle versprechen, den Schmerz zu lindern, indem sie das Chaos organisieren, das unweigerlich entsteht, wenn Softwareentwickler Änderungen an ihren Codebasen vornehmen.
Wie viele Entwickler wollten Sie etwas, das „einfach funktioniert“, damit Sie mit der eigentlichen Softwareentwicklung weitermachen können. Sie haben also Git flow aufgegriffen, ein Verzweigungsmodell, das Git-Benutzern oft empfohlen wird. Vielleicht waren Sie anfangs mit der Logik von Git Flow an Bord, bis Sie in der Praxis auf einige Probleme gestoßen sind. Oder vielleicht scheint Git Flow nicht gut genug zu sein, um es zu übernehmen. Schließlich spielen unzählige Variablen eine Rolle, und kein einzelnes Verzweigungsmodell wird in jeder Situation gut funktionieren.
Gute Nachrichten! Als Variation des klassischen Git-Flow-Modells vereinfacht der erweiterte Git-Flow die häufigeren Manöver des Git-Flow-Workflows, während die Hauptvorteile erhalten bleiben.
Glanz und Elend des klassischen Git-Flow-Modells
Ich bin ein starker Befürworter von Git Flow, seit ich entdeckt habe, wie es sich auszeichnet, wenn ein Produkt entwickelt wird, das sich in signifikanten Wertsteigerungen (mit anderen Worten, Releases ) entwickelt.
Ein signifikanter Wertzuwachs erfordert einen erheblichen Zeitaufwand, um abgeschlossen zu werden, wie die Sprints von mehr als zwei Wochen, die normalerweise in der Scrum-basierten Entwicklung verwendet werden. Wenn ein Entwicklungsteam bereits für die Produktion bereitgestellt hat, kann es zu Problemen kommen, wenn sich der Umfang der nächsten Version an derselben Stelle ansammelt, an der sich der Produktionscode befindet – z. B. im Hauptzweig des von ihnen verwendeten Git-Repositorys.
Während sich das Produkt noch in der anfänglichen Entwicklungsphase befindet – dh es gibt keine Produktion und es gibt keine echten Benutzer des Produkts – ist es für das Team in Ordnung, alles einfach im Hauptzweig zu belassen. Eigentlich ist es mehr als in Ordnung: Diese Strategie ermöglicht das schnellste Entwicklungstempo ohne großen Aufwand. Aber in einer Produktionsumgebung ändern sich die Dinge; dann verlassen sich echte Menschen darauf, dass das Produkt stabil ist.
Wenn es beispielsweise einen kritischen Fehler in der Produktion gibt, der sofort behoben werden muss, wäre es eine große Katastrophe für das Entwicklungsteam, die gesamte bisher im Hauptzweig angefallene Arbeit rückgängig machen zu müssen, nur um den Fix bereitzustellen. Und die Bereitstellung von Code ohne angemessene Tests – ob der Code als unausgegoren oder als gut entwickelt gilt – ist eindeutig keine Option.
Hier glänzen Verzweigungsmodelle, einschließlich Git-Flow. Jedes ausgeklügelte Verzweigungsmodell sollte Fragen dazu beantworten, wie man die nächste Version von der Version des Systems isoliert , die derzeit von Benutzern verwendet wird, wie man diese Version mit der nächsten Version aktualisiert und wie man Hotfixes für alle kritischen Fehler in die aktuelle Version einführt.
Der Git-Flow-Prozess adressiert diese grundlegenden Szenarien, indem er „Main“ (den Produktions- oder „aktuelle Version“-Branch) und „Develop“ (den Entwicklungs- oder „Next Release“-Branch) trennt und alle Regeln zur Verwendung von Feature-/Release-/Hotfix-Branchs bereitstellt . Es löst effektiv viele Probleme in den Entwicklungsworkflows von Release-basierten Produkten.
Aber selbst bei Projekten, die gut für das klassische Git-Flow-Modell geeignet sind, habe ich unter den typischen Problemen gelitten, die es mit sich bringen kann:
- Der Git-Flow ist komplex, mit zwei langlebigen Zweigen, drei Arten von temporären Zweigen und strengen Regeln, wie Zweige miteinander umgehen. Diese Komplexität erhöht die Wahrscheinlichkeit von Fehlern und erhöht den Aufwand für deren Behebung.
- Release- und Hotfix-Branches erfordern ein „doppeltes Zusammenführen“ – einmal in main, dann in development. Manchmal kann man vergessen, beides zu tun. Sie können die Git-Flussverzweigung mit Skripten oder VCS-GUI-Client-Plug-ins vereinfachen, aber Sie müssen sie zuerst für jeden Computer jedes Entwicklers einrichten, der an einem bestimmten Projekt beteiligt ist.
- In CI/CD-Workflows erhalten Sie normalerweise zwei endgültige Builds für ein Release – eines aus dem letzten Commit des Release-Zweigs selbst und ein weiteres aus dem Merge-Commit zum Hauptverzeichnis. Genau genommen sollten Sie das aus dem Hauptteil verwenden, aber die beiden sind normalerweise identisch, was zu Verwechslungen führen kann.
Geben Sie „Erweiterter Git-Flow“ ein
Das erste Mal, dass ich den erweiterten Git-Flow verwendet habe, war bei einem Greenfield-Closed-Source-Projekt. Ich habe mit einem anderen Entwickler zusammengearbeitet, und wir haben an dem Projekt gearbeitet, indem wir uns direkt auf den Hauptzweig festgelegt haben.
Hinweis: Bis zur ersten öffentlichen Veröffentlichung eines Produkts ist es aus Gründen der Geschwindigkeit und Einfachheit des Entwicklungsworkflows absolut sinnvoll, alle Änderungen direkt in den Hauptzweig zu übertragen – selbst wenn Sie ein Git-Flow-Befürworter sind. Da es noch keine Produktion gibt, besteht keine Möglichkeit eines Produktionsfehlers, den das Team so schnell wie möglich beheben muss. Die ganze Verzweigungsmagie, die der klassische Git-Flow impliziert, ist daher zu diesem Zeitpunkt übertrieben.
Dann näherten wir uns der ersten Veröffentlichung und waren uns einig, dass wir uns über diesen Punkt hinaus nicht mehr wohl fühlen würden, wenn wir uns direkt auf den Hauptzweig festlegen würden. Wir waren ziemlich schnell vorangekommen, und geschäftliche Prioritäten ließen nicht viel Raum, um einen felsenfesten Entwicklungsprozess zu etablieren – dh einen mit ausreichend automatisierten Tests, um uns die Gewissheit zu geben, dass unser Hauptzweig in einem releasefähigen Zustand bleibt.
Es schien ein gültiger Fall für das klassische Git-Flow-Modell zu sein. Mit getrennten Haupt- und Entwicklungszweigen und genügend Zeit zwischen signifikanten Werterhöhungen bestand die Zuversicht, dass die überwiegend manuelle QA zu ausreichend guten Ergebnissen führen würde. Als ich mich für Git Flow aussprach, schlug mein Kollege etwas Ähnliches vor, jedoch mit einigen wesentlichen Unterschieden.
Zuerst habe ich zurückgedrängt. Einige der vorgeschlagenen „Patches“ für den klassischen Git-Flow erschienen mir etwas zu revolutionär. Ich dachte, sie könnten die Hauptidee brechen, und der gesamte Ansatz würde zu kurz kommen. Aber bei weiterem Nachdenken wurde mir klar, dass diese Optimierungen den Git-Fluss nicht wirklich unterbrechen. In der Zwischenzeit machen sie es zu einem besseren Git-Branching-Modell, indem sie alle oben genannten Schwachstellen lösen.
Nach dem Erfolg mit dem modifizierten Ansatz in diesem Projekt habe ich ihn in einem anderen Closed-Source-Projekt mit einem kleinen Team dahinter verwendet, wo ich der ständige Eigentümer der Codebasis war und ein oder zwei ausgelagerte Entwickler von Zeit zu Zeit halfen. Bei diesem Projekt gingen wir sechs Monate in die Produktion und seitdem verwenden wir CI- und E2E-Tests seit über einem Jahr, mit Releases etwa jeden Monat.
Meine Gesamterfahrung mit diesem neuen Branching-Ansatz war so positiv, dass ich sie mit meinen Entwicklerkollegen teilen wollte, um ihnen zu helfen, die Nachteile des klassischen Git-Flows zu umgehen.
Ähnlichkeiten mit klassischem Git-Flow: Entwicklungsisolation
Für die Arbeitsisolation im erweiterten Git-Fluss gibt es immer noch zwei langlebige Branches, main und development. (Benutzer haben immer noch Hotfix- und Release-Fähigkeiten – mit Betonung auf „Fähigkeiten“, da dies keine Verzweigungen mehr sind. Wir werden im Abschnitt „Unterschiede“ auf Details eingehen.)
Es gibt kein offizielles Benennungsschema für klassische Git-Flow-Funktionszweige. Sie verzweigen sich einfach von der Entwicklung und führen sie wieder in die Entwicklung ein, wenn das Feature fertig ist. Teams können jede beliebige Namenskonvention verwenden oder einfach hoffen, dass Entwickler aussagekräftigere Namen als „my-branch“ verwenden. Dasselbe gilt für den erweiterten Git-Fluss.
Alle Features, die sich bis zu einem gewissen Schnittpunkt im Entwicklungszweig angesammelt haben, werden die neue Version prägen.
Squash-Merges
Ich empfehle dringend, Squash-Merge für Feature-Branches zu verwenden, um den Verlauf die meiste Zeit schön und linear zu halten. Ohne sie beginnen Commit-Graphen (von GUI-Tools oder git log --graph
) schlampig auszusehen, wenn ein Team auch nur mit einer Handvoll Feature-Branches jongliert:
Aber selbst wenn Sie mit der Optik in diesem Szenario einverstanden sind, gibt es noch einen weiteren Grund, es zu kürzen. Ohne zu quetschen, erzählen Commit-Verlaufsansichten – darunter sowohl einfaches git log
(ohne --graph
) als auch GitHub – selbst bei den einfachsten Merge-Szenarien ziemlich zusammenhangslose Geschichten:
Der Nachteil bei der Verwendung von Squash-Merging ist, dass der ursprüngliche Feature-Branch-Verlauf verloren geht. Aber dieser Vorbehalt gilt nicht einmal, wenn Sie beispielsweise GitHub verwenden, das den vollständigen ursprünglichen Verlauf eines Feature-Zweigs über die per Squash zusammengeführte Pull-Anforderung offenlegt, selbst nachdem der Feature-Zweig selbst gelöscht wurde.
Unterschiede zum klassischen Git-Flow: Releases und Hotfixes
Lassen Sie uns den Veröffentlichungszyklus durchgehen, da dies (hoffentlich) die Hauptsache ist, die Sie tun werden. Wenn wir an dem Punkt angelangt sind, an dem wir veröffentlichen möchten, was sich in der Entwicklung angesammelt hat, handelt es sich ausschließlich um eine Obermenge von main. Danach beginnen die größten Unterschiede zwischen klassischem und erweitertem Git-Flow.

Veröffentlichungen im erweiterten Git-Flow
Jeder Schritt beim Erstellen eines Releases mit erweitertem Git-Flow unterscheidet sich vom klassischen Git-Flow-Prozess:
- Releases basieren eher auf Main als auf Developer. Markieren Sie den aktuellen Tipp des Hauptasts mit etwas Sinnvollem. Ich habe Tags basierend auf dem aktuellen Datum im ISO 8601-Format mit vorangestelltem „v“ übernommen, z. B. v2020-09-09 .
- Wenn es an einem Tag mehrere Veröffentlichungen gibt – zum Beispiel Hotfixes – kann das Format je nach Bedarf mit einer fortlaufenden Nummer oder einem Buchstaben versehen werden.
- Beachten Sie, dass die Tags im Allgemeinen nicht den Veröffentlichungsdaten entsprechen. Sie sollen Git lediglich dazu zwingen, einen Verweis darauf zu behalten, wie der Hauptzweig aussah, als der nächste Veröffentlichungsprozess begann.
- Pushen Sie das Tag mit
git push origin <the new tag name>
. - Danach eine kleine Überraschung: Löschen Sie Ihre lokale Hauptniederlassung . Keine Sorge, wir werden es in Kürze wiederherstellen.
- Alle Commits an main sind immer noch sicher – wir haben sie vor der Garbage Collection geschützt, indem wir im vorherigen Schritt main markiert haben. Jeder dieser Commits – sogar Hotfixes, wie wir gleich behandeln werden – ist auch Teil von development.
- Stellen Sie einfach sicher, dass nur eine Person in einem Team dies für eine bestimmte Version tut; das ist die sogenannte „Release Manager“-Rolle. Ein Release Manager ist normalerweise das erfahrenste und/oder dienstälteste Teammitglied, aber ein Team tut gut daran, zu vermeiden, dass ein bestimmtes Teammitglied diese Rolle dauerhaft übernimmt. Sinnvoller ist es, Wissen im Team zu verbreiten, um den berüchtigten Bus-Faktor zu erhöhen.
- Erstellen Sie einen neuen lokalen Hauptzweig am Tip-Commit Ihres Entwicklungszweigs .
- Pushen Sie diese neue Struktur mit
git push --force
, da das entfernte Repo eine so „drastische Änderung“ nicht so einfach akzeptiert. Auch dies ist nicht so unsicher, wie es in diesem Zusammenhang erscheinen mag, denn:- Wir verschieben lediglich den Hauptzweigzeiger von einem Commit zu einem anderen.
- Diese Änderung wird jeweils nur von einem bestimmten Teammitglied vorgenommen.
- Die tägliche Entwicklungsarbeit findet im Develop-Zweig statt, sodass Sie die Arbeit von niemandem stören, indem Sie auf diese Weise in den Hauptzweig wechseln.
- Sie haben Ihre Neuerscheinung! Stellen Sie es in der Staging-Umgebung bereit und testen Sie es. (Wir werden weiter unten praktische CI/CD-Muster besprechen.) Alle Korrekturen gehen direkt an den Hauptzweig, und er wird deshalb beginnen, vom Entwicklungszweig abzuweichen.
- Gleichzeitig können Sie im Entwicklungszweig mit der Arbeit an einem neuen Release beginnen, derselbe Vorteil wie im klassischen Git-Flow.
- Für den unglücklichen Fall, dass zu diesem Zeitpunkt ein Hotfix für das benötigt wird, was sich derzeit in der Produktion befindet (nicht für die bevorstehende Version in der Bereitstellung), finden Sie weitere Einzelheiten zu diesem Szenario unter „Umgang mit Hotfixes während einer aktiven Version…“.
- Wenn Ihre neue Version als stabil genug erachtet wird, stellen Sie die endgültige Version in der Produktionsumgebung bereit und führen Sie einen einzigen Squash-Merge von main to development durch, um alle Fixes aufzunehmen.
Hotfixes im erweiterten Git-Flow
Hotfix-Fälle sind zweierlei. Wenn Sie einen Hotfix machen, ohne dass es eine aktive Version gibt – dh das Team bereitet eine neue Version im Entwicklungszweig vor – ist es ein Kinderspiel: Commit to main, lassen Sie Ihre Änderungen bereitstellen und im Staging testen, bis sie fertig sind für die Produktion bereitstellen.
Wählen Sie als letzten Schritt Ihr Commit von „main“ bis „development“ aus, um sicherzustellen, dass die nächste Version alle Fixes enthält. Falls Sie am Ende mehrere Hotfix-Commits haben, sparen Sie Aufwand – insbesondere wenn Ihre IDE oder ein anderes Git-Tool dies ermöglichen kann – indem Sie einen Patch erstellen und anwenden, anstatt mehrmals Rosinen herauszupicken. Der Versuch, Merge Main zur Entwicklung nach der ersten Veröffentlichung zu quetschen, wird wahrscheinlich zu Konflikten mit dem unabhängigen Fortschritt im Entwicklungszweig führen, daher empfehle ich das nicht.
Der Umgang mit Hotfixes während einer aktiven Version – dh wenn Sie nur Push-Main erzwingen und immer noch die neue Version vorbereiten – ist der schwächste Teil des erweiterten Git-Flusses. Je nach Länge Ihres Release-Zyklus und Schweregrad des Problems, das Sie lösen müssen, sollten Sie immer darauf abzielen, Fixes in das neue Release selbst aufzunehmen – das ist der einfachste Weg und wird den gesamten Arbeitsablauf überhaupt nicht stören.
Falls das ein No-Go ist – Sie müssen schnell einen Fix einführen und können es kaum erwarten, bis die neue Version fertig ist – dann machen Sie sich bereit für eine etwas komplizierte Git-Prozedur:
- Erstellen Sie einen Zweig – wir nennen ihn „Neuveröffentlichung“, aber Ihr Team kann hier jede beliebige Namenskonvention übernehmen – mit demselben Commit wie der aktuelle Tipp von main. Neuerscheinung pushen.
- Löschen Sie den lokalen Hauptzweig am Commit des Tags, das Sie zuvor für die aktuelle aktive Version erstellt haben, und erstellen Sie ihn neu. Push-Main erzwingen.
- Führen Sie die erforderlichen Fixes in main ein, stellen Sie sie in der Staging-Umgebung bereit und testen Sie sie. Sobald dies fertig ist, stellen Sie es in der Produktion bereit.
- Geben Sie Änderungen von der aktuellen Hauptversion an die neue Version weiter, entweder per Cherry-Picking oder einem Patch.
- Wiederholen Sie danach die Release-Prozedur: Taggen Sie die Spitze des aktuellen Mains und pushen Sie das Tag, löschen und erstellen Sie den lokalen Main an der Spitze des New-Release-Zweigs neu und erzwingen Sie Push Main.
- Sie werden das vorherige Tag wahrscheinlich nicht benötigen, also können Sie es entfernen.
- Der New-Release-Zweig ist jetzt überflüssig, sodass Sie ihn auch entfernen können.
- Sie sollten jetzt wie gewohnt mit einer neuen Version loslegen können. Schließen Sie ab, indem Sie die Notfall-Hotfixes von main über Rosinenpickerei oder einen Patch verbreiten.
Bei richtiger Planung, ausreichend hoher Codequalität und einer gesunden Entwicklungs- und Qualitätssicherungskultur ist es unwahrscheinlich, dass Ihr Team diese Methode verwenden muss. Es war ratsam, einen solchen Notfallplan für einen verbesserten Git-Fluss zu entwickeln und zu testen, nur für den Fall – aber ich musste ihn nie in der Praxis verwenden.
CI/CD-Setup zusätzlich zum erweiterten Git-Flow
Nicht jedes Projekt erfordert eine dedizierte Entwicklungsumgebung. Es kann einfach genug sein, auf jedem Entwicklercomputer eine ausgeklügelte lokale Entwicklungsumgebung einzurichten.
Aber eine dedizierte Entwicklungsumgebung kann zu einer gesünderen Entwicklungskultur beitragen. Durch das Ausführen von Tests, das Messen der Testabdeckung und das Berechnen von Komplexitätsmetriken im Entwicklungszweig werden häufig die Kosten von Fehlern gesenkt, indem sie erkannt werden, bevor sie in der Staging-Umgebung landen.
Ich fand einige CI/CD-Muster besonders nützlich, wenn sie mit einem verbesserten Git-Fluss kombiniert wurden:
- Wenn Sie eine Entwicklungsumgebung benötigen, richten Sie CI so ein, dass es bei jedem Commit in den Entwicklungszweig erstellt, getestet und bereitgestellt wird. Passen Sie hier auch E2E-Tests an, wenn Sie es haben und es in Ihrem Fall Sinn macht.
- Richten Sie CI zum Erstellen, Testen und Bereitstellen in der Staging-Umgebung bei jedem Commit zum Hauptzweig ein. E2E-Tests sind auch an dieser Stelle sehr vorteilhaft.
- Es mag überflüssig erscheinen, E2E-Tests an beiden Stellen zu verwenden, aber denken Sie daran, dass Hotfixes in der Entwicklung nicht vorkommen. Durch das Auslösen von E2E bei Commits an main werden Hotfixes und alltägliche Änderungen getestet, bevor sie veröffentlicht werden, aber auch durch das Auslösen von Commits zur Entwicklung werden Fehler früher erkannt.
- Konfigurieren Sie CI so, dass Ihr Team auf manuelle Anfrage hin Builds von der Haupt- in die Produktionsumgebung bereitstellen kann.
Solche Muster sind relativ einfach, bieten jedoch leistungsstarke Maschinen zur Unterstützung des täglichen Entwicklungsbetriebs.
Das erweiterte Git-Flow-Modell: Verbesserungen und mögliche Einschränkungen
Enhanced Git Flow ist nicht jedermanns Sache. Es nutzt die umstrittene Taktik, den Hauptzweig mit Gewalt voranzutreiben, so dass Puristen es ablehnen können. Aus praktischer Sicht ist daran jedoch nichts auszusetzen.
Wie bereits erwähnt, sind Hotfixes während eines Releases schwieriger, aber immer noch möglich. Mit der richtigen Aufmerksamkeit für QA, Testabdeckung usw. sollte das nicht allzu oft passieren, daher ist es aus meiner Sicht ein gültiger Kompromiss für die Gesamtvorteile des erweiterten Git-Flows im Vergleich zum klassischen Git-Flow. Mich würde sehr interessieren, wie der verbesserte Git-Flow in größeren Teams und bei komplexeren Projekten abschneidet, bei denen Hotfixes möglicherweise häufiger vorkommen.
Meine positiven Erfahrungen mit dem erweiterten Git-Flow-Modell drehen sich auch hauptsächlich um kommerzielle Closed-Source-Projekte. Dies kann für ein Open-Source-Projekt problematisch sein, bei dem Pull-Requests häufig auf einer alten Release-Ableitung des Quellbaums basieren. Es gibt keine technischen Hürden, um das zu lösen – es könnte nur mehr Aufwand erfordern als erwartet. Ich freue mich über Rückmeldungen von Lesern mit viel Erfahrung im Open-Source-Bereich bezüglich der Anwendbarkeit des erweiterten Git-Flusses in solchen Fällen.
Besonderer Dank gilt dem Toptal-Kollegen Antoine Pham für seine Schlüsselrolle bei der Entwicklung der Idee hinter dem verbesserten Git-Fluss.
Weiterführende Literatur im Toptal Engineering Blog:
- Trunk-basierte Entwicklung vs. Git Flow
- Git-Workflows für Profis: Ein guter Git-Leitfaden
Als Microsoft Gold Partner ist Toptal Ihr Elite-Netzwerk von Microsoft-Experten. Bauen Sie leistungsstarke Teams mit den Experten auf, die Sie brauchen – überall und genau dann, wenn Sie sie brauchen!