Git
Chapters ▾ 2nd Edition

3.2 Git Branching - Einfaches Branching und Merging

Einfaches Branching und Merging

Lass uns ein einfaches Beispiel für das Verzweigen und Zusammenführen (engl. branching and merging) anschauen, wie es dir in einem praxisnahen Workflow begegnen könnte. Stell dir vor, du führst folgende Schritte aus:

  1. Du arbeitest an einer Website

  2. Du erstellst einen Branch für eine neue Anwendergeschichte (engl. User Story), an der du gerade arbeitest

  3. Du erledigst einige Arbeiten in diesem Branch

In diesem Moment erhältst du einen Anruf, dass ein anderes Problem kritisch ist und ein Hotfix benötigt wird. Dazu wirst du folgendes tun:

  1. Du wechselst zu deinem Produktions-Branch

  2. Du erstellst einen Branch, um den Hotfix einzufügen

  3. Nachdem der Test abgeschlossen ist, mergst du den Hotfix-Branch und schiebst ihn in den Produktions-Branch

  4. Du wechselst zurück zu deiner ursprünglichen Anwenderstory und arbeitest daran weiter

Einfaches Branching

Lass uns zunächst annehmen, du arbeitest an deinem Projekt und hast bereits ein paar Commits in deinem master Branch gemacht.

Ein einfacher Commit-Verlauf
Abbildung 18. Ein einfacher Commit-Verlauf

Du hast dich dafür entschieden, an „Issue #53“ aus irgendeinem Fehlerverfolgungssystem, das deine Firma benutzt, zu arbeiten. Um einen neuen Branch anzulegen und gleichzeitig zu diesem zu wechseln, kannst du die Anweisung git checkout zusammen mit der Option -b ausführen:

$ git checkout -b iss53
Switched to a new branch "iss53"

Das ist die Kurzform der beiden folgenden Befehle:

$ git branch iss53
$ git checkout iss53
Erstellen eines neuen Branch-Zeigers
Abbildung 19. Erstellen eines neuen Branch-Zeigers

Du arbeitest an deiner Website und führst einige Commits durch. Sobald du das machst, bewegt das den iss53 Branch vorwärts, weil du in ihn gewechselt (engl. checked out) bist. Das bedeutet, dein HEAD zeigt auf diesen Branch:

$ vim index.html
$ git commit -a -m 'Create new footer [issue 53]'
Der `iss53` Branch hat sich bei deiner Arbeit vorwärts bewegt
Abbildung 20. Der iss53 Branch hat sich bei deiner Arbeit vorwärts bewegt

Jetzt bekommst du einen Anruf, dass es ein Problem mit der Website gibt und du es umgehend beheben musst. Bei Git musst du deinen Fix nicht zusammen mit den Änderungen bereitstellen, die du bereits an iss53 vorgenommen hast. Du musst auch keinen großen Aufwand betreiben, diese Änderungen rückgängig zu machen, bevor du an den neuen Hotfix arbeiten kannst, um die Produktionsumgebung zu fixen. Alles, was du machen musst, ist zu deinem vorherigen master Branch zu wechseln.

Beachten dabei, dass Git das Wechseln zu einem anderen Branch blockiert, falls dein Arbeitsverzeichnis oder dein Staging-Bereich nicht committete Modifikationen enthält, die Konflikte verursachen. Generell ist es am besten, einen sauberen Zustand des Arbeitsbereichs anzustreben, bevor du Branches wechselst. Es gibt Möglichkeiten, das zu umgehen (nämlich das Verstecken (engl. Stashen) und Revidieren (engl. Amending) von Änderungen), die wir später in Kapitel 7 Git Stashing behandeln werden. Lass uns vorerst annehmen, du hast für alle deine Änderungen Commits durchgeführt, sodass du zu deinem vorherigen master Branch wechseln kannst.

$ git checkout master
Switched to branch 'master'

Zu diesem Zeitpunkt befindet sich das Arbeitsverzeichnis des Projektes in exakt dem gleichen Zustand, in dem es sich befand, bevor du mit der Arbeit an „Issue #53“ begonnen hast und du kannst dich direkt auf den Hotfix konzentrieren. Das ist ein wichtiger Punkt, den du unbedingt beachten solltest: Wenn du die Branches wechselst, setzt Git dein Arbeitsverzeichnis zurück, um so auszusehen, wie es das letzte Mal war, als du in den Branch committed hast. Dateien werden automatisch hinzugefügt, entfernt und verändert, um sicherzustellen, dass deine Arbeitskopie auf demselben Stand ist wie zum Zeitpunkt deines letzten Commits auf diesem Branch.

Als Nächstes musst du dich um den Hotfix kümmern. Lass uns einen hotfix Branch erstellen, an dem du bis zu dessen Fertigstellung arbeitest:

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'Fix broken email address'
[hotfix 1fb7853] Fix broken email address
 1 file changed, 2 insertions(+)
Auf dem `master` Branch basierender Hotfix-Branch
Abbildung 21. Auf dem master Branch basierender Hotfix-Branch

Du kannst deine Tests durchführen, dich vergewissern, dass der Hotfix das macht, was du von ihm erwartest und schließlich den Branch hotfix wieder in deinen master Branch integrieren (engl. merge), um ihn in der Produktion einzusetzen. Das machst du mit der Anweisung git merge:

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

Dir wird bei diesem Zusammenführen der Ausdruck „fast-forward“ auffallen. Da der Commit C4, auf den der von dir eingebundene Branch hotfix zeigt, direkt vor dem Commit C2 liegt, auf dem du dich befindest, bewegt Git den Pointer einfach nach vorne. Um es anders auszudrücken: Wenn du versuchst, einen Commit mit einem Commit zusammenzuführen, der durch Verfolgen der Historie des ersten Commits erreicht werden kann, vereinfacht Git die Dinge, indem er den Zeiger nach vorne bewegt, da es keine verzweigte Arbeiten gibt, die miteinander gemerged werden müssen – das wird als „fast-forward“ bezeichnet.

Deine Änderung befindet sich nun im Schnappschuss des Commits, auf den der master Branch zeigt und du kannst deinen Fix ausliefern und installieren.

`master` wurde zu `hotfix` „fast-forwarded“
Abbildung 22. master wurde zu hotfix „fast-forwarded“

Nachdem deine überaus wichtiger Fix bereitgestellt wurde, kannst du dich wieder dem zuwenden, woran du vorher gearbeitet hast. Zunächst solltest du jedoch den hotfix Branch löschen, weil du diesen nicht länger benötigst – schließlich verweist der master Branch auf denselben Entwicklungsstand. Du kannst ihn mit der Anweisung git branch und der Option -d löschen:

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

Jetzt kannst du zum vorherigen Branch wechseln, auf dem du mit deinen Arbeiten an „Issue #53“ begonnen hattest und daran weiter arbeiten.

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'Finish the new footer [issue 53]'
[iss53 ad82d7a] Finish the new footer [issue 53]
1 file changed, 1 insertion(+)
Arbeiten an `iss53` fortsetzen
Abbildung 23. Arbeiten an iss53 fortsetzen

Erwähnenswert ist, dass die Änderungen, die du in deinem hotfix Branch durchgeführt hast, nicht in den Dateien deines iss53 Branch enthalten sind. Wenn du diese Änderungen übernehmen willst, kannst du deinen master Branch in den iss53 Branch mergen, indem du git merge master ausführst. Du kannst aber auch warten und dich später dazu entscheiden, den iss53 Branch in master zu übernehmen (engl. pullen).

Einfaches Merging

Angenommen, du hast dich entschieden, dass dein Issue #53 abgeschlossen ist und du bereit bist, ihn in deinem Branch master zu mergen. Dann wirst du deinen iss53 Branch in den master Branch mergen, so wie du es zuvor mit dem hotfix Branch gemacht hast. Du musst nur mit der Anweisung checkout zum Branch wechseln, in welchen du etwas einfließen lassen willst und dann die Anweisung git merge ausführen:

$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

Das sieht ein bisschen anders aus, als das Merging mit dem hotfix Branch, welches du zuvor gemacht hast. Hier hat sich der Entwicklungsverlauf an einem früheren Zustand geteilt. Da der Commit auf dem Branch, auf dem du dich gerade befindest, kein unmittelbarer Vorgänger des Branches ist, in den du mergst, muss Git einige Arbeiten erledigen. In diesem Fall führt Git einen einfachen Drei-Wege-Merge durch, indem er die beiden Schnappschüsse verwendet, auf die die Branch-Spitzen und der gemeinsame Vorgänger der beiden zeigen.

Drei Schnappschüsse
Abbildung 24. Drei Schnappschüsse, die bei einem typischen merge benutzt werden

Anstatt einfach den Zeiger des Branches vorwärts zu bewegen, erstellt Git einen neuen Schnappschuss, der aus dem Drei-Wege-Merge resultiert und erzeugt automatisch einen neuen Commit, der darauf zeigt. Das wird auch als Merge-Commit bezeichnet und ist ein Spezialfall, weil er mehr als nur einen Vorgänger hat.

Ein Merge-Commit
Abbildung 25. Ein Merge-Commit

Da deine Änderungen jetzt eingeflossen sind, hast du keine weitere Verwendung mehr für den iss53 Branch. Du kannst den Issue in deinem Issue-Tracking-System schließen und den Branch löschen:

$ git branch -d iss53

Einfache Merge-Konflikte

Gelegentlich verläuft der Merge-Prozess nicht ganz reibungslos. Wenn du in den beiden Branches, die du zusammenführen willst, an derselben Stelle in derselben Datei unterschiedliche Änderungen vorgenommen hast, wird Git nicht in der Lage sein, diese sauber zusammenzuführen. Wenn dein Fix für „Issue #53“ den gleichen Teil einer Datei wie der Branch hotfix geändert hat, erhältst du einen Merge-Konflikt, der ungefähr so aussieht:

$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

Git konnte einen neuen Merge-Commit nicht automatisch erstellen. Es hat den Prozess angehalten, bis du den Konflikt beseitigt hast. Wenn du sehen möchtest, welche Dateien zu irgendeinem Zeitpunkt nach einem Merge-Konflikt nicht zusammengeführt wurden, kannst du git status ausführen:

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

Alles, was Merge-Konflikte ausgelöst hat und nicht behoben wurde, wird als unmerged angezeigt. Git fügt den Dateien, die Konflikte haben, Standardmarkierungen zur Konfliktlösung hinzu, so dass du sie manuell öffnen und diese Konflikte lösen können. Deine Datei enthält einen Bereich, der in etwa so aussieht:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

Das bedeutet, die Version in HEAD (das ist der aktuelle commit master Branches, denn der wurde per checkout aktiviert, als du den Merge gestartet hast) ist der obere Teil des Blocks (alles oberhalb von =======) und die Version aus dem iss53 Branch ist in dem darunter befindliche Teil. Um den Konflikt zu lösen, musst du dich entweder für einen der beiden Teile entscheiden oder du führst die Inhalte selbst zusammen. Du kannst diesen Konflikt beispielsweise lösen, indem du den gesamten Block durch diesen ersetzen:

<div id="footer">
please contact us at email.support@github.com
</div>

Diese Lösung hat von beiden Teilen etwas und die Zeilen mit <<<<<<<, ======= und >>>>>>> wurden vollständig entfernt. Nachdem du alle problematischen Bereiche in allen von dem Konflikt betroffenen Dateien beseitigt hast, führst du einfach die Anweisung git add für alle betroffenen Dateien aus, um sie als gelöst zu markieren. Dieses ‚Staging‘ der Dateien markiert sie für Git als bereinigt.

Wenn du ein grafisches Tool benutzen möchtest, um die Probleme zu lösen, dann kannst du git mergetool verwenden, welches ein passendes grafisches Merge-Tool startet und dich durch die Konfliktbereiche führt:

$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

Wenn du ein anderes Merge-Tool anstelle des Standardwerkzeugs verwenden möchten (Git wählte in diesem Fall opendiff, da die Anweisung auf einem Mac ausgeführt wurde), dann kannst du alle unterstützten Werkzeuge sehen, die oben nach „one of the following tools“ aufgelistet sind. Tippe einfach den Namen des gewünschten Programms ein.

Anmerkung

Wenn du fortgeschrittenere Werkzeuge zur Lösung kniffliger Merge-Konflikte benötigst, erfährst du mehr darüber in Kapitel 7 Fortgeschrittenes Merging.

Nachdem du das Merge-Tool beendet hast, wirst du von Git gefragt, ob das Zusammenführen erfolgreich war. Wenn du das bestätigst, wird die Datei der Staging-Area hinzugefügt und der Konflikt als gelöst markiert. Du kannst den Befehl git status erneut ausführen, um zu überprüfen, ob alle Konflikte gelöst wurden:

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

Wenn du damit zufrieden bist und geprüft hast, dass alles, was Konflikte aufwies, der Staging-Area hinzugefügt wurde, kannst du die Anweisung git commit ausführen, um den Merge-Commit abzuschließen. Die standardmäßige Commit-Nachricht sieht ungefähr so aus:

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#	.git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#	modified:   index.html
#

Du kannst diese Commit-Nachricht noch Details darüber hinzufügen, wie du diesen Merge-Konflikt gelöst hast. Es könnte für künftige Betrachter dieses Commits hilfreich sein, zu verstehen, warum du etwas getan hast, falls es nicht offensichtlich ist.

scroll-to-top