Git
Chapters ▾ 2nd Edition

3.2 Git Branching - Einfaches Branching und Merging

Einfaches Branching und Merging

Lassen Sie uns ein einfaches Beispiel für das Verzweigen und Zusammenführen (engl. branching and merging) anschauen, wie es Ihnen in einem praxisnahen Workflow begegnen könnte. Führen Sie diese Schritte aus:

  1. Arbeiten Sie an einer Website

  2. Erstellen Sie einen Branch für eine neue Anwendergeschichte, an der Sie gerade arbeiten

  3. Erledigen Sie einige Arbeiten in diesem Branch

In diesem Moment erhalten Sie einen Anruf, dass ein anderes Problem kritisch ist und Sie einen Hotfix benötigen. Dazu werden Sie folgendes machen:

  1. Wechseln Sie zu Ihrer Produktions-Branch

  2. Erstellen Sie einen Branch, um den Hotfix einzufügen

  3. Nachdem der Test abgeschlossen ist, mergen Sie den Hotfix-Branch und schieben ihn in die Produktions-Branch

  4. Wechseln Sie zurück zu Ihrer ursprünglichen Anwenderstory und arbeiten Sie daran weiter

Einfaches Branching

Lassen Sie uns zunächst annehmen, Sie arbeiten an Ihrem Projekt und haben bereits ein paar Commits in Ihre master-Branch gemacht.

Ein einfacher Commit-Verlauf
Figure 18. Ein einfacher Commit-Verlauf

Sie haben sich dafür entschieden, an „Issue #53“ zu arbeiten aus irgendeinem Fehlerverfolgunssystem, das Ihre Firma benutzt. Um einen neuen Branch anzulegen und gleichzeitig zu diesem zu wechseln, können Sie 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
Figure 19. Erstellen eines neuen Branch-Zeigers

Sie arbeiten an Ihrer Website und führen einige Commits durch. Sobald Sie das machen, bewegt das den iss53-Branch vorwärts, weil Sie in ihn gewechselt (engl. checked out) haben. Das bedeutet, Ihr HEAD zeigt auf diesen Branch:

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

Jetzt bekommen Sie einen Anruf, dass es ein Problem mit der Website gibt und Sie es umgehend beheben müssen. Bei Git müssen Sie Ihren Fix nicht zusammen mit den Änderungen bereitstellen, die Sie bereits an iss53 vorgenommen haben, und Sie müssen auch keinen großen Aufwand damit betreiben, diese Änderungen rückgängig zu machen, bevor Sie daran arbeiten können, Ihren Fix auf das anzuwenden, was sich in der Produktionsumgebung befindet. Alles, was Sie machen müssen, ist, zu Ihrem master-Branch zurück zu wechseln.

Beachten Sie dabei, dass Git das Wechseln zu einer anderen Branch blockiert, falls Ihr Arbeitsverzeichnis oder Ihr Staging-Bereich nicht committete Modifikationen enthält, die Konflikte verursachen. Es ist am besten, einen sauberen Zustand des Arbeitsbereichs anzustreben, bevor Sie die Zweige wechseln. Es gibt Möglichkeiten, das zu umgehen (nämlich das Verstecken/Stashen und Revidieren/Amending von Änderungen), die wir später in Kapitel 7 Git Stashing behandeln werden. Lassen Sie uns vorerst annehmen, Sie haben für alle Ihre Änderungen Commits durchgeführt, sodass Sie zu Ihrem master-Branch zurück wechseln können.

$ 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 Sie mit der Arbeit an „Issue #53“ begonnen haben und Sie können sich direkt auf den Hotfix konzentrieren. Das ist ein wichtiger Punkt, den Sie unbedingt beachten sollten: Wenn Sie die Branches wechseln, setzt Git Ihr Arbeitsverzeichnis zurück, um so auszusehen, wie es das letzte Mal war, als Sie in den Branch committed haben. Dateien werden automatisch hinzugefügt, entfernt und verändert, um sicherzustellen, dass Ihre Arbeitskopie auf dem selben Stand ist wie zum Zeitpunkt Ihres letzten Commits auf diesem Branch.

Als Nächstes müssen Sie sich um den Hotfix kümmern. Lassen Sie uns einen hotfix-Branch erstellen, an dem Sie bis zu dessen Fertigstellung arbeiten:

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

Sie können Ihre Tests durchführen, sich vergewissern, dass der Hotfix das macht, was Sie von ihm erwarten und schließlich den Branch hotfix wieder in Ihren master-Zweig integrieren (engl. merge), um ihn in der Produktion einzusetzen. Das machen Sie mit der Anweisung git merge:

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

Ihnen wird bei diesem Zusammenführen der Ausdruck „fast-forward“ auffallen. Da der Commit C4, auf den der von Ihnen eingebundene Branch hotfix zeigt, direkt vor dem Commit C2 liegt, auf dem Sie sich befinden, bewegt Git den Pointer einfach nach vorne. Um es anders auszudrücken, wenn Sie versuchen, 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 abweichenden Arbeiten gibt, die miteinander gemergt werden müssen – das wird als „fast-forward“ bezeichnet.

Ihre Änderung befindet sich nun im Schnappschuss des Commits, auf den der master-Branch zeigt und Sie können Ihre Fehlerbehebung anwenden.

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

Nachdem Ihre überaus wichtige Fehlerbehebung bereitgestellt wurde, können Sie sich wieder dem zuwenden, woran Sie gerade gearbeitet haben, als Sie unterbrochen wurden. Zunächst sollten Sie jedoch den hotfix-Branch löschen, weil Sie diesen nicht länger benötigen – schließlich verweist der master-Branch auf den selben Entwicklungsstand. Sie können ihn mit der Anweisung git branch und der Option -d löschen:

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

Jetzt können Sie zu dem Branch zurückwechseln, auf dem Sie mit Ihren Arbeiten an „Issue #53“ begonnen hatten, und daran weiter arbeiten.

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

Es ist erwähnenswert, dass die Arbeit, die Sie in Ihrem hotfix-Branch durchgeführt haben, nicht in den Dateien in Ihrem iss53-Branch enthalten ist. Wenn Sie diese Änderungen übernehmen müssen, können Sie in Ihren master-Branch den iss53-Zweig einbinden indem Sie git merge master ausführen, oder Sie können warten, bis Sie sich später entscheiden, den iss53-Zweig wieder zurück nach master zu pullen.

Einfaches Merging

Angenommen, Sie haben entschieden, dass Ihr Issue #53 abgeschlossen ist und Sie bereit sind, ihn in Ihren „Master“ Branch zu integrieren. Dann werden Sie Ihren iss53-Branch in den master-Branch mergen, so wie Sie es zuvor mit dem hotfix-Branch gemacht haben. Sie müssen nur mit der Anweisung checkout zum dem Branch zu wechseln, in welchen Sie etwas einfließen lassen wollen 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, das Sie zuvor gemacht haben. Hier hat sich der Entwicklungsverlauf an einem früheren Zustand geteilt. Da der Commit auf dem Branch, auf dem Sie sich gerade befinden, kein unmittelbarer Vorgänger der Branch ist, in den Sie fusionieren, 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 Vorfahr der beiden zeigen.

Drei Schnappschüsse
Figure 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
Figure 25. Ein Merge-Commit

Da Ihre Änderungen jetzt eingeflossen sind, haben Sie keinen weiteren Bedarf mehr für den iss53-Branch. Sie können das Ticket in Ihrem Ticket-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 Sie in den beiden Branches, die Sie zusammenführen wollen, an der selben Stelle in der selben Datei unterschiedliche Änderungen vorgenommen haben, wird Git nicht in der Lage sein, diese sauber zusammenzuführen. Wenn Ihr Fix für „Issue #53“ den gleichen Teil einer Datei wie der Branch hotfix geändert hat, erhalten Sie 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 automatisch keinen neuen Merge-Commit erstellen. Es hat den Prozess angehalten, bis Sie den Konflikt beseitigt haben. Wenn Sie sehen möchten, welche Dateien nach einem Merge-Konflikt an einem bestimmten Punkt nicht zusammengeführt wurden, können Sie 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 Sie sie manuell öffnen und diese Konflikte lösen können. Ihre 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 (Ihres master-Branches, denn der wurde per checkout aktiviert, als Sie das merge gestartet haben) ist der obere Teil des Blocks (alles oberhalb von =======), und die Version aus dem iss53-Branch sieht wie der darunter befindliche Teil aus. Um den Konflikt zu lösen, müssen Sie sich entweder für einen der beiden Teile entscheiden oder Sie führen die Inhalte selbst zusammen. Sie können diesen Konflikt beispielsweise lösen, indem Sie 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 Sie alle problematischen Bereiche in allen von dem Konflikt betroffenen Dateien beseitigt haben, führen Sie 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 Sie ein grafisches Tool benutzen möchten, um die Probleme zu lösen, dann können Sie git mergetool verwenden, welches ein passendes grafisches Merge-Tool startet und Sie 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 Sie 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 können Sie alle unterstützten Werkzeuge oben – nach „one of the following tools“ – aufgelistet sehen. Tippen Sie einfach den Namen des gewünschten Programms ein.

Note

Wenn Sie fortgeschrittenere Werkzeuge zur Lösung kniffliger Merge-Konflikte benötigen, erfahren Sie mehr daüber in Kapitel 7 Fortgeschrittenes Merging.

Nachdem Sie das Merge-Tool beendet haben, werden Sie von Git gefragt, ob das Zusammenführen erfolgreich war. Wenn Sie dem Skript bestätigen, dass es das war, wird die Datei der Staging Area hinzugefügt und der Konflikt als gelöst zu markiert. Sie können 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 Sie damit zufrieden sind und Sie geprüft haben, dass alles, was Konflikte aufwies, zum Index hinzugefügt wurde, können Sie 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
#

Sie können dieser Commit-Nachricht noch Details darüber hinzufügen, wie Sie diesen Merge-Konflikt gelöst haben. Es könnte für künftige Betrachter dieses Commits hilfreich sein zu verstehen, warum Sie was getan haben, falls es nicht offensichtlich ist.