Kapitel ▾ 2. Auflage

3.2 Git Branching - Grundlegendes Branching und Merging

Grundlegendes Branching und Merging

Wir gehen ein einfaches Beispiel für Branching und Merging mit einem Workflow durch, den Sie in der Praxis verwenden könnten. Sie werden die folgenden Schritte ausführen:

  1. Arbeiten Sie an einer Website.

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

  3. Arbeiten Sie in diesem Branch.

Zu diesem Zeitpunkt erhalten Sie einen Anruf, dass ein anderes Problem kritisch ist und Sie einen Hotfix benötigen. Sie werden Folgendes tun:

  1. Wechseln Sie zu Ihrem Produktionsbranch.

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

  3. Nachdem er getestet wurde, mergen Sie den Hotfix-Branch und pushen ihn in die Produktion.

  4. Wechseln Sie zurück zu Ihrer ursprünglichen User Story und arbeiten Sie weiter.

Grundlegendes Branching

Zunächst sagen wir, dass Sie an Ihrem Projekt arbeiten und bereits ein paar Commits auf dem master-Branch haben.

A simple commit history
Abbildung 18. Eine einfache Commit-Historie

Sie haben beschlossen, an Issue #53 in welchem Issue-Tracking-System Ihr Unternehmen auch immer verwendet, zu arbeiten. Um einen neuen Branch zu erstellen und gleichzeitig zu ihm zu wechseln, können Sie den Befehl git checkout mit dem Schalter -b ausführen.

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

Dies ist eine Kurzform für

$ git branch iss53
$ git checkout iss53
Creating a new branch pointer
Abbildung 19. Erstellen eines neuen Branch-Pointers

Sie arbeiten an Ihrer Website und machen ein paar Commits. Dadurch bewegt sich der iss53-Branch vorwärts, da Sie ihn ausgecheckt haben (d.h. Ihr HEAD zeigt darauf).

$ vim index.html
$ git commit -a -m 'Create new footer [issue 53]'
The `iss53` branch has moved forward with your work
Abbildung 20. Der iss53-Branch hat sich mit Ihrer Arbeit weiterentwickelt.

Jetzt erhalten Sie den Anruf, dass es ein Problem mit der Website gibt und Sie es sofort beheben müssen. Mit Git müssen Sie Ihren Fix nicht zusammen mit den Änderungen für iss53 deployen, und Sie müssen nicht viel Aufwand betreiben, um diese Änderungen zurückzusetzen, bevor Sie Ihren Fix auf das, was in der Produktion ist, anwenden können. Alles, was Sie tun müssen, ist, zu Ihrem master-Branch zurückzuwechseln.

Beachten Sie jedoch, dass Git Sie nicht zu einem anderen Branch wechseln lässt, wenn Ihr Arbeitsverzeichnis oder Ihr Staging-Bereich nicht committete Änderungen enthält, die mit dem auszucheckenden Branch in Konflikt stehen. Es ist am besten, einen sauberen Arbeitszustand zu haben, wenn Sie zwischen Branches wechseln. Es gibt Wege, dies zu umgehen (nämlich Stashing und Commit Amending), die wir später in Stashing und Bereinigen behandeln werden. Vorerst gehen wir davon aus, dass Sie alle Ihre Änderungen committet haben, sodass Sie zu Ihrem master-Branch zurückwechseln können.

$ git checkout master
Switched to branch 'master'

An diesem Punkt ist Ihr Projektarbeitsverzeichnis genau so, wie es vor Beginn der Arbeit an Issue #53 war, und Sie können sich auf Ihren Hotfix konzentrieren. Dies ist ein wichtiger Punkt, den Sie sich merken sollten: Wenn Sie zu einem anderen Branch wechseln, setzt Git Ihr Arbeitsverzeichnis zurück, damit es so aussieht wie beim letzten Commit auf diesem Branch. Es fügt Dateien hinzu, entfernt sie und modifiziert sie automatisch, um sicherzustellen, dass Ihre Arbeitskopie dem Zustand des Branches bei Ihrem letzten Commit entspricht.

Als Nächstes müssen Sie einen Hotfix durchführen. Lassen Sie uns einen hotfix-Branch erstellen, an dem wir arbeiten, bis er abgeschlossen ist.

$ 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(+)
Hotfix branch based on `master`
Abbildung 21. Hotfix-Branch basierend auf master

Sie können Ihre Tests ausführen, sicherstellen, dass der Hotfix das ist, was Sie wollen, und schließlich den hotfix-Branch zurück in Ihren master-Branch mergen, um ihn in die Produktion zu deployen. Dies tun Sie mit dem Befehl git merge.

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

Sie werden in diesem Merge die Phrase "fast-forward" bemerken. Da der Commit C4, auf den der Branch hotfix, den Sie gemergt haben, gezeigt hat, direkt vor dem Commit C2 lag, auf dem Sie sich befinden, verschiebt Git den Zeiger einfach nach vorne. Anders ausgedrückt: Wenn Sie versuchen, einen Commit mit einem Commit zu mergen, der durch Verfolgen der Historie des ersten Commits erreicht werden kann, vereinfacht Git die Dinge, indem es den Zeiger nach vorne bewegt, da keine divergierenden Arbeiten zusammengeführt werden müssen – dies wird als "Fast-Forward" bezeichnet.

Ihre Änderung befindet sich nun im Snapshot des Commits, auf den der master-Branch zeigt, und Sie können den Fix deployen.

`master` is fast-forwarded to `hotfix`
Abbildung 22. master wird auf hotfix vorwärtsgeschoben.

Nachdem Ihr wichtiger Fix deployed wurde, sind Sie bereit, zu der Arbeit zurückzukehren, die Sie vor Ihrer Unterbrechung geleistet haben. Zuerst löschen Sie jedoch den hotfix-Branch, da Sie ihn nicht mehr benötigen – der master-Branch zeigt auf dieselbe Stelle. Sie können ihn mit der Option -d zu git branch löschen.

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

Nun können Sie zu Ihrem Branch mit der laufenden Arbeit an Issue #53 zurückwechseln und daran weiterarbeiten.

$ 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(+)
Work continues on `iss53`
Abbildung 23. Die Arbeit an iss53 wird fortgesetzt.

Es ist erwähnenswert, dass die Arbeit, die Sie in Ihrem hotfix-Branch geleistet haben, nicht in den Dateien Ihres iss53-Branches enthalten ist. Wenn Sie sie übernehmen müssen, können Sie Ihren master-Branch in Ihren iss53-Branch mergen, indem Sie git merge master ausführen, oder Sie können warten, bis Sie diese Änderungen integrieren, bis Sie beschließen, den iss53-Branch später wieder in master zu mergen.

Grundlegendes Merging

Angenommen, Sie haben beschlossen, dass Ihre Arbeit an Issue #53 abgeschlossen ist und bereit ist, in Ihren master-Branch gemergt zu werden. Um dies zu tun, werden Sie Ihren iss53-Branch in master mergen, ähnlich wie Sie Ihren hotfix-Branch zuvor gemergt haben. Alles, was Sie tun müssen, ist, den Branch auszuchecken, in den Sie mergen möchten, und dann den Befehl git merge auszufü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(+)

Dies sieht etwas anders aus als der hotfix-Merge, den Sie zuvor durchgeführt haben. In diesem Fall ist Ihre Entwicklungshistorie von einem älteren Punkt abgewichen. Da der Commit auf dem Branch, den Sie gerade auschecken, kein direkter Vorgänger des Branches ist, den Sie mergen, muss Git einige Arbeit leisten. In diesem Fall führt Git einen einfachen Drei-Wege-Merge durch, wobei die beiden Snapshots, auf die die Branch-Spitzen zeigen, und der gemeinsame Vorfahre der beiden verwendet werden.

Three snapshots used in a typical merge
Abbildung 24. Drei Snapshots, die bei einem typischen Merge verwendet werden.

Anstatt nur den Branch-Zeiger nach vorne zu bewegen, erstellt Git einen neuen Snapshot, der aus diesem Drei-Wege-Merge resultiert, und erstellt automatisch einen neuen Commit, der darauf zeigt. Dies wird als Merge-Commit bezeichnet und ist besonders, da er mehr als einen Elternteil hat.

A merge commit
Abbildung 25. Ein Merge-Commit

Nachdem Ihre Arbeit gemergt wurde, benötigen Sie den iss53-Branch nicht mehr. Sie können das Issue in Ihrem Issue-Tracking-System schließen und den Branch löschen.

$ git branch -d iss53

Grundlegende Merge-Konflikte

Gelegentlich verläuft dieser Prozess nicht reibungslos. Wenn Sie denselben Teil derselben Datei in den beiden zu mergenden Branches unterschiedlich geändert haben, kann Git sie nicht sauber mergen. Wenn Ihre Korrektur für Issue #53 denselben Teil einer Datei wie der hotfix-Branch modifiziert hat, erhalten Sie einen Merge-Konflikt, der in etwa wie folgt 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 hat keinen neuen Merge-Commit automatisch erstellt. Es hat den Prozess pausiert, während Sie den Konflikt lösen. Wenn Sie zu irgendeinem Zeitpunkt nach einem Merge-Konflikt sehen möchten, welche Dateien nicht gemergt sind, 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 aufweist und noch nicht gelöst ist, wird als "nicht gemergt" aufgeführt. Git fügt den Dateien mit Konflikten Standard-Konfliktlösungsmarkierungen hinzu, sodass Sie sie manuell öffnen und die Konflikte lösen können. Ihre Datei enthält einen Abschnitt, der in etwa wie folgt 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

Dies bedeutet, dass die Version in HEAD (Ihr master-Branch, da dies das war, was Sie ausgecheckt hatten, als Sie Ihren Merge-Befehl ausgeführt haben) der obere Teil dieses Blocks ist (alles oberhalb von =======), während die Version in Ihrem iss53-Branch alles in der unteren Hälfte ist. Um den Konflikt zu lösen, müssen Sie entweder die eine oder die andere Seite wählen oder den Inhalt selbst mergen. Sie könnten diesen Konflikt beispielsweise lösen, indem Sie den gesamten Block durch Folgendes ersetzen:

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

Diese Lösung enthält einen Teil von jeder Sektion, und die Zeilen <<<<<<<, ======= und >>>>>>> wurden vollständig entfernt. Nachdem Sie jeden dieser Abschnitte in jeder konfliktreichen Datei gelöst haben, führen Sie git add für jede Datei aus, um sie als gelöst zu markieren. Das Staging der Datei markiert sie in Git als gelöst.

Wenn Sie ein grafisches Tool verwenden möchten, um diese Probleme zu lösen, können Sie git mergetool ausführen, das ein geeignetes visuelles Merge-Tool startet und Sie durch die Konflikte 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 als das Standardtool verwenden möchten (Git hat in diesem Fall opendiff gewählt, da der Befehl unter macOS ausgeführt wurde), können Sie alle unterstützten Tools oben nach "one of the following tools" sehen. Geben Sie einfach den Namen des Tools ein, das Sie bevorzugen.

Hinweis

Wenn Sie erweiterte Werkzeuge zur Lösung kniffliger Merge-Konflikte benötigen, behandeln wir mehr zum Thema Merging in Erweitertes Merging.

Nachdem Sie das Merge-Tool beendet haben, fragt Git, ob der Merge erfolgreich war. Wenn Sie dem Skript mitteilen, dass er erfolgreich war, staged es die Datei, um sie für Sie als gelöst zu markieren. Sie können 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 überprüft haben, dass alles mit Konflikten staged wurde, können Sie git commit eingeben, um den Merge-Commit zu finalisieren. Die Commit-Nachricht sieht standardmäßig etwa 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
#

Wenn Sie denken, dass es für andere, die diesen Merge in Zukunft betrachten, hilfreich wäre, können Sie diese Commit-Nachricht mit Details darüber ändern, wie Sie den Merge gelöst haben, und erklären, warum Sie die von Ihnen vorgenommenen Änderungen vorgenommen haben, falls diese nicht offensichtlich sind.