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. Stellen sie sich vor, sie führen folgende Schritte aus:

  1. Sie arbeiten an einer Website

  2. Sie erstellen einen Branch für eine neue Anwendergeschichte (engl. User Story), an der Sie gerade arbeiten

  3. Sie 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 tun:

  1. Sie wechseln zu Ihrem Produktions-Branch

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

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

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

Einfaches Branching

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

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

Sie haben sich dafür entschieden, an „Issue #53“ aus irgendeinem Fehlerverfolgungssystem, das Ihre Firma benutzt, zu arbeiten. 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
Abbildung 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 'Create new footer [issue 53]'
Der `iss53` Branch hat sich bei Ihrer Arbeit vorwärts bewegt
Abbildung 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. Sie müssen auch keinen großen Aufwand betreiben, diese Änderungen rückgängig zu machen, bevor Sie an den neuen Hotfix arbeiten können, um die Produktionsumgebung zu fixen. Alles, was Sie machen müssen, ist, zu Ihrem vorherigen master Branch zu wechseln.

Beachten Sie dabei, dass Git das Wechseln zu einem anderen Branch blockiert, falls Ihr Arbeitsverzeichnis oder Ihr Staging-Bereich nicht committete Modifikationen enthält, die Konflikte verursachen. Generell ist es am besten, einen sauberen Zustand des Arbeitsbereichs anzustreben, bevor Sie Branches wechseln. 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. Lassen Sie uns vorerst annehmen, Sie haben für alle Ihre Änderungen Commits durchgeführt, sodass Sie zu Ihrem vorherigen master Branch 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 demselben 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 '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

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 Branch 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“
Abbildung 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 denselben 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 vorherigen Branch wechseln, 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 '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 Sie in Ihrem hotfix Branch durchgeführt haben, nicht in den Dateien ihres iss53 Branch enthalten sind. Wenn Sie diese Änderungen übernehmen wollen, können Sie Ihrem master Branch in den iss53 Branch übernehmen, indem Sie git merge master ausführen. Sie können aber auch warten und sich später dazu entscheiden, den iss53 Branch in master zu übernehmen (engl. pullen).

Einfaches Merging

Angenommen, Sie haben entschieden, dass Ihr Issue #53 abgeschlossen ist und Sie bereit sind, ihn in Ihren Branch master 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 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, welches 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 des Branches ist, in den Sie mergen, 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 Ihre Änderungen jetzt eingeflossen sind, haben Sie keinen weiteren Bedarf mehr für den iss53 Branch. Sie können den Issue in Ihrem 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 Sie in den beiden Branches, die Sie zusammenführen wollen, an derselben Stelle in derselben 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 einen neuen Merge-Commit nicht automatisch erstellen. Es hat den Prozess angehalten, bis Sie den Konflikt beseitigt haben. Wenn Sie sehen möchten, welche Dateien zu irgendeinem Zeitpunkt nach einem Merge-Konflikt 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 (das ist der aktuelle commit master Branches, denn der wurde per checkout aktiviert, als Sie den Merge gestartet haben) 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, 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 sehen, die oben nach „one of the following tools“ aufgelistet sind. Tippen Sie einfach den Namen des gewünschten Programms ein.

Anmerkung

Wenn Sie fortgeschrittenere Werkzeuge zur Lösung kniffliger Merge-Konflikte benötigen, erfahren Sie mehr darü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 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, der Staging-Area 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.

scroll-to-top