Git-Verzweigungen¶
Verzweigen ist eine Funktion, die in den meisten modernen Versionskontrollsystemen verfügbar ist. In anderen VCS-Systemen kann das Verzweigen eine teure Operation sein, die sowohl Zeit als auch Speicherplatz kostet; in Git sind Verzweigungen jedoch Verweise auf einen Schnappschuss eurer Änderungen. Wenn ihr eine neue Funktion hinzufügen oder einen Fehler beheben wollt, legt ihr einen neuen Zweig an, um eure Änderungen darin zu kapseln. Dadurch könnt ihr euch auf diese Aufgabe konzentrieren ohne zunächst gleichzeitige Änderungen im Hauptzweig berücksichtigen zu müssen. Umgekehrt hält es auch den Hauptzweig frei von fragwürdigem Code. Git-Zweige wurden daher ein fester Bestandteil des täglichen Arbeitsablaufs.
Ihr könnt euch Verzweigungen auch als ein neues Arbeitsverzeichnis mit neuen Staging-Bereich und Projektverlauf vorstellen wobei eure Commits zunächst in der Historie für den aktuellen Zweig aufgezeichnet werden.
Gebräuchliche Befehle¶
$ git branch [-a] [-l "GLOB_PATTERN"]
zeigt alle lokalen Verzweigungen in einem Repository an.
-a
zeigt auch alle entfernten Verzweigungen an.
-l
beschränkt die Zweige auf diejenigen, die einem bestimmten Muster entsprechen.
$ git branch --sort=-committerdate
sortiert die Zweige nach dem Commit-Datum.
Mit
git config --global branch.sort -committerdate
könnt ihr diese Einstellung auch zu eurer Standardeinstellung machen.$ git branch BRANCH_NAME
erstellt auf Basis des aktuellen
HEAD
einen neuen Zweig.$ git switch [-c] BRANCH_NAME
wechselt zwischen Zweigen.
-c
erstellt einen neuen Zweig.
Bemerkung
In Git < 2.23 steht euch
git switch
noch nicht zur Verfügung. In diesem Fall müsst ihr nochgit checkout
verwenden:$ git checkout [-b] [BRANCH_NAME]
ändert das Arbeitsverzeichnis in den angegebenen Zweig.
-b
erstellt den angegebenen Zweig, wenn dieser nicht schon besteht.
$ git merge FROM_BRANCH_NAME
verbindet den angegebenen mit dem aktuellen Zweig, in dem ihr euch gerade befindet, z.B.:
$ git switch main $ git merge hotfix Updating f42c576..3a0874c Fast forward setup.py | 1 - 1 files changed, 0 insertions(+), 1 deletions(-)
Fast forward
besagt, dass der neue Commit direkt auf den ursprünglichen Commit folgte und somit der Zeiger (branch pointer) nur weitergeführt werden musste.
In anderen Fällen kann die Ausgabe z.B. so aussehen:
$ git switch main $ git merge 'my-feature' Merge made by recursive. setup.py | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)
recursive
ist eine Merge-Strategie, die verwendet wird, sofern die Zusammenführung nur zu
HEAD
erfolgt.
Merge-Konflikte¶
Gelegentlich stößt Git beim Zusammenführen jedoch auf Probleme, z.B.:
$ git merge 'my-feature'
automatischer Merge von setup.py
KONFLIKT (Inhalt): Merge-Konflikt in setup.py
Automatischer Merge fehlgeschlagen; beheben Sie die Konflikte und committen Sie dann das Ergebnis.
Die Historie kann dann z.B. so aussehen:
* 49770a2 (HEAD -> main) Fix merge conflict with my-feature
|\
| * 9412467 (my-feature) My feature
* | 46ab1a2 Hotfix directly in main
|/
* 0c65f04 Initial commit
Verbesserte Konfliktanzeige mit zdiff3¶
Normalerweise stellt Git Zusammenführungskonflikte folgendermaßen dar:
<<<<<<< HEAD
This line has been changed by feature one.
This line has also been changed by feature one.
This line will be changed by feature two.
=======
This line is changed by feature one.
This line has been changed by feature two.
This line has also been changed by feature two.
>>>>>>> feature_two
Zwischen den Markierungen <<<<<<<
und =======
befinden sich die Zeilen
des Merge-Ziels. Die Zeilen zwischen den Markierungen =======
und
>>>>>>>
sind die Zeilen der Merge-Quelle. Die Beschriftungen nach den
Pfeilmarkierungen benennen die Commit-Referenzen, die zusammengeführt werden.
Dies ist oft ausreichend, um einen Konflikt lösen zu können. Aber es kann auch unnötig herausfordernd sein, weil die ursprünglichen Zeilen, von denen beide Seiten ausgingen, fehlen. Die gemeinsame Basis, von der beide Seiten ausgegangen sind, schaffen Klarheit über den Kontext, in dem beide Änderungen entstanden.
Wenn iht merge.conflictStyle
auf zdiff3
setzt, könnt ihr euch auch die gemeinsame Basis anzeigen lassen:
$ git config --global merge.conflictStyle zdiff3
Hier ist der gleiche Merge mit diesem Stil:
<<<<<<< HEAD
This line has been changed by feature one.
This line has also been changed by feature one.
This line will be changed by feature two.
||||||| 45d92bd
This line is changed by feature one.
This line will be changed by feature one and feature two.
This line will be changed by feature two.
=======
This line is changed by feature one.
This line has been changed by feature two.
This line has also been changed by feature two.
Die gemeinsame Basis wird nun zwischen den Markierungen |||||||
und
=======
angezeigt mit dem SHA-Wert der gemeinsamen Basis. Dieser zusätzliche
Kontext ist oft nützlich, um einen Konflikt auflösen zu können.
rerere
, um aufgezeichnete Konfliktlösungen wiederzuverwenden¶
rerere erleichtert euch, immer
wieder dieselben Merge-Konflikte lösen zu müssen. Dies kann z.B. passieren, wenn ihr einen Commit in mehrere Zweige zusammenführen
oder wenn ihr einen Zweig wiederholt rebasen müsst. Das Beheben von
Merge-Konflikten erfordert Konzentration und Energie, und es ist Verschwendung,
denselben Konflikt immer wieder neu zu lösen. git rerere wird jedoch nur selten direkt
aufgerufen, sondern meist global aktiviert. Dann wird er automatisch von git
merge
, git rebase
und git commit
verwendet. Seine wichtigste
Auswirkung besteht darin, dass er der Ausgabe dieser Befehle einige Meldungen
hinzufügt. Ihr könnt ihn aktivieren mit:
$ git config --global rerere.enabled true
Schauen wir uns ein Beispiel für git rerere
in Aktion an. Angenommen, ihr
versucht eine Zusammenführung und stoßt auf Konflikte:
% git merge rerere-example
automatischer Merge von README.md
KONFLIKT (Inhalt): Merge-Konflikt in README.md
Preimage für 'README.md' aufgezeichnet.
Automatischer Merge fehlgeschlagen; beheben Sie die Konflikte und committen Sie dann das Ergebnis.
git rerere
schrieb die dritte Zeile, Preimage für 'README.md'
aufgezeichnet.
, d.h., dass der Konflikt aufgezeichnet
wurde, bevor wir ihn beheben. Wenn wir den Konflikt nun beheben, können wir mit
der Zusammenführung fortfahren, in unserem Beispiel mit:
$ git add README.md
$ git merge --continue
Konfliktauflösung für 'README.md' aufgezeichnet.
[main 5935d00] Merge branch 'rerere-example'
git rerere
meldet nun Konfliktauflösung für 'README.md' aufgezeichnet.
,
d.h., dass es gespeichert hat, wie wir die Konflikte in
dieser Datei aufgelöst haben.
Angenommen, ihr macht diese Zusammenführung rückgängig, weil ihr feststellgestellt habt, dass sie nicht fertig war:
$ git reset --keep @~
Später wiederholt ihr die Zusammenführung:
$ git merge rerere-example
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Resolved 'README.md' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
When finished, apply stashed changes with `git stash pop`
git rerere
löste den Konflikt unter Verwendung der früheren Lösung,
d.h., es hat eure vorherige Zusammenführung wiederverwendet.
Prüft nun, ob die Datei korrekt ist, und fahrt dann fort:
$ git add README.md
$ git merge --continue
[main c922b21] Merge branch 'rerere-example'
git rerere
speichert seine Daten innerhalb des .git
-Verzeichnisses
eures Git-Repositorys in einem rr-cache
-Verzeichnis. Dabei solltet ihr
zweierlei beachten:
Der Rerere-Cache ist lokal. Er wird nicht geteilt, wenn ihr
git push
durchführt, so dass eure Teamkollegen die von euch durchgeführten Merges nicht wiederverwenden können.Git’s automatische Garbage-Collection löscht Einträge aus dem
rr-cache
. Sie wird durch zwei Konfigurationsoptionen gesteuert:- gc.rerereResolved
bestimmt, wie lange Einträge für gelöste Konflikte aufbewahrt werden. Der Standardwert ist 60 Tage. Und mit
git config gc.rerereResolved
könnt ihr die Standardwerte für euer Projekt ändern.- gc.rerereUnresolved
bestimmt, wie lange Einträge für ungelöste Konflikte aufbewahrt werden. Der Standardwert ist 15 Tage.
Zweige löschen¶
$ git branch -d [BRANCH_NAME]
löscht den ausgewählten Zweig, wenn er bereits in einen anderen überführt wurde.
-D
statt-d
erzwingt die Löschung.
Entfernte Zweige¶
Bisher haben diese Beispiele alle lokalen Verzweigungen gezeigt. Der Befehl
git branch
funktioniert jedoch auch mit entfernten Zweigen. Um mit
entfernten Zweigen arbeiten zu können, muss zunächst ein entferntes Repository
konfiguriert und zur lokalen Repository-Konfiguration hinzugefügt werden:
$ git remote add origin https://ce.cusy.io/veit/NEWREPO.git
Entfernte Zweige hinzufügen¶
Nun kann der Zweig auch im entfernten Repository hinzugefügt werden:
$ git push --set-upstream origin [BRANCH_NAME]
Wollt ihr alle Zweige eines lokalen Repositories dem entfernten Repo hinzufügen, könnt ihr dies mit:
$ git push --set-upstream origin --all
Damit dies für Zweige ohne Tracking-Upstream automatisch geschieht, könnt ihr folgendes konfigurieren:
$ git config --global push.autoSetupRemote true
Entfernte Zweige löschen¶
Mit git branch -d
löscht ihr die Zweige nur lokal. Um sie auch auf dem
entfernten Server zu löschen, könnt ihr folgendes eingeben:
$ git push origin --delete [BRANCH_NAME]
Um entfernte Zweige auch bei euch lokal zu entfernen, könnt ihr git fetch
mit der Option --prune
oder -p
ausführen. Ihr könnt dieses Verhalten
auch zur Standardeinstellung machen, indem ihr fetch.prune
aktiviert:
$ git config --global fetch.prune true
Siehe auch
Zweige umbenennen¶
Ihr könnt Zweige umbenennen, z.B. mit
$ git branch --move master main
Dies ändert euren lokalen master
-Zweig in main
. Damit andere den neuen
Zweig sehen können, müsst ihr ihn auf den entfernten Server pushen. Dadurch wird
der main
-Zweig auch auf dem entfernten Server verfügbar:
$ git push origin main
Der aktuelle Zustand eures Repository kann nun z.B. so aussehen:
$ git branch -a
* main
remotes/origin/HEAD -> origin/master
remotes/origin/main
remotes/origin/master
Euer lokaler
master
-Zweig ist verschwunden, da er durch denmain
-Zweig ersetzt wurde.Der
main
-Zweig ist auch auf dem entfernten Rechner vorhanden.Auch der
master
-Zweig ist jedoch auch noch auf dem entfernten Server vorhanden. Vermutlich werden also andere weiterhin denmaster
-Zweig für ihre Arbeit verwenden, bis ihr die folgenden Änderungen vorgenommen habt:Für alle Projekte, die von diesem Projekt abhängen, muss der Code und/oder die Konfiguration aktualisiert werden.
Die Konfigurationsdateien des test-runner müssen ggf. aktualisiert werden.
Build- und Release-Skripte müssen angepasst werden.
Die Einstellungen auf eurem Repository-Server wie der Standardzweig des Repository, Zusammenführungsregeln und anderes müssen angepasst werden.
Verweise auf den alten Zweig in der Dokumentation müssen aktualisiert werden.
Alle Pull- oder Merge-Requests, die auf den
master
-Zweig abzielen, sollten geschlossen werden.
Nachdem ihr all diese Aufgaben erledigt habt und sicher seid, dass der
main
-Zweig genauso funktioniert wie der master
-Zweig, könnt ihr den
master
-Zweig löschen:
$ git push origin --delete master
Team-Mitgliedeer können ihre lokal noch vorhandenen Referenzen auf den
master
-Zweig löschen mit
$ git fetch origin --prune