Git-Tutorial: Branches in Git erstellen, bearbeiten und verschmelzen

Autor / Redakteur: Mirco Lang / Stephan Augsten

Dieser letzte Teil unserer Git-Serie für Einsteiger gibt einen Einblick in tiefergreifende Mechanismen der Versionierung. Wir zeigen, wie sich ein Entwicklungsstrang grundsätzlich verästeln und anschließend wieder verschmelzen lässt.

Anbieter zum Thema

Branches erlauben es, neue, experimentelle Features für ein Projekt zu entwickeln und bei Gefallen mit dem Hauptzweig zu verschmelzen.
Branches erlauben es, neue, experimentelle Features für ein Projekt zu entwickeln und bei Gefallen mit dem Hauptzweig zu verschmelzen.
(© dekzer007 - stock.adobe.com)

Im ersten Teil des Git-Tutorials haben Sie gelernt, wie Sie lokal mit Git arbeiten, Änderungen vornehmen und committen. Im zweiten Teil kam die Teamarbeit hinzu und Sie haben Ihr Arbeit mit anderen geteilt und eventuelle Konflikte gelöst. Wenn Sie alleine mit Git arbeiten oder in einem sehr, sehr kleinen Team, könnten Sie mit diesem Wissen bereits ganz praktisch mit Git arbeiten.

Beide Teile endeten mit Befehlen, in denen „HEAD“ vorkam – und hier braucht es jetzt doch ein wenig Theorie, wie Git überhaupt intern arbeitet. In diesem letzten Teil der Einsteigerserie bekommen Sie einen kurzen Einblick in die Git-Interna und lernen, wie Sie Ihren Entwicklungsstrang ganz grundsätzlich verästeln und anschließend wieder verschmelzen.

Verästeln heißt, dass Sie einen neuen „Branch“ anlegen, um beispielsweise neue, experimentelle Features für Ihr Projekt zu entwickeln. Bringen diese nicht den gewünschten Erfolg, wurde der Hautpstrang nicht gestört, gelingen sie, können sie mit dem Hauptstrang verschmolzen werden. Man nutzt quasi eine Sandbox auf Basis des aktuellen Entwicklungsstands, um in eine andere Richtung weiter zu entwickeln – und die Änderungen können dann in die Hauptversion übernommen werden oder in eine separate Version einfließen.

Aber zunächst als Einführung nochmal die beiden HEAD-Befehle aus den vorigen Teilen:

git checkout HEAD^ -- test

… und …

git reset HEAD^^^

Der checkout-Befehl würde hier die Datei „test“ nicht in der aktuellen, sondern der davor committeten Version wiederherstellen. Und der reset-Befehl würde Ihren ganzen Entwicklungsstrang um drei Commits zurückstellen.

Wie arbeitet Git?

Die kompletten Git-Interna zu erklären verschlingt ganze (dicke!) Bücher und ist für ein Tutorial nicht unbedingt erforderlich. Im Laufe der Zeit werden Aufgaben und Probleme auftauchen, für die Sie immer wieder nach Lösungen suchen müssen. Daraus ergibt sich nach und nach das Gesamtbild. Wir beschränken uns auf das Nötigste.

Wie in Teil 1 erwähnt, sind Commits wie Snapshots zu verstehen. Genauer gesagt werden beim Committen mehrere Elemente gespeichert: Das Commit-Object enthält die Commit-Nachricht sowie Namen und Mail-Adresse des Autors und verweist auf den eigentlichen Snapshot. Dieser enthält wiederum die Namen der geänderten Dateien und verweist seinerseits auf diese Dateien, deren Inhalte als so genannte Blobs gespeichert werden. Neue Commits verweisen zusätzlich auf den vorherigen Commit (das Verweisen/Zeigen übernehmen in Git die so genannten „Pointer“).

Stellen Sie sich Ihre Arbeit einfach als eine lange Liste von Commits vor. Diese erste, einfache Liste ist bereits ein Branch, nämlich der Master-Branch – daher auch das Wörtchen „master“ in Ihrem Git-Prompt. Der Master wird dabei auch wieder als Pointer gesehen, der eben auf den letzten Commit des Branches zeigt.

Wenn Sie nun einen neuen Branch „Testing“ anlegen, zeigt Testing genau wie Master auf denselben letzten Commit – noch gibt es ja keine unterschiedlichen Entwicklungen. Der Master-Branch wird von Git genauso behandelt wie jeder andere Branch, die einzige Besonderheit besteht darin, dass er durch „git init“ automatisch angelegt wird.

Und jetzt kommt endlich das zitierte HEAD ins Spiel: Woher weiß Git, ob Sie nun in der Entwicklungslinie Master oder der Linie Testing arbeiten wollen? Natürlich wieder durch einen Pointer, nämlich HEAD. HEAD zeigt immer auf einen bestimmten Commit eines bestimmten Branches. Würden Sie also auf den erzeugten Branch Testing wechseln, würde erst mal einfach nur der HEAD-Zeiger von Master auf Testing umgesetzt.

Die eigentliche Verzweigung entsteht dann durch Änderungen. Änderungen erstellen Sie dort, wo aktuell der HEAD ist und dieser Branch wächst mit jedem Commit weiter. Wenn Sie einen ersten Commit im Branch Testing erzeugen, gibt es noch keine Abspaltung – die Zeiger Testing und HEAD wären einfach einen Commit weiter als der Zeiger Master, es gibt nach wie vor nur einen Strang von Commits.

Wenn Sie nun aber zurück zum Master-Branch wechseln und einen neuen Commit erstellen, verzweigt sich dieser eine Strang natürlich. Ausgehend vom letzten gemeinsamen Commit, gibt es dann einen eigenständigen Branch Master und einen Branch Testing. Und natürlich lassen sich diese Zweige später wieder verschmelzen.

In der Praxis könnte das zum Beispiel so aussehen, dass Teilprojekte komplett auf eigenen Branches entwickeln und später in den Master-Branch eingebaut werden.

Die Praxis

Spielen Sie das Prozedere am besten einmal durch, um die ganze Theorie auch mal in Aktion zu sehen und die Befehle kennenzulernen. Sie benötigen einfach ein leeres, lokales Repository, also beispielsweise per:

git init branching

Im Repository Branching erstellen Sie erst mal nur eine einzelne Testdatei mit …

touch test.txt

…, adden sie mittels …

git add test.txt

… und committen die Änderungen via:

git commit -m „testcommit

Um gleich vergleichen zu können, rufen Sie noch das Log mit …

git log --graph –decorate

… auf; die beiden Optionen sorgen für eine hübschere Darstellung.

Erstellen Sie nun einen neuen Branch „Testing“:

git branch testing

Wechseln Sie dann zum neuen Branch über:

git checkout testing“

Führen Sie wieder …

git log --graph –decorate

…. aus und vergleichen Sie die Ausgabe mit dem Log-Aufruf aus dem Master-Branch: Sie sehen denselben Status und direkt in der ersten Zeile, dass HEAD sowohl auf den Branch Testing als auch auf Master zeigt.

HEAD zeigt auf den Branch „testing“, der „master“-Branch liegt einen Commit zurück.
HEAD zeigt auf den Branch „testing“, der „master“-Branch liegt einen Commit zurück.
(Bild: Lang / Git)

Legen Sie nun, immer noch im Testing-Branch, eine Datei an, adden und committen Sie wieder. Rufen Sie abermals das Log auf. Jetzt hat sich die Situation verändert: HEAD liegt wie immer in der ersten Zeile beim letzten Commit, allerdings ist nur noch der Branch Testing angegeben. Der Branch Master liegt hingegen immer noch beim selben Commit wie vorher, also einen Eintrag zurück.

Wechseln Sie wieder mit dem checkout-Befehl zum Master-Branch. (Zum späteren Vergleich: Schauen Sie mit „ls“ in das Arbeitsverzeichnis – die angelegte zweite Testdatei ist nicht vorhanden.) Jetzt können Sie den Testing-Branch verschmelzen:

git merge testing

Ein Blick in das Log zeigt sofort, dass die Änderungen aus dem Testing-Branch übernommen wurden und HEAD wieder auf beide Branches zeigt.

Ganz wichtig: Wenn Sie sich noch mal mit „ls“ den Inhalt Ihres Arbeitsverzeichnisses anschauen, sehen Sie natürlich auch die zum letzten Commit gehörende zweite Testdatei. Der checkout-Befehl macht zwei Dinge: Zum einen wird HEAD auf den letzten Commit des Branches gesetzt, zum anderen wird das Arbeitsverzeichnis zurückgesetzt. Und genau so haben Sie Zugriff auf alte Versionen von Dateien: Sie setzen HEAD auf einen Commit von vor zwei Jahren und schon finden Sie im Arbeitsverzeichnis auch die Dateiversionen von vor zwei Jahren.

Damit nochmal zu den beiden Befehlen vom Anfang: Der checkout-Befehl kann nicht nur zwischen Branches wechseln, sondern auch Dateien auschecken:

git checkout HEAD^ -- test.txt

… stellt die Version der Datei „test.txt“ einen Commit vor der aktuellen Position (^) von HEAD wieder her. Damit wird also die letzte Änderung rückgängig gemacht – die Datei muss dann natürlich wieder in dieser korrigierten Version committed werden, will man sie denn in dieser Version weiterführen.

Der zweite Befehl führt etwas Neues ein:

git reset HEAD^^^

Man ahnt es, der Befehl setzt HEAD um drei Commits zurück. Vorsicht: Sobald Sie nun erneut irgendetwas committen, sind die drei zurückgesprungenen Commits endgültig weg. Andererseits: Der reset-Befehl passt in seiner Grundform im Gegensatz zum checkout-Befehl nicht das Arbeitsverzeichnis an, die Dateien sind noch vorhanden – und könnten nun erneut geaddet und committed werden. Auf diese Art könnte man zum Beispiel drei einzelne Commits nachträglich als einen einzigen absetzen.

Wie geht es weiter?

Sie haben in unserer kurzen Tutorial-Reihe gelernt, wie sich Git grundsätzlich bedienen lässt, wie Sie einen einfachen lokalen Workflow durchführen, Repos mit anderen Teilen und in diesem Teil, am Beispiel von Branches, wie Git eigentlich intern arbeitet.

In Gitk zeigt sich das Chaos, das sich schon mit wenigen Branches anrichten lässt.
In Gitk zeigt sich das Chaos, das sich schon mit wenigen Branches anrichten lässt.
(Bild: Lang / Git)

Fast alles, was Sie an Befehlen und Möglichkeiten gesehen haben, bietet noch etliche weitere Optionen und mögliche Probleme – Branches in verteilten Umgebungen und mit vielen Beteiligten sind kein Selbstläufer. Aber Sie sollten weit genug gekommen sein, um konkreten Probleme über die Referenz oder die vielen kurzen, bündigen Antworten auf Stackoverflow zu lösen.

Ein Tipp zum Schluss: Erstellen Sie sich während der Einarbeitung auf jeden Fall eine Liste mit neu gelernten und/oder ganz praktisch genutzten Befehlen – wenn sich die Basics einmal eingebrannt haben, können Sie wunderbar streichen und sich ein persönliches Cheat-Sheet erstellen.

(ID:45379794)