Versionsverwaltung mit Branches in Git: Eine kurze Einführung

Das Erstellen und Verwalten von unterschiedlichen Entwicklungszweigen, den Branches, geschieht in Git schnell und einfach. Verschaffen wir uns einen kleinen Überblick, welche Strategien es beim Anlegen von Branches gibt.

Wozu legen wir Branches an?

Initialisierst du ein Git-Repository, wie wir es bei der Versionskontrolle mit Git in Visual Studio Code gemacht haben, haben wir einen Hauptentwicklungszweig namens „master“. Unsere beiden Beispiel-Commits haben wir auf diesem Branch gemacht. Das hat auch problemlos geklappt, warum sollten wir also weitere Branches einführen?

Stellen wir uns vor, dass wir fortwährend auf dem master arbeiten. Wir werkeln vor uns hin, committen unsere Arbeit regelmäßig, alles läuft. Nun kommt aber eine weitere Person hinzu, die auch an unserem Projekt arbeitet und plötzlich wird alles komplizierter. Wir arbeiten zum Teil an den gleichen Dateien, es kommt zu Konflikten, unser Projekt ist häufiger nicht lauffähig, aber unser Ziel ist es ja gerade das Projekt so weit wie möglich zu automatisieren und jederzeit in einem Zustand zu haben, dass wir die Software ausliefern können. Das ist in dieser Konstellation nicht mehr so einfach möglich. Die Schwierigkeiten nehmen zu, wenn noch mehr Personen an dem Projekt mitarbeiten.

Die Lösung ist es, einen Entwicklungszweig zu haben, von dem jederzeit ein auslieferbares Artefakt bereitsteht, ohne aber die Entwicklerinnen und Entwickler bei ihrer Arbeit auszubremsen. Diese legen für ihre konkrete Aufgabe einen jeweils eigenen Branch an, der vom masterabzweigt. Sie erfüllen ihre Aufgabe auf diesem Zweig, schreiben also Code und committen diesen in ihren Branch. Dabei stellen sie sicher, dass ihre Änderungen im Zusammenspiel mit dem Code auf master lauffähig sind und das auszuliefernde Artefakt nicht beschädigen. Sind diese Voraussetzungen gegeben, verschmelzen sie schließlich ihren selbst angelegten Branch mit dem master; diesen Vorgang nennt man „merge“. Dabei fließt nicht nur das Endergebnis des Branchs ein, sondern auch alle seine Commits.

Branches ermöglichen es also, dass die Entwicklung fortschreiten kann und dass nebenbei neue Features neben Bugfixes und womöglich Code-Experimente stattfinden können.

Verschiedene Branching-Strategien

In der Regel gibt es einen geschützten Hauptentwicklungszweig, der sich nicht ohne weiteres Löschen lässt und Änderungen auf diesem Branch besonderer Rechte bedürfen. Fast immer handelt es sich hierbei um den master.

Auch wenn wir bei Git mit einem verteilten System arbeiten, gibt es meistens dennoch einen zentralen Server, auf dem das Projekt gehostet wird; der sogenannte origin. Häufig handelt es sich dabei um einen der bekannten Git-Hoster wie GitHub, GitLab oder BitBucket. Wie wir mit Visual Studio Code einen Zugang bei einem solchen Hoster einrichten, sehen wir uns in einem späteren Post an.

Es gibt nicht die eine richtige Branching-Strategie, die für jedes Team passend ist. Es ist Aufgabe des Teams, sich auf eine Branching-Strategie zu einigen. Welche Strategie passt, hängt sowohl von der Team-Größe als auch von der Komplexität des jeweiligen Projekts ab. Wichtig ist nur, dass sich alle an die einmal gewählte Branching-Strategie halten, ansonsten funktioniert es nicht.

Die hier gezeigten Bildschirmfotos zeigen eine grafische Ansicht der Branch-Verläufe im Terminal. Das dafür verwendete Kommando lautet:

git log --pretty=format:"%h %s" --graph

Keine Branches: Kann man so machen, aber dann hat man wenig von den Vorteilen von Git. Für Umsteiger von zentralisierten Versionsverwaltungen ist das zu Beginn mit Git ein einfacher Einstieg.

Master-Strategie

Hierbei handelt es sich zumeist um Umsteiger aus zentralisierten Versionskontrollsystemen. Die Entwicklerinnen und Entwickler arbeiten mit dem Repository auf einem zentralen Server, von dem das Projekt geklont, bearbeitet und schließlich wieder eingecheckt wird.

Diese recht simple Arbeitsweise funktioniert prinzipiell, sie verzichtet jedoch auf die Vorteile, die man als Git-Anwenderin und Anwender haben kann: das kostengünstige, einfache Branching.

Features werden auf Branches entwickelt und anschließend in den master gemerged; eine einfache, aber effektive Branching-Strategie.

Master-Feature-Branch-Strategie

Wir haben unseren master, von dem für die Entwicklung von Features Branches abgezweigt werden. Diese werden parallel zum Fortschritt auf dem master entwickelt und nach Beenden der Arbeit auf den master zurückgespielt (gemerged). Behält man als Entwicklerin oder Entwickler auf einem Feature-Branch die fortschreitende Arbeit auf dem master ein wenig im Auge und merged daher regelmäßig diese Fortschritte in den eigenen Feature-Branch, so bleiben böse Überraschungen, wie beispielsweise Konflikte im Code beim finalen Merge des Feature-Branch in den master aus, denn der Stand auf dem Feature-Branch entfernt sich niemals zu sehr vom Stand auf dem master.

Möchte ein Team-Mitglied nach getaner Arbeit die eigenen Änderungen in den master mergen, erstellt er oder sie einen sogenannten Pull-Request. Dieser Pull-Request geht an eine andere Entwicklerin oder einen anderen Entwickler, die oder der dann die Änderungen prüft und – falls nichts zu beanstanden ist – dem Pull-Request stattgibt und die Änderungen in den master merged. Ist der Merge vollzogen, wird der Feature-Branch gelöscht, um die Übersichtlichkeit zu erhalten.

Bei dieser Strategie können auch Bugfixes auf eigenen Branches bearbeitet werden, es müssen nicht ausschließlich Features sein.

Dieser Arbeitsablauf wird häufig in kleineren Teams und Projekten gelebt, weil die Flexibilität der Branches sinnvoll genutzt wird. Allen Beteiligten ist klar, dass auf master die „Wahrheit“ ist, von wo aus die einzelnen Releases abgehen.

Neben den unbegrenzt laufenden Branches master und develop werden beim Git-Flow-Ansatz für Releases, Hotfixes und Features kurzzeitig Entwicklungszweige angelegt. Dieses aufwändige Verfahren ist gut durchdacht, aber auch aufwändig.

Git-Flow-Strategie

Während die beiden ersten Strategien im Großen und Ganzen recht einfach nachzuvollziehen sind, ist die Anwendung des Git-Flow-Arbeitsablaufs etwas aufwändiger.

Hierbei gibt es zunächst die beiden unbegrenzt laufenden Entwicklungszweige master und develop. Auf master befindet sich stets die zuletzt ausgelieferte Version der Software. Der Branch develop hingegen bildet den jeweiligen Entwicklungsstand des Projekts ab, der für die nächste Auslieferung vorgesehen ist. Hiervon baut das CI/CD-System die zu testenden Artefakte. Hat develop einen Stand erreicht, der veröffentlicht werden soll, dann gibt es einen Merge von develop auf master und die Software wird veröffentlicht.

Zunächst einmal gibt es wie bei der vorherigen Strategie die Arbeitsweise mithilfe von Pull-Requests, die von besonders autorisierten Entwicklerinnen und Entwicklern geprüft und freigegeben werden. Die gesamte Entwicklung wird stets in Branches vorangetrieben.

Sollte es plötzlich das dringende Bedürfnis geben, einen Hotfix ausliefern zu müssen, weil ein katastrophaler Fehler aufgetaucht ist, der das Verwenden unserer Software unmöglich macht, dann greifen wir auf den master zurück. Wir können – unabhängig von dem wie weit auch immer fortgeschrittenen Zustand auf develop – einfach einen Branch des letzten Commits vom master anlegen und auf diesem den Fehler beheben. Wir liefern die korrigierte Software aus, mergen den Hotfix-Branch auf master zurück, denn dort befindet sich ja immer der Stand unserer augenblicklich am Markt befindlichen Software. Außerdem mergen wir unseren Hotfix in develop, denn wir wollen, dass der Fehler auch in allen künftigen Releases korrigiert ist. Nach den beiden Merges können wir den Hotfix-Branch löschen.

Ansonsten geschieht die Entwicklung wie bei der zuvor genannten Strategie in Feature-Branches. Diese werden von develop abgezweigt und nach Beendigung der Arbeit dorthin gemerged. Während der Arbeit auf dem Feature-Branch ist es wichtig, die parallel laufenden Änderungen auf develop regelmäßig in den eigenen Branch zu mergen, damit es zu keinen Merge-Konflikten kommt. Was hierbei deutlich wird, ist dass sich Feature-Branches niemals direkt mit dem master austauschen, sondern immer nur mit develop.

Sind wir soweit, dass wir eine neue Version unserer Software ausliefern können, so zweigen wir von develop einen Release-Branch ab. Auf diesem können noch in letzter Minute winzige Bug-Fixes einfließen, aber es wird auf diesem Branch nicht entwickelt. Die Entwicklung findet parallel auf develop statt. Ist die Entscheidung gefallen und unsere Software wird veröffentlicht, mergen wir den Release-Branch mit dem master, denn es handelt sich ja um einen Release und wir mergen ihn mit develop, da wir die Last-Minute-Änderungen in die Entwicklung integrieren möchten.

Man sieht, dass diese Branching-Strategie recht ausgeklügelt, aber dadurch auch ein wenig aufwändig ist. Somit eignet sie sich eher für größere Teams. Im Blog-Post „A successful Git branching model“ von Vincent Driessen kannst du das Originalkonzept zum Git-Flow-Modell nachlesen.

Fazit

Wichtig ist, dass klar kommuniziert wird, wie die jeweilige Branching-Strategie aussieht und dass sich alle Beteiligten daran halten. Dazu gehört beispielsweise auch die Festlegung, ob Commit-Messages auf Englisch oder Deutsch zu formulieren sind.

Darüber hinaus gilt es, die Namen der Branches so zu wählen, dass sie erkennbar und verständlich bleiben. Wer ein externes Tracking-Tool für Bugs und Feature einsetzt, sollte die Issue-Nummer des gerade bearbeiteten Tickets mit in den Branch-Namen aufnehmen, so dass die Zuordnung bei einem Überblick der aktiven Branches klar ist. Zur Nachvollziehbarkeit gehören außerdem sinnvolle und verständliche Commit-Messages, die durchaus ein paar Sätze lang sein dürfen, denn diese Messages sind für Menschen und nicht für Computer; das darf man nie vergessen.

Photo by Ilana Beer from Burst