Subsections of Versionierung mit Git
Intro: Versionskontrolle in der Softwareentwicklung
TL;DR
In der Softwareentwicklung wird häufig ein Versionsmanagementsystem (VCS) eingesetzt, welches die Verwaltung
von Versionsständen und Änderungen ermöglicht. Ein Repository sammelt dabei die verschiedenen Änderungen
(quasi wie eine Datenbank der Software-Versionsstände). Die Software Git ist verbreiteter Vertreter und
arbeitet mit dezentralen Repositories.
Ein neues lokales Repository kann man mit git init
anlegen. Der Befehl legt den Unterordner .git/
im
aktuellen Ordner an, darin befindet sich das lokale Repository und weitere von Git benötigte Dateien
(FINGER WEG!). Die Dateien und anderen Unterordner im aktuellen Ordner können nun der Versionskontrolle
hinzugefügt werden.
Den lokal vorliegenden (Versions-) Stand der Dateien im aktuellen Ordner nennt man auch "Workingcopy".
Ein bereits existierendes Repo kann mit git clone <url>
geklont werden.
GitHub ist nicht Git, sondern ein kommerzieller Anbieter, der das Hosten von
Git-Repositories und weitere Features anbietet.
Videos (HSBI-Medienportal)
Lernziele
- (K1) Varianten der Versionierung
- (K1) Begriffe Workingcopy und Repository
- (K2) Github ist nicht Git
- (K2) Erstellung von lokalen Git-Repositories
- (K3) Umgang mit entsprechenden Git-Befehlen auf der Konsole
Typische Probleme bei SW-Entwicklung
- Was hat wer wann (und wo) geändert? Und warum?
- Ich brauche den Stand von gestern/letzter Woche/...
- Ich will schnell mal eine neue Idee ausprobieren ...
- Ich arbeite an mehreren Rechnern (Synchronisation)
- Wir müssen gemeinsam an der gleichen Codebasis arbeiten.
- Wir arbeiten am Release v42, aber Kunde braucht schnell einen Fix für v40
Folgen SW-Entwicklung ohne Versionsverwaltung
- Filesystem müllt voll mit manuell versionierten
Dateien/Sicherungen ala
file_20120507_version2_cagi.txt
- Ordner/Projekte müssen dupliziert werden für neue Ideen
- Code müllt voll mit auskommentierten Zeilen ("Könnte ja noch gebraucht werden")
- Unklar, wann welche Änderung von wem warum eingeführt wurde
- Unbeabsichtigtes Überschreiben mit älteren Versionen beim Upload
in gemeinsamen Filesharing-Bereich
Prinzip Versionsverwaltung
-
Repository:
Datenbank mit verschiedenen Versionsständen, Kommentaren, Tags etc.
-
Workingcopy:
Arbeitskopie eines bestimmten Versionsstandes
Varianten: Zentrale Versionsverwaltung (Beispiel SVN)
Es gibt ein zentrales Repository (typischerweise auf einem Server), von dem die Developer einen
bestimmten Versionsstand "auschecken" (sich lokal kopieren) und in welches sie Änderungen wieder
zurück "pushen".
Zur Abfrage der Historie und zum Veröffentlichen von Änderungen benötigt man entsprechend immer
eine Verbindung zum Server.
Varianten: Verteilte Versionsverwaltung (Beispiel Git)
In diesem Szenario hat jeder Developer nicht nur die Workingcopy, sondern auch noch eine Kopie
des Repositories. Zusätzlich kann es einen oder mehrere Server geben, auf denen dann nur das
Repository vorgehalten wird, d.h. dort gibt es normalerweise keine Workingcopy. Damit kann
unabhängig voneinander gearbeitet werden.
Allerdings besteht nun die Herausforderung, die geänderten Repositories miteinander abzugleichen.
Das kann zwischen dem lokalen Rechner und dem Server passieren, aber auch zwischen zwei "normalen"
Rechnern (also zwischen den Developern).
Hinweis: GitHub ain't no Git! Git ist eine Technologie zur Versionsverwaltung. Es gibt verschiedene
Implementierungen und Plugins für IDEs und Editoren. GitHub ist dagegen ein
Dienstleister, wo man Git-Repositories ablegen kann und auf diese mit Git (von der Konsole oder aus der
IDE) zugreifen kann. Darüber hinaus bietet der Service aber zusätzliche Features an, beispielsweise
ein Issue-Management oder sogenannte Pull-Requests. Dies hat aber zunächst mit Git nichts zu tun.
Weitere populäre Anbieter sind beispielsweise Bitbucket oder Gitlab
oder Gitea, wobei einige auch selbst gehostet werden können.
Versionsverwaltung mit Git: Typische Arbeitsschritte
-
Repository anlegen (oder clonen)
-
Dateien neu erstellen (und löschen, umbenennen, verschieben)
-
Änderungen einpflegen ("committen")
-
Änderungen und Logs betrachten
-
Änderungen rückgängig machen
-
Projektstand markieren ("taggen")
-
Entwicklungszweige anlegen ("branchen")
-
Entwicklungszweige zusammenführen ("mergen")
-
Änderungen verteilen (verteiltes Arbeiten, Workflows)
(Globale) Konfiguration
Minimum:
git config --global user.name <name>
git config --global user.email <email>
Diese Konfiguration muss man nur einmal machen.
Wenn man den Schalter --global
weglässt, gelten die Einstellungen nur
für das aktuelle Projekt/Repo.
Zumindest Namen und EMail-Adresse muss man setzen, da Git diese
Information beim Anlegen der Commits speichert (== benötigt!).
Aliase:
git config --global alias.ci commit
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.st status
git config --global alias.ll 'log --all --graph --decorate --oneline'
Zusätzlich kann man weitere Einstellungen vornehmen, etwa auf bunte
Ausgabe umschalten: git config --global color.ui auto
oder Abkürzungen
(Aliase) für Befehle definieren: git config --global alias.ll 'log --all --oneline --graph --decorate'
...
Git (und auch GitHub) hat kürzlich den Namen des Default-Branches von master
auf main
geändert. Dies kann man in Git ebenfalls selbst einstellen:
git config --global init.defaultBranch <name>
.
Anschauen kann man sich die Einstellungen in der Textdatei ~/.gitconfig
oder per Befehl git config --global -l
.
Neues Repo anlegen
Wrap-Up
- Git: Versionsmanagement mit dezentralen Repositories
- Anlegen eines lokalen Repos mit
git init
- Clonen eines existierenden Repos mit
git clone <url>
Basics der Versionsverwaltung mit Git (lokale Repos)
TL;DR
Änderungen an Dateien (in der Workingcopy) werden mit git add
zum "Staging" (Index) hinzugefügt.
Dies ist eine Art Sammelbereich für Änderungen, die mit dem nächsten Commit in das Repository
überführt werden. Neue (bisher nicht versionierte Dateien) müssen ebenfalls zunächst mit
git add
zum Staging hinzugefügt werden.
Änderungen kann man mit git log
betrachten, dabei erhält man u.a. eine Liste der Commits und der
jeweiligen Commmit-Messages.
Mit git diff
kann man gezielt Änderungen zwischen Commits oder Branches betrachten.
Mit git tag
kann man bestimmte Commits mit einem "Stempel" (zusätzlicher Name) versehen, um diese
leichter finden zu können.
Wichtig sind die Commit-Messages: Diese sollten eine kurze Zusammenfassung haben, die aktiv
formuliert wird (was ändert dieser Commit: "Formatiere den Java-Code entsprechend Style"; nicht aber
"Java-Code nach Style formatiert"). Falls der Kommentar länger sein soll, folgt eine Leerzeile auf
die erste Zeile (Zusammenfassung) und danach ein Block mit der längeren Erklärung.
Videos (HSBI-Medienportal)
Lernziele
- (K3) Umgang mit Dateien: Hinzufügen zum und Löschen aus Repo
- (K3) Umgang mit Änderungen: Hinzufügen zum Staging und Commit
- (K3) Herausfinden von Unterschieden, Ansehen der Historie
- (K3) Ignorieren von Dateien und Ordnern
Versionsverwaltung mit Git: Typische Arbeitsschritte
-
Repository anlegen (oder clonen)
-
Dateien neu erstellen (und löschen, umbenennen, verschieben)
-
Änderungen einpflegen ("committen")
-
Änderungen und Logs betrachten
-
Änderungen rückgängig machen
-
Projektstand markieren ("taggen")
-
Entwicklungszweige anlegen ("branchen")
-
Entwicklungszweige zusammenführen ("mergen")
-
Änderungen verteilen (verteiltes Arbeiten, Workflows)
Dateien unter Versionskontrolle stellen
-
git add .
(oder git add <file>
)
=> Stellt alle Dateien (bzw. die Datei <file>
)
im aktuellen Verzeichnis unter Versionskontrolle
-
git commit
=> Fügt die Dateien dem Repository hinzu
Abfrage mit git status
Änderungen einpflegen
- Abfrage mit:
git status
- "Staging" von modifizierten Dateien:
git add <file>
- Committen der Änderungen im Stage:
git commit
Anmerkung: Alternativ auch mit git commit -m "Kommentar"
, um das Öffnen
des Editors zu vermeiden ... geht einfach schneller ;)
Das "staging area" stellt eine Art Zwischenebene zwischen Working Copy und
Repository dar: Die Änderungen sind temporär "gesichert", aber noch nicht
endgültig im Repository eingepflegt ("committed").
Man kann den Stage dazu nutzen, um Änderungen an einzelnen Dateien zu sammeln
und diese dann (in einem Commit) gemeinsam einzuchecken.
Man kann den Stage in der Wirkung umgehen, indem man alle in der Working Copy
vorliegenden Änderungen per git commit -a -m "Kommentar"
eincheckt. Der
Schalter "-a
" nimmt alle vorliegenden Änderungen an bereits versionierten
Dateien, fügt diese dem Stage hinzu und führt dann den Commit durch. Das ist
das von SVN bekannte Verhalten. Achtung: Nicht versionierte Dateien bleiben
dabei außen vor!
Letzten Commit ergänzen
-
git commit --amend -m "Eigentlich wollte ich das so sagen"
Wenn keine Änderungen im Stage sind, wird so die letzte Commit-Message geändert.
-
git add <file>; git commit --amend
Damit können vergessene Änderungen an der Datei <file>
zusätzlich im letzten Commit aufgezeichnet werden.
In beiden Fällen ändert sich die Commit-ID!
Weitere Datei-Operationen: hinzufügen, umbenennen, löschen
- Neue (unversionierte) Dateien und Änderungen an versionierten Dateien zum Staging hinzufügen:
git add <file>
- Löschen von Dateien (Repo+Workingcopy):
git rm <file>
- Löschen von Dateien (nur Repo):
git rm --cached <file>
- Verschieben/Umbenennen:
git mv <fileAlt> <fileNeu>
Aus Sicht von Git sind zunächst alle Dateien "untracked", d.h. stehen nicht
unter Versionskontrolle.
Mit git add <file>
(und git commit
) werden Dateien in den Index (den
Staging-Bereich, d.h. nach dem Commit letztlich in das Repository) aufgenommen.
Danach stehen sie unter "Beobachtung" (Versionskontrolle). So lange, wie eine
Datei identisch zur Version im Repository ist, gilt sie als unverändert
("unmodified"). Eine Änderung führt entsprechend zum Zustand "modified", und
ein git add <file>
speichert die Änderungen im Stage. Ein Commit überführt
die im Stage vorgemerkte Änderung in das Repo, d.h. die Datei gilt wieder
als "unmodified".
Wenn eine Datei nicht weiter versioniert werden soll, kann sie aus dem Repo
entfernt werden. Dies kann mit git rm <file>
geschehen, wobei die Datei auch
aus der Workingcopy gelöscht wird. Wenn die Datei erhalten bleiben soll, aber
nicht versioniert werden soll (also als "untracked" markiert werden soll), dann
muss sie mit git rm --cached <file>
aus der Versionskontrolle gelöscht werden.
Achtung: Die Datei ist dann nur ab dem aktuellen Commit gelöscht, d.h. frühere
Revisionen enthalten die Datei noch!
Wenn eine Datei umbenannt werden soll, geht das mit git mv <fileAlt> <fileNeu>
.
Letztlich ist dies nur eine Abkürzung für die Folge git rm --cached <fileAlt>
,
manuelles Umbenennen der Datei in der Workingcopy und git add <fileNeu>
.
Commits betrachten
Änderungen und Logs betrachten
-
git diff [<file>]
Änderungen zwischen Workingcopy und letztem Commit (ohne Stage)
Das "staging area" wird beim Diff von Git behandelt, als wären die dort
hinzugefügten Änderungen bereits eingecheckt (genauer: als letzter Commit
im aktuellen Branch im Repo vorhanden).
D.h. wenn Änderungen in einer Datei mittels git add <datei>
dem Stage
hinzugefügt wurden, zeigt git diff <datei>
keine Änderungen an!
-
git diff commitA commitB
Änderungen zwischen Commits
-
Blame: git blame <file>
Wer hat was wann gemacht?
Dateien ignorieren: .gitignore
- Nicht alle Dateien gehören ins Repo:
- generierte Dateien:
.class
- temporäre Dateien
- Datei
.gitignore
anlegen und committen
- Wirkt auch für Unterordner
- Inhalt: Reguläre Ausdrücke für zu ignorierende Dateien und Ordner
# Compiled source #
*.class
*.o
*.so
# Packages #
*.zip
# All directories and files in a directory #
bin/**/*
Zeitmaschine
-
Änderungen in Workingcopy rückgängig machen
- Änderungen nicht in Stage:
git checkout <file>
oder git restore <file>
- Änderungen in Stage:
git reset HEAD <file>
oder git restore --staged <file>
=> Hinweise von git status
beachten!
-
Datei aus altem Stand holen:
git checkout <commit> <file>
, oder
git restore --source <commit> <file>
-
Commit verwerfen, Geschichte neu: git revert <commit>
Hinweis: In den neueren Versionen von Git ist der Befehl git restore
hinzugekommen, mit
dem Änderungen rückgängig gemacht werden können. Der bisherige Befehl git checkout
steht
immer noch zur Verfügung und bietet über git restore
hinaus weitere Anwendungsmöglichkeiten.
- Stempel (Tag) vergeben:
git tag <tagname> <commit>
- Tags anzeigen:
git tag
und git show <tagname>
Wann und wie committen?
Jeder Commit stellt einen Rücksetzpunkt dar!
Typische Regeln:
- Kleinere "Häppchen" einchecken: ein Feature oder Task
(das nennt man auch atomic commit: das kleinste Set an Änderungen, die
gemeinsam Sinn machen und die ggf. gemeinsam zurückgesetzt werden können)
- Logisch zusammenhängende Änderungen gemeinsam einchecken
- Projekt muss nach Commit compilierbar sein
- Projekt sollte nach Commit lauffähig sein
Ein Commit sollte in sich geschlossen sein, d.h. die kleinste Menge an Änderungen
enthalten, die gemeinsam einen Sinn ergeben und die (bei Bedarf) gemeinsam
zurückgesetzt oder verschoben werden können. Das nennt man auch atomic commit.
Wenn Sie versuchen, die Änderungen in Ihrem Commit zu beschreiben (siehe nächste Folie
"Commit-Messages"), dann werden Sie einen atomic commit mit einem kurzen Satz (natürlich
im Imperativ!) beschreiben können. Wenn Sie mehr Text brauchen, haben Sie wahrscheinlich
keinen atomic commit mehr vor sich.
Lesen Sie dazu auch How atomic Git commits dramatically increased my productivity - and will increase yours too.
Schreiben von Commit-Messages: WARUM?!
Schauen Sie sich einmal einen Screenshot eines git log --oneline 61e48f0..e2c8076
im Dungeon-CampusMinden/Dungeon an:
Nun stellen Sie sich vor, Sie sind auf der Suche nach Informationen, suchen einen
bestimmten Commit oder wollen eine bestimmte Änderung finden ...
Wenn man das genauer analysiert, dann stören bestimmte Dinge:
- Mischung aus Deutsch und Englisch
- "Vor-sich-hin-Murmeln": "Layer system 5"
- Teileweise werden Tags genutzt wie
[BUG]
, aber nicht durchgängig
- Mischung zwischen verschiedenen Formen: "Repo umbenennen", "Benenne Repo um", "Repo umbenannt"
- Unterschiedliche Groß- und Kleinschreibung
- Sehr unterschiedlich lange Zeilen/Kommentare
Das Beachten einheitlicher Regeln ist enorm wichtig!
Leider sagt sich das so leicht - in der Praxis macht man es dann
doch schnell wieder unsauber. Dennoch, auch im Dungeon-Repo gibt
es einen positiven Trend (git log --oneline 8039d6c..7f49e89
):
Typische Regeln und Konventionen tauchen überall auf, beispielsweise
in [Chacon2014] oder bei Tim Pope (siehe nächstes Beispiel) oder bei
"How to Write a Git Commit Message".
Short (50 chars or less) summary of changes
More detailed explanatory text, if necessary. Wrap it to about
72 characters or so. In some contexts, the first line is treated
as the subject of an email and the rest of the text as the body.
The blank line separating the summary from the body is critical
(unless you omit the body entirely); tools like rebase can get
confused if you run the two together.
Further paragraphs come after blank lines.
- Bullet points are okay, too
- Typically a hyphen or asterisk is used for the bullet, preceded
by a single space, with blank lines in between, but conventions
vary here
Quelle: "A Note About Git Commit Messages" by Tim Pope on tbaggery.com
Denken Sie sich die Commit-Message als E-Mail an einen zukünftigen Entwickler,
der das in fünf Jahren liest!
Vom Aufbau her hat eine E-Mail auch eine Summary und dann den eigentlichen Inhalt ...
Erklären Sie das "WARUM" der Änderung! (Das "WER", "WAS", "WANN" wird bereits
automatisch von Git aufgezeichnet ...)
Wrap-Up
- Änderungen einpflegen zweistufig (
add
, commit
)
- Status der Workingcopy mit
status
ansehen
- Logmeldungen mit
log
ansehen
- Änderungen auf einem File mit
diff
bzw. blame
ansehen
- Projektstand markieren mit
tag
- Ignorieren von Dateien/Ordnern: Datei
.gitignore
Challenges
Versionierung 101
- Legen Sie ein Repository an.
- Fügen Sie Dateien dem Verzeichnis hinzu und stellen Sie einige davon
unter Versionskontrolle.
- Ändern Sie eine Datei und versionieren Sie die Änderung.
- Was ist der Unterschied zwischen "
git add .; git commit
" und
"git commit -a
"?
- Wie finden Sie heraus, welche Dateien geändert wurden?
- Entfernen Sie eine Datei aus der Versionskontrolle, aber nicht aus dem
Verzeichnis!
- Entfernen Sie eine Datei komplett (Versionskontrolle und Verzeichnis).
- Ändern Sie eine Datei und betrachten die Unterschiede zum letzten Commit.
- Fügen Sie eine geänderte Datei zum Index hinzu. Was erhalten Sie bei
git diff <datei>
?
- Wie können Sie einen früheren Stand einer Datei wiederherstellen? Wie
finden Sie überhaupt den Stand?
- Legen Sie sich ein Java-Projekt in Ihrer IDE an an. Stellen Sie dieses
Projekt unter Git-Versionskontrolle. Führen Sie die vorigen Schritte mit
Ihrer IDE durch.
Interaktive Git-Tutorials: Schaffen Sie die Rätsel?
Git Branches: Features unabhängig entwickeln und mit Git verwalten
TL;DR
Die Commits in Git bauen aufeinander auf und bilden dabei eine verkettete "Liste". Diese "Liste" nennt man
auch Branch (Entwicklungszweig). Beim Initialisieren eines Repositories wird automatisch ein Default-Branch
angelegt, auf dem die Commits dann eingefügt werden.
Weitere Branches kann man mit git branch
anlegen, und die Workingcopy kann mit git switch
oder git checkout
auf einen anderen Branch umgeschaltet werden. Auf diese Weise kann man an mehreren Features parallel arbeiten,
ohne dass die Arbeiten sich gegenseitig stören.
Zum Mergen (Vereinigen) von Branches gibt es git merge
. Dabei werden die Änderungen im angegebenen Branch in
den aktuell in der Workingcopy ausgecheckten Branch integriert und hier ggf. ein neuer Merge-Commit erzeugt. Falls
es in beiden Branches inkompatible Änderungen an der selben Stelle gab, entsteht beim Mergen ein Merge-Konflikt.
Dabei zeigt Git in den betroffenen Dateien jeweils an, welche Änderung aus welchem Branch stammt und man muss
diesen Konflikt durch Editieren der Stellen manuell beheben.
Mit git rebase
kann die Wurzel eines Branches an eine andere Stelle verschoben werden. Dies wird später bei
Workflows eine Rolle spielen.
Videos (HSBI-Medienportal)
Lernziele
- (K3) Erzeugen von Branches
- (K3) Mergen von Branches, Auflösen möglicher Konflikte
- (K3) Rebasen von Branches
Neues Feature entwickeln/ausprobieren
A---B---C master
- Bisher nur lineare Entwicklung: Commits bauen aufeinander auf (lineare Folge von Commits)
master
ist der (Default-) Hauptentwicklungszweig
- Pointer auf letzten Commit
- Default-Name: "
master
" (muss aber nicht so sein bzw. kann geändert werden)
Anmerkung: Git und auch Github haben den Namen für den Default-Branch von master
auf main
geändert. Der Name an sich ist aber für Git bedeutungslos und kann mittels
git config --global init.defaultBranch <name>
geändert werden. In Github hat der
Default-Branch eine gewisse Bedeutung, beispielsweise ist der Default-Branch das
automatische Ziel beim Anlegen von Pull-Requests. In Github kann man den Default-Namen
global in den User-Einstellungen (Abschnitt "Repositories") und für jedes einzelne
Repository in den Repo-Einstellungen (Abschnitt "Branches") ändern.
Entwicklung des neuen Features soll stabilen master
-Branch nicht beeinflussen
=> Eigenen Entwicklungszweig für die Entwicklung des Features anlegen:
- Neuen Branch erstellen:
git branch wuppie
- Neuen Branch auschecken:
git checkout wuppie
oder git switch wuppie
Alternativ: git checkout -b wuppie
oder git switch -c wuppie
(neuer Branch und auschecken in einem Schritt)
A---B---C master, wuppie
Startpunkt: prinzipiell beliebig (jeder Commit in der Historie möglich).
Die gezeigten Beispiel zweigen den neuen Branch direkt vom aktuell ausgecheckten
Commit/Branch ab. Also aufpassen, was gerade in der Workingcopy los ist!
Alternativ nutzen Sie die Langform: git branch wuppie master
(mit master
als
Startpunkt; hier kann jeder beliebige Branch, Tag oder Commit genutzt werden).
Nach Anlegen des neuen Branches zeigen beide Pointer auf den selben Commit.
Anmerkung: In neueren Git-Versionen wurde der Befehl "switch
" eingeführt,
mit dem Sie in der Workingcopy auf einen anderen Branch wechseln können. Der
bisherige Befehl "checkout
" funktioniert aber weiterhin.
Arbeiten im Entwicklungszweig ...
D wuppie
/
A---B---C master
- Entwicklung des neuen Features erfolgt im eigenen Branch: beeinflusst den
stabilen
master
-Branch nicht
- Wenn in der Workingcopy der Feature-Branch ausgecheckt ist, gehen die
Commits in den Feature-Branch; der
master
bleibt auf dem alten Stand
- Wenn der
master
ausgecheckt wäre, würden die Änderungen in den master
gehen, d.h. der master
würde sich ab Commit C
parallel zu wuppie
entwickeln
Problem: Fehler im ausgelieferten Produkt
D wuppie
/
A---B---C master
Fix für master
nötig:
git checkout master
git checkout -b fix
- Änderungen in
fix
vornehmen ...
Das führt zu dieser Situation:
D wuppie
/
A---B---C master
\
E fix
git checkout <branchname>
holt den aktuellen Stand des jeweiligen
Branches in die Workingcopy. (Das geht in neueren Git-Versionen auch
mit git switch <branchname>
.)
Man kann weitere Branches anlegen, d.h. hier im Beispiel ein neuer
Feature-Branch fix
, der auf dem master
basiert. Analog könnte man
auch Branches auf der Basis von wuppie
anlegen ...
Fix ist stabil: Integration in master
D wuppie
/
A---B---C master
\
E fix
git checkout master
git merge fix
=> fast forward von master
git branch -d fix
Der letzte Schritt entfernt den Branch fix
.
D wuppie
/
A---B---C---E master
-
Allgemein: git merge <branchname>
führt die Änderungen im angegebenen Branch
<branchname>
in den aktuell in der Workingcopy ausgecheckten Branch ein. Daraus
resultiert für den aktuell ausgecheckten Branch ein neuer Commit, der Branch
<branchname>
bleibt dagegen auf seinem bisherigen Stand.
Beispiel:
- Die Workingcopy ist auf
A
git merge B
führt A
und B
zusammen: B
wird in A
gemergt
- Wichtig: Der Merge-Commit (sofern nötig) findet hierbei in
A
statt!
In der Abbildung ist A
der master
und B
der fix
.
-
Nach dem Merge existieren beide Branches weiter (sofern sie nicht explizit
gelöscht werden)
-
Hier im Beispiel findet ein sogenannter "Fast forward" statt.
"Fast forward" ist ein günstiger Spezialfall beim Merge: Beide Branches
liegen in einer direkten Kette, d.h. der Zielbranch kann einfach
"weitergeschaltet" werden. Ein Merge-Commit ist in diesem Fall nicht
notwendig und wird auch nicht angelegt.
Feature weiter entwickeln ...
D---F wuppie
/
A---B---C---E master
git switch wuppie
- Weitere Änderungen im Branch
wuppie
...
git switch <branchname>
holt den aktuellen Stand des jeweiligen Branches in
die Workingcopy. Man kann also jederzeit in der Workingcopy die Branches wechseln
und entsprechend weiterarbeiten.
Hinweis: Während der neue git switch
-Befehl nur Branches umschalten kann,
funktioniert git checkout
sowohl mit Branchnamen und Dateinamen - damit kann
man also auch eine andere Version einer Datei in der Workingcopy "auschecken".
Falls gleiche Branch- und Dateinamen existieren, muss man für das Auschecken
einer Datei noch "--
" nutzen: git checkout -- <dateiname>
.
Feature ist stabil: Integration in master
D---F wuppie D---F wuppie
/ => / \
A---B---C---E master A---B---C---E---G master
git checkout master
git merge wuppie
=> Kein fast forward möglich: Git sucht nach gemeinsamen Vorgänger
Hier im Beispiel ist der Standardfall beim Mergen dargestellt: Die beiden
Branches liegen nicht in einer direkten Kette von Commits, d.h. hier wurde
parallel weitergearbeitet.
Git sucht in diesem Fall nach dem gemeinsamen Vorgänger beider Branches und
führt die jeweiligen Änderungen (Differenzen) seit diesem Vorgänger in einem
Merge-Commit zusammen.
Im master
entsteht ein neuer Commit, da kein fast forward beim
Zusammenführen der Branches möglich!
Anmerkung: git checkout wuppie; git merge master
würde den master
in den
wuppie
mergen, d.h. der Merge-Commit wäre dann in wuppie
.
Beachten Sie dabei die "Merge-Richtung":
- Die Workingcopy ist auf
A
git merge B
führt A
und B
zusammen: B
wird in A
gemergt
- Wichtig: Der Merge-Commit (sofern nötig) findet hierbei in
A
statt!
In der Abbildung ist A
der master
und B
der wuppie
.
Achtung: Richtung beachten! git checkout A; git merge B
führt beide Branches zusammen,
genauer: führt die Änderungen von B
in A
ein, d.h. der entsprechende Merge-Commit ist in A
!
Konflikte beim Mergen
(Parallele) Änderungen an selber Stelle => Merge-Konflikte
$ git merge wuppie
Auto-merging hero.java
CONFLICT (content): Merge conflict in hero.java
Automatic merge failed; fix conflicts and then commit the result.
Git fügt Konflikt-Marker in die Datei ein:
<<<<<<< HEAD:hero.java
public void getActiveAnimation() {
return null;
=======
public Animation getActiveAnimation() {
return this.idleAnimation;
>>>>>>> wuppie:hero.java
- Der Teil mit
HEAD
ist aus dem aktuellen Branch in der Workingcopy
- Der Teil aus dem zu mergenden Branch ist unter
wuppie
notiert
- Das
=======
trennt beide Bereiche
Merge-Konflikte auflösen
Manuelles Editieren nötig (Auflösung des Konflikts):
- Entfernen der Marker
- Hinzufügen der Datei zum Index
- Analog für restliche Dateien mit Konflikt
- Commit zum Abschließen des Merge-Vorgangs
Alternativ: Nutzung graphischer Oberflächen mittels git mergetool
Rebasen: Verschieben von Branches
D---F wuppie D---F wuppie
/ => / \
A---B---C---E master A---B---C---E---G master
Bisher haben wir Branches durch Mergen zusammengeführt. Dabei entsteht in der Regel ein extra
Merge-Commit (im Beispiel G
), außer es handelt sich um ein fast forward. Außerdem erkennt
man in der Historie sehr gut, dass hier in einem separaten Branch gearbeitet wurde, der irgendwann
in den master
gemergt wurde.
Leider wird dieses Vorgehen in großen Projekten recht schnell sehr unübersichtlich. Außerdem
werden Merges in der Regeln nur von besonders berechtigten Personen (Manager) durchgeführt, die im
Falle von Merge-Konflikten diese dann selbst auflösen müssten (ohne aber die fachliche Befähigung
zu haben). Hier greift man dann häufig zur Alternative Rebase. Dabei wird der Ursprung eines
Branches auf einen bestimmten Commit verschoben. Im Anschluss ist dann ein Merge mit fast forward,
also ohne die typischen rautenförmigen Ketten in der Historie und ohne extra Merge-Commit möglich.
Dies kann aber auch als Nachteil gesehen werden, da man in der Historie den früheren Branch nicht
mehr erkennt! Ein weiterer schwerwiegender Nachteil ist, dass alle Commits im verschobenen Branch
umgeschrieben werden und damit neue Commit-IDs bekommen. Das verursacht bei der Zusammenarbeit in
Projekten massive Probleme! Als Vorteil gilt, dass man mögliche Merge-Konflikte bereits beim Rebasen
auflösen muss, d.h. hier muss derjenige, der den Merge "beantragt", durch einen vorherigen Rebase den
konfliktfreien Merge sicherstellen. Mehr dazu in “Branching-Strategien”
und “Workflows”.
git rebase master wuppie
führt zu
D'---F' wuppie
/
A---B---C---E master
Nach dem Rebase von wuppie
auf master
sieht es so aus, als ob der Branch wuppie
eben erst vom master
abgezweigt wurde. Damit ist dann ein fast forward Merge von wuppie
in den master
möglich, d.h. es gibt keine Raute und auch keinen extra Merge-Commit (hier nicht
gezeigt).
Man beachte aber die Änderung der Commit-IDs von wuppie
: Aus D
wird D'
! (Datum, Ersteller
und Message bleiben aber erhalten.)
Don't lose your HEAD
Wrap-Up
- Anlegen von Branches mit
git branch
- Umschalten der Workingcopy auf anderen Branch:
git checkout
oder git switch
- Mergen von Branches und Auflösen von Konflikten:
git merge
- Verschieben von Branches mit
git rebase
Challenges
Branches und Merges
-
Legen Sie in Ihrem Projekt einen Branch an. Ändern Sie einige Dateien
und committen Sie die Änderungen. Checken Sie den Master-Branch aus und
mergen Sie die Änderungen. Was beobachten Sie?
-
Legen Sie einen weiteren Branch an. Ändern Sie einige Dateien und
committen Sie die Änderungen. Checken Sie den Master-Branch aus und
ändern Sie dort ebenfalls:
- Ändern Sie eine Datei an einer Stelle, die nicht bereits im Branch
modifiziert wurde.
- Ändern Sie eine Datei an einer Stelle, die bereits im Branch
manipuliert wurde.
Committen Sie die Änderungen.
Mergen Sie den Branch jetzt in den Master-Branch. Was beobachten Sie? Wie
lösen Sie Konflikte auf?
Interaktive Git-Tutorials: Schaffen Sie die Rätsel?
Branching-Strategien mit Git
TL;DR
Das Erstellen und Mergen von Branches ist in Git besonders einfach. Dies kann man sich in der Entwicklung zunutze machen
und die einzelnen Features unabhängig voneinander in eigenen Hilfs-Branches ausarbeiten.
Es haben sich zwei grundlegende Modelle etabliert: "Git-Flow" und "GitHub Flow".
In Git-Flow gibt es ein umfangreiches Konzept mit verschiedenen Branches für feste Aufgaben, welches sich besonders
gut für Entwicklungmodelle mit festen Releases eignet. Es gibt zwei langlaufende Branches: master
enthält den stabilen
veröffentlichten Stand, in develop
werden die Ergebnisse der Entwicklung gesammelt. Features werden in kleinen Feature-Branches
entwickelt, die von develop
abzweigen und dort wieder hineinmünden. Für Releases wird von develop
ein eigener Release-Branch
angelegt und nach Finalisierung in den master
und in develop
gemergt. Fixes werden vom master
abgezweigt, und wieder
in den master
und auch nach develop
integriert. Dadurch stehen auf dem master
immer die stabilen Release-Stände zur
Verfügung, und im develop
sammeln sich die Entwicklungsergebnisse.
Der GitHub Flow basiert auf einem deutlich schlankeren Konzept und passt gut für die kontinuierliche Entwicklung ohne
echte Releases. Hier hat man auch wieder einen master
als langlaufenden Branch, der die stabilen Release-Stände enthält.
Vom master
zweigen direkt die kleinen Feature-Branches ab und werden auch wieder direkt in den master
integriert.
Videos (HSBI-Medienportal)
Lernziele
- (K3) Einsatz von Themenbranches in der Entwicklung
- (K3) Git-Flow-Modell anwenden
- (K3) GitHub Flow-Modell anwenden
Nutzung von Git in Projekten: Verteiltes Git (und Workflows)
Git ermöglicht ein einfaches und schnelles Branchen. Dies kann man mit
entsprechenden Branching-Strategien sinnvoll für die SW-Entwicklung einsetzen.
Im Folgenden sollen also die Frage betrachtet werden: Wie setze ich Branches sinnvoll ein?
Umgang mit Branches: Themen-Branches
I---J---K wuppieV1
/
D---F wuppie
/
A---B---C---E master
\
G---H test
Branchen ist in Git sehr einfach und schnell. Deshalb wird (gerade auch im Vergleich mit
SVN) gern und viel gebrancht.
Ein häufiges anzutreffendes Modell ist dabei die Nutzung von
Themen-Branches: Man hat einen Hauptzweig (master
). Wann immer eine neue
Idee oder ein Baustein unabhängig entwickelt werden soll/kann, wird ein
entsprechender Themen-Branch aufgemacht. Dabei handelt es sich normalerweise
um kleine Einheiten!
Themenbranches haben in der Regel eine kurze Lebensdauer: Wenn die Entwicklung
abgeschlossen ist, wird die Idee bzw. der Baustein in den Hauptzweig integriert
und der Themenbranch gelöscht.
Umgang mit Branches: Langlaufende Branches
A---B---D master
\
C---E---I develop
\
F---G---H topic
Häufig findet man in (größeren) Projekten Branches, die über die gesamte
Lebensdauer des Projekts existieren, sogenannte "langlaufende Branches".
Normalerweise gibt es einen Branch, in dem stets der stabile Stand des Projekts
enthalten ist. Dies ist häufig der master
. In diesem Branch gibt es nur
sehr wenige Commits: normalerweise nur Merges aus dem develop
-Branch (etwa
bei Fertigstellung einer Release-Version) und ggf. Fehlerbehebungen.
Die aktive Entwicklung findet in einem separaten Branch statt: develop
. Hier
nutzt man zusätzlich Themen-Branches für die Entwicklung einzelner Features,
die nach Fertigstellung in den develop
gemergt werden.
Kleinere Projekte kommen meist mit den zwei langlaufenden Branches in der
obigen Darstellung aus. Bei größeren Projekten finden sich häufig noch etliche
weitere langlaufende Branches, beispielsweise "Proposed Updates" etc. beim
Linux-Kernel.
- Vorteile:
- Mehr Struktur im Projekt durch in ihrer Semantik wohldefinierte
Branches
- Durch weniger Commits pro Branch lässt sich die Historie leichter
verfolgen (u.a. auch aus bestimmter Rollen-Perspektive: Entwickler,
Manager, ...)
- Nachteile: Bestimmte "ausgezeichnete" Branches; zusätzliche Regeln zum
Umgang mit diesen beachten
Komplexe Branching-Strategie: Git-Flow
A---B---------------------G---J1 master
\ / \ /
\ / X fix
\ / \
C-------------F----I--J2 develop
\ / \ /
\ / H1 featureB
\ /
D1----D2 featureA
\
E1---E2---E3---E4---E5 featureC
Das Git-Flow-Modell von Vincent Driessen
(nvie.com/posts/a-successful-git-branching-model)
zeigt einen in der Praxis überaus bewährten Umgang mit Branches. Lesen Sie an
der angegebenen Stelle nach, besser kann man die Nutzung dieses eleganten
Modells eigentlich nicht erklären :-)
Git-Flow: Hauptzweige master und develop
A---B-------E---------------J master
\ / /
C---D---F---G---H---I---K develop
Bei Git-Flow gibt es zwei langlaufende Branches: Den master
, der immer den stabilen
Stand enthält und in den nie ein direkter Commit gemacht wird, sowie den develop
,
wo letztlich (ggf. über Themenbranches) die eigentliche Entwicklung stattfindet.
Änderungen werden zunächst im develop
erstellt und getestet. Wenn die Features
stabil sind, erfolgt ein Merge von develop
in den master
. Hier kann noch der
Umweg über einen release
-Branch genommen werden: Als "Feature-Freeze" wird vom
develop
ein release
-Branch abgezweigt. Darin wird das Release dann aufpoliert,
d.h. es erfolgen nur noch kleinere Korrekturen und Änderungen, aber keine echte
Entwicklungsarbeit mehr. Nach Fertigstellung wird der release
dann sowohl in den
master
als auch develop
gemergt.
Git-Flow: Weitere Branches als Themen-Branches
A---B---------------------I-------------K master
\ / /
C------------F----H-------------J---L develop
\ / \ / /
\ / G1 featureB /
\ / /
D1---D2 featureA /
\ /
E1---E2---E3---E4---E5 featureC
Für die Entwicklung eigenständiger Features bietet es sich auch im
Git-Flow an, vom develop
entsprechende Themenbranches abzuzweigen
und darin jeweils isoliert die Features zu entwickeln. Wenn diese
Arbeiten eine gewisse Reife haben, werden die Featurebranches in den
develop
integriert.
Git-Flow: Merging-Detail
---C--------E develop
\ / git merge --no-ff
D1---D2 featureA
vs.
---C---D1---D2 develop git merge
Wenn beim Mergen ein "fast forward" möglich ist, würde Git beim Mergen
eines (Feature-) Branches in den develop
(oder allgemein in einen anderen
Branch) keinen separaten Commit erzeugen (Situation rechts in der Abbildung).
Damit erscheint der develop
-Branch wie eine lineare Folge von Commits. In
manchen Projekten wird dies bevorzugt, weil die Historie sehr übersichtlich
aussieht.
Allerdings verliert man die Information, dass hier ein Feature entwickelt wurde
und wann es in den develop
integriert wurde (linke Seite in obiger Abbildung).
Häufig wird deshalb ein extra Merge-Commit mit git merge --no-ff <branch>
(extra Schalter "--no-ff
") erzwungen, obwohl ein "fast forward" möglich wäre.
Anmerkung: Man kann natürlich auch über Konventionen in den Commit-Kommentaren
eine gewisse Übersichtlichkeit erzwingen. Beispielsweise könnte man vereinbaren,
dass alle Commit-Kommentare zu einem Feature "A" mit "feature a:
" starten müssen.
Git-Flow: Umgang mit Fehlerbehebung
A---B---D--------F1 master
\ \ /
\ E1---E2 fix
\ \
C1-------F2 develop
Wenn im stabilen Branch (also dem master
) ein Problem bekannt wird,
darf man es nicht einfach im master
fixen. Stattdessen wird ein extra
Branch vom master
abgezweigt, in dem der Fix entwickelt wird. Nach
Fertigstellung wird dieser Branch sowohl in den master
als auch den
develop
gemergt, damit auch im Entwicklungszweig der Fehler behoben ist.
Dadurch entspricht jeder Commit im master
einem Release.
Vereinfachte Braching-Strategie: GitHub Flow
A---B---C----D-----------E master
\ \ / /
\ ta1 topicA /
\ /
tb1---tb2---tb3 topicB
Github verfolgt eine deutlich vereinfachte Strategie: "GitHub Flow"
(vgl. "GitHub Flow" (S. Chacon)
bzw. "GitHub flow" (GitHub, Inc.)).
Hier ist der stabile Stand ebenfalls immer im master
. Features werden ebenso
wie im Git-Flow-Modell in eigenen Feature-Branches entwickelt.
Allerdings zweigen Feature-Branches immer direkt vom master
ab und werden nach
dem Test auch immer dort wieder direkt integriert (es gibt also keine weiteren
langlaufenden Branches wie develop
oder release
).
In der obigen Abbildung ist zu sehen, dass für die Entwicklung eines Features ein
entsprechender Themenbranch vom master
abgezweigt wird. Darin erfolgt dann die
Entwicklung des Features, d.h. mehrere Commits. Das Mergen des Features in den
master
erfolgt dann aber nicht lokal, sondern mit einem "Pull-Request" auf dem
Server: Sobald man im Feature-Branch einen "diskussionswürdigen" Stand hat, wird ein
Pull-Request (PR) über die Weboberfläche aufgemacht (streng genommen gehört
dies in die Kategorie “Zusammenarbeit” bzw. “Workflows”;
außerdem gehört ein PR nicht zu Git selbst, sondern zum Tooling von Github). In
einem PR können andere Entwickler den Code kommentieren und ergänzen. Jeder weitere
Commit auf dem Themenbranch wird ebenfalls Bestandteil des Pull-Requests. Parallel
laufen ggf. automatisierte Tests etc. und durch das Akzeptieren des PR in der
Weboberfläche erfolgt schließlich der Merge des Feature-Branches in den master
.
Diskussion: Git-Flow vs. GitHub Flow
In der Praxis zeigt sich, dass das Git-Flow-Modell besonders gut geeignet ist,
wenn man tatsächlich so etwas wie "Releases" hat, die zudem nicht zu häufig
auftreten.
Das GitHub-Flow-Vorgehen bietet sich an, wenn man entweder keine Releases hat
oder diese sehr häufig erfolgen (typisch bei agiler Vorgehensweise). Zudem
vermeidet man so, dass die Feature-Branches zu lange laufen, womit normalerweise
die Wahrscheinlichkeit von Merge-Konflikten stark steigt.
Achtung: Da die Feature-Branches direkt in den master
, also den stabilen
Produktionscode gemergt werden, ist es hier besonders wichtig, vor dem Merge
entsprechende Tests durchzuführen und den Merge erst zu machen, wenn alle Tests
"grün" sind.
Hier ein paar Einstiegsseiten für die Diskussion, die teilweise sehr erbittert
(und mit ideologischen Zügen) geführt wird (erinnert an die Diskussionen, welche
Linux-Distribution die bessere sei):
Wrap-Up
- Einsatz von Themenbranches für die Entwicklung
- Unterschiedliche Modelle:
- Git-Flow: umfangreiches Konzept, gut für Entwicklung mit festen Releases
- GitHub Flow: deutlich schlankeres Konzept, passend für kontinuierliche Entwicklung ohne echte Releases
Quellen
- [AtlassianGit] Become a git guru.
Atlassian Pty Ltd, 2022. - [Chacon2014] Pro Git
Chacon, S. und Straub, B., Apress, 2014. ISBN 978-1-4842-0077-3.
Kapitel 3 - [GitCheatSheet] Git Cheat Sheets
Github Inc., 2022. - [GitFlow] Git-Flow: A successful Git branching model
Driessen, V., 2010. - [GitHubFlow] GitHub Flow
Chacon, S., 2013. - [GitHubFlowGH] GitHub flow
GitHub Inc., 2022.
Arbeiten mit Git Remotes (dezentrale Repos)
TL;DR
Eine der Stärken von Git ist das Arbeiten mit verteilten Repositories. Zu jeder Workingcopy gehört
eine Kopie des Repositories, wodurch jederzeit alle Informationen einsehbar sind und auch offline
gearbeitet werden kann. Allerdings muss man für die Zusammenarbeit mit anderen Entwicklern die lokalen
Repos mit den "entfernten" Repos (auf dem Server oder anderen Entwicklungsrechnern) synchronisieren.
Beim Klonen eines Repositories mit git clone <url>
wird das fremde Repo mit dem Namen origin
im lokalen Repo festgehalten. Dieser Name wird auch als Präfix für die Branches in diesem Repo genutzt,
d.h. die Branches im Remote-Repo tauchen als origin/<branch>
im lokalen Repo auf. Diese Remote-Branches
kann man nicht direkt bearbeiten, sondern man muss diese Remote-Branches in einem lokalen Branch auschecken
und dann darin weiterarbeiten. Es können beliebig viele weitere Remotes dem eigenen Repository hinzugefügt
werden.
Änderungen aus einem Remote-Repo können mit git fetch <remote>
in das lokale Repo geholt werden.
Dies aktualisiert nur die Remote-Branches <remote>/<branch>
! Die Änderungen können anschließend
mit git merge <remote>/<branch>
in den aktuell in der Workingcopy ausgecheckten Branch gemergt werden.
(Anmerkung: Wenn mehrere Personen an einem Branch arbeiten, will man die eigenen Arbeiten in dem Branch
vermutlich eher auf den aktuellen Stand des Remote rebasen statt mergen!) Eigene Änderungen können
mit git push <remote> <branch>
in das Remote-Repo geschoben werden.
Um den Umgang mit den Remote-Branches und den davon abgeleiteten lokalen Branches zu vereinfachen,
gibt es das Konzept der "Tracking Branches". Dabei "folgt" ein lokaler Branch einem Remote-Branch.
Ein einfaches git pull
oder git push
holt dann Änderungen aus dem Remote-Branch in den ausgecheckten
lokalen Branch bzw. schiebt Änderungen im lokalen Branch in den Remote-Branch.
Videos (HSBI-Medienportal)
Lernziele
- (K3) Erzeugen eines Clones von fremden Git-Repositories
- (K3) Holen der Änderungen vom fremden Repo
- (K3) Aktualisierung der lokalen Branches
- (K3) Pushen der lokalen Änderungen ins fremde Repo
- (K3) Anlegen von lokalen Branches vs. Anlegen von entfernten Branches
- (K3) Anlegen eines Tracking Branches zum Vereinfachen der Arbeit
Nutzung von Git in Projekten: Verteiltes Git (und Workflows)
Git ermöglicht eine einfaches Zusammenarbeit in verteilten Teams. Nachdem wir die verschiedenen
Branching-Strategien betrachtet haben, soll im Folgenden die Frage betrachtet werden: Wie arbeite
ich sinnvoll über Git mit anderen Kollegen und Teams zusammen? Welche Modelle haben sich etabliert?
Clonen kann sich lohnen ...
https://github.com/Programmiermethoden-CampusMinden/PM-Lecture
---C---D---E master
=> git clone https://github.com/Programmiermethoden-CampusMinden/PM-Lecture
./PM-Lecture/ (lokaler Rechner)
---C---D---E master
^origin/master
Git-Repository mit der URL <URL-Repo>
in lokalen Ordner <directory>
auschecken:
git clone <URL-Repo> [<directory>]
- Workingcopy ist automatisch über den Namen
origin
mit dem remote Repo auf
dem Server verbunden
- Lokaler Branch
master
ist mit dem remote Branch origin/master
verbunden
("Tracking Branch", s.u.), der den Stand des master
-Branches auf dem
Server spiegelt
Für die URL sind verschiedene Protokolle möglich, beispielsweise:
- "
file://
" für über das Dateisystem erreichbare Repositories (ohne Server)
- "
https://
" für Repo auf einem Server: Authentifikation mit Username
und Passwort (!)
- "
git@
" für Repo auf einem Server: Authentifikation mit SSH-Key (diese
Variante wird im Praktikum im Zusammenspiel mit dem Gitlab-Server im
SW-Labor verwendet)
Eigener und entfernter master entwickeln sich weiter ...
https://github.com/Programmiermethoden-CampusMinden/PM-Lecture
---C---D---E---F---G master
./PM-Lecture/ (lokaler Rechner)
---C---D---E---H master
^origin/master
Nach dem Auschecken liegen (in diesem Beispiel) drei master
-Branches vor:
- Der
master
auf dem Server,
- der lokale
master
, und
- die lokale Referenz auf den
master
-Branch auf dem Server: origin/master
.
Der lokale master
ist ein normaler Branch und kann durch Commits verändert
werden.
Der master
auf dem Server kann sich ebenfalls ändern, beispielsweise weil
jemand anderes seine lokalen Änderungen mit dem Server abgeglichen hat
(git push
, s.u.).
Der Branch origin/master
lässt sich nicht direkt verändern! Das ist lediglich
eine lokale Referenz auf den master
-Branch auf dem Server und zeigt an,
welchen Stand man bei der letzten Synchronisierung hatte. D.h. erst mit dem
nächsten Abgleich wird sich dieser Branch ändern (sofern sich der entsprechende
Branch auf dem Server verändert hat).
Anmerkung: Dies gilt analog für alle anderen Branches. Allerdings wird
nur der origin/master
beim Clonen automatisch als lokaler Branch ausgecheckt.
Zur Abbildung:
Während man lokal arbeitet (Commit H
auf dem lokalen master
), kann es passieren,
dass sich auch das remote Repo ändert. Im Beispiel wurden dort die beiden Commits
F
und G
angelegt (durch git push
, s.u.).
Wichtig: Da in der Zwischenzeit das lokale Repo nicht mit dem Server abgeglichen
wurde, zeigt der remote Branch origin/master
immer noch auf den Commit
E
!
Änderungen im Remote holen und Branches zusammenführen
https://github.com/Programmiermethoden-CampusMinden/PM-Lecture
---C---D---E---F---G master
=> git fetch origin
./PM-Lecture/ (lokaler Rechner)
---C---D---E---H master
\
F---G origin/master
Änderungen auf dem Server mit dem eigenen Repo abgleichen
Mit git fetch origin
alle Änderungen holen
- Alle remote Branches werden aktualisiert und entsprechen den jeweiligen
Branches auf dem Server: Im Beispiel zeigt jetzt
origin/master
ebenso
wie der master
auf dem Server auf den Commit G
.
- Neue Branches auf dem Server werden ebenfalls "geholt", d.h. sie liegen
nach dem Fetch als entsprechende remote Branches vor
- Auf dem Server gelöschte Branches werden nicht automatisch lokal gelöscht;
dies kann man mit
git fetch --prune origin
automatisch erreichen
Wichtig: Es werden nur die remote Branches aktualisiert, nicht die lokalen Branches!
master-Branch nach "git fetch origin" zusammenführen
- Mit
git checkout master
Workingcopy auf eigenen master
umstellen
- Mit
git merge origin/master
Änderungen am origin/master
in eigenen master
mergen
- Mit
git push origin master
eigene Änderungen ins remote Repo pushen
https://github.com/Programmiermethoden-CampusMinden/PM-Lecture
---C---D---E---H---I master
\ /
F---G
./PM-Lecture/ (lokaler Rechner)
---C---D---E---H---I master
\ /^origin/master
F---G
Anmerkung: Schritt (2) kann man auch per git pull origin master
erledigen ... Ein pull
fasst fetch
und merge
zusammen (s.u.).
Anmerkung Statt dem merge
in Schritt (2) kann man auch den lokalen master
auf den
aktualisierten origin/master
rebasen und vermeidet damit die "Raute". Der pull
kann
mit der Option "--rebase
" auf "rebase" umgestellt werden (per Default wird bei pull
ein "merge" ausgeführt).
Auf dem Server ist nur ein fast forward merge möglich
Sie können Ihre Änderungen in Ihrem lokalen master
auch direkt in das remote Repo
pushen, solange auf dem Server ein fast forward merge möglich ist.
Wenn aber (wie in der Abbildung) der lokale und der remote master
divergieren,
müssen Sie den Merge wie beschrieben lokal durchführen (fetch
/merge
oder pull
)
und das Ergebnis wieder in das remote Repo pushen (dann ist ja wieder ein
fast forward merge möglich, es sei denn, jemand hat den remote master
in der
Zwischenzeit weiter geschoben - dann muss die Aktualisierung erneut durchgeführt
werden).
Branches und Remotes
Zusammenfassung: Arbeiten mit Remotes
-
Änderungen vom Server holen: git fetch <remote>
=> Holt alle Änderungen vom Repo <remote>
ins eigene Repo
(Workingcopy bleibt unangetastet!)
-
Aktuellen lokalen Branch auffrischen: git merge <remote>/<branch>
(oder alternativ git pull <remote> <branch>
)
-
Eigene Änderungen hochladen: git push <remote> <branch>
Anmerkung: push geht nur, wenn
- Ziel ein "bare"-Repository ist, und
- keine Konflikte entstehen
=> im remote Repo nur "fast forward"-Merge möglich
=> bei Konflikten erst fetch
und merge
, danach push
Anmerkung: Ein "bare"-Repository enthält keine Workingcopy, sondern nur
das Repo an sich. Die ist bei Repos, die Sie auf einem Server wie Gitlab oder
Github anlegen, automatisch der Fall. Sie können aber auch lokal ein solches
"bare"-Repo anlegen, indem Sie beim Initialisieren den Schalter --bare
mitgeben: git init --bare
...
Beispiel
git fetch origin # alle Änderungen vom Server holen
git checkout master # auf lokalen Master umschalten
git merge origin/master # lokalen Master aktualisieren
... # Herumspielen am lokalen Master
git push origin master # lokalen Master auf Server schicken
Vereinfachung: Tracking Branches
Vorsicht: pull
und push
beziehen sich nur auf ausgecheckten Tracking Branch
Einrichten von Tracking Branches
-
git clone
: lokaler master
trackt automatisch origin/master
-
Remote Branch als Tracking Branch einrichten:
- Änderungen aus remote Repo holen:
git fetch <remote>
- Tracking Branch anlegen:
git checkout -t <remote>/<branch>
(=> Option -t
richtet den remote Branch als Tracking Branch ein)
-
Lokalen neuen Branch ins remote Repo schicken und als Tracking Branch einrichten:
- Lokalen Branch erzeugen:
git checkout -b <branch>
- Lokalen Branch ins Repo schicken:
git push -u <remote> <branch>
(=> Option -u
richtet den lokalen Branch als Tracking Branch ein)
Hinzufügen eines (weiteren) Remote Repository
Sie können einem Repo beliebig viele Remotes hinzufügen:
git remote add <name> <url>
Beispiel: git remote add andi git@github.com:andi/repo.git
- Remote
origin
wird bei clone
automatisch angelegt
- Ansehen der Remotes mit
git remote -v
fetch
, push
und pull
jeweils über den vergebenen Namen
Beispiel: git fetch andi
oder git push origin master
Wrap-Up
Challenges
Interaktive Git-Tutorials: Schaffen Sie die Rätsel?
Zusammenarbeit: Git-Workflows und Merge-/Pull-Requests
TL;DR
Git erlaubt unterschiedliche Formen der Zusammenarbeit.
Bei kleinen Teams kann man einen einfachen zentralen Ansatz einsetzen. Dabei gibt es ein zentrales
Repo auf dem Server, und alle Team-Mitglieder dürfen direkt in dieses Repo pushen. Hier muss man
sich gut absprechen und ein vernünftiges Branching-Schema ist besonders wichtig.
In größeren Projekten gibt es oft ein zentrales öffentliches Repo, wo aber nur wenige Personen
Schreibrechte haben. Hier forkt man sich dieses Repo, erstellt also eine öffentliche Kopie auf dem
Server. Diese Kopie klont man lokal und arbeitet hier und pusht die Änderungen in den eigenen öffentlich
sichtbaren Fork. Um die Änderungen ins Projekt-Repo integrieren zu lassen, wird auf dem Server ein
sogenannter Merge-Request (Gitlab) bzw. Pull-Request (GitHub) erstellt. Dies erlaubt zusätzlich ein
Review und eine Diskussion direkt am Code. Damit man die Änderungen im Hauptprojekt in den eigenen
Fork bekommt, trägt man das Hauptprojekt als weiteres Remote in die Workingcopy ein und aktualisiert
regelmäßig die Hauptbranches, von denen dann auch die eigenen Feature-Branches ausgehen sollten.
Videos (HSBI-Medienportal)
Lernziele
- (K2) Git-Workflows für die Zusammenarbeit
- (K2) Unterschied zwischen einem Pull/Merge und einem Pull/Rebase
- (K2) Welche Commits werden Bestandteil eines Merge-Requests (und warum)
- (K3) Einsatz des zentralisierten Workflows
- (K3) Einsatz des einfachen verteilten Workflows mit unterschiedlichen Repos
- (K3) Aktualisieren Ihres Clones und Ihres Forks mit Änderungen aus dem blessed Repo
- (K3) Erstellen von Beiträgen zu einem Projekt über Merge-Requests
- (K3) Aktualisierung von Merge-Requests
- (K3) Diskussion über den Code in Merge-Requests
Nutzung von Git in Projekten: Verteiltes Git (und Workflows)
Git ermöglicht ein einfaches und schnelles Branchen. Dies kann man mit
entsprechenden Branching-Strategien sinnvoll für die SW-Entwicklung einsetzen.
Auf der anderen Seite ermöglicht Git ein sehr einfaches verteiltes Arbeiten.
Auch hier ergeben sich verschiedene Workflows, wie man mit anderen Entwicklern
an einem Projekt arbeiten will/kann.
Im Folgenden sollen also die Frage betrachtet werden: Wie gestalte ich die Zusammenarbeit?
Antwort: Workflows mit Git ...
Zusammenarbeit: Zentraler Workflow mit Git (analog zu SVN)
In kleinen Projektgruppen wie beispielsweise Ihrer Arbeitsgruppe wird häufig
ein einfacher zentralisierter Workflow bei der Versionsverwaltung genutzt. Im
Mittelpunkt steht dabei ein zentrales Repository, auf dem alle Teammitglieder
gleichberechtigt und direkt pushen dürfen.
-
Vorteile:
- Einfachstes denkbares Modell
- Ein gemeinsames Repo (wie bei SVN)
- Alle haben Schreibzugriff auf ein gemeinsames Repo
-
Nachteile:
- Definition und Umsetzung von Rollen mit bestimmten Rechten ("Manager",
"Entwickler", "Gast-Entwickler", ...) schwierig bis unmöglich
(das ist kein Git-Thema, sondern hängt von der Unterstützung durch den
Anbieter des Servers ab)
- Jeder darf überall pushen: Enge und direkte Abstimmung nötig
- Modell funktioniert meist nur in sehr kleinen Teams (2..3 Personen)
Zusammenarbeit: Einfacher verteilter Workflow mit Git
In großen und/oder öffentlichen Projekten wird üblicherweise ein Workflow
eingesetzt, der auf den Möglichkeiten von verteilten Git-Repositories basiert.
Dabei wird zwischen verschiedenen Rollen ("Integrationsmanager", "Entwickler")
unterschieden.
Sie finden dieses Vorgehen beispielsweise beim Linux-Kernel und auch häufig bei
Projekten auf Github.
Den hier gezeigten Zusammenhang kann man auf weitere Ebenen verteilen, vgl. den
im Linux-Kernel gelebten "Dictator and Lieutenants Workflow" (siehe Literatur).
Hinweis: Hier wird nur die Zusammenarbeit im verteilten Team dargestellt. Dazu
kommt noch das Arbeiten mit verschiedenen Branches!
Anmerkung: In der Workingcopy wird das eigene (öffentliche) Repo oft als origin
und das geschützte ("blessed") Master-Repo als upstream
referenziert.
Anmerkungen zum Forken
Sie könnten auch das Original-Repo direkt clonen. Allerdings würden dann die
push
dort aufschlagen, was in der Regel nicht erwünscht ist (und auch nicht
erlaubt ist).
Deshalb forkt man das Original-Repo auf dem Server, d.h. auf dem Server wird
eine Kopie des Original-Repos im eigenen Namespace angelegt. Auf diese Kopie
hat man dann uneingeschränkten Zugriff.
Anmerkungen zu den Namen für die Remotes: origin
und upstream
Üblicherweise checkt man die Kopie lokal aus (d.h. erzeugt einen Clone).
In der Workingcopy verweist dann origin
auf die Kopie. Um Änderungen am
Original-Repo zu erhalten, fügt man dieses unter dem Namen upstream
als
weiteres Remote-Repo hinzu. Dies ist eine nützliche Konvention.
Um Änderungen aus dem Original-Repo in den eigenen Fork (und die Workingcopy)
zu bringen, führt man dann einfach folgendes aus (im Beispiel für den master
):
$ git checkout master # Workingcopy auf master
$ git pull upstream master # Aktualisiere lokalen master mit master aus Original-Repo
$ git push origin master # Pushe lokalen master in den Fork
Feature-Branches aktualisieren: Mergen mit master vs. Rebase auf master
Im Netz finden sich häufig Anleitungen, wonach man Änderungen im master
mit einem Merge in den Feature-Branch holt, also sinngemäß:
$ git checkout master # Workingcopy auf master
$ git pull upstream master # Aktualisiere lokalen master mit master aus Vorgabe-Repo
$ git checkout feature # Workingcopy auf feature
$ git merge master # Aktualisiere feature: Merge master in feature
$ git push origin feature # Push aktuellen feature ins Team-Repo
Das funktioniert rein technisch betrachtet.
Allerdings spielt in den meisten Git-Projekten der master
üblicherweise eine besondere Rolle
(vgl. Branching-Strategien) und ist
üblicherweise stets das Ziel eines Merge, aber nie die Quelle! D.h. per Konvention geht der
Fluß von Änderungen stets in den master
(und nicht heraus).
Wenn man sich nicht an diese Konvention hält, hat man später möglicherweise Probleme, die
Merge-Historie zu verstehen (welche Änderung kam von woher)!
Um die Änderungen im master
in einen Feature-Branch zu bekommen, sollte deshalb ein Rebase
(des Feature-Branches auf den master
) vor einem Merge (des master
in den Feature-Branch)
bevorzugt werden.
Merk-Regel: Merge niemals nie den master
in Feature-Branches!
Achtung: Ein Rebase bei veröffentlichten Branches ist problematisch, da Dritte auf diesem
Branch arbeiten könnten und entsprechend auf die Commit-IDs angewiesen sind. Nach einem Rebase
stimmen diese Commit-IDs nicht mehr, was normalerweise mindestens zu Verärgerung führt ... Die
Dritten müssten ihre Arbeit dann auf den neuen Feature-Branch (d.h. den Feature-Branch nach
dessen Rebase) rebasen ... vgl. auch "The Perils of Rebasing" in Abschnitt "3.6 Rebasing" in
[Chacon2014].
Mögliches Szenario im Praktikum
Im Praktikum haben Sie das Vorgabe-Repo. Dieses könnten Sie als upstream
in Ihre lokale
Workingcopy einbinden.
Mit Ihrem Team leben Sie vermutlich einen zentralen Workflow, d.h. Sie binden Ihr gemeinsames
Repo als origin
in Ihre lokale Workingcopy ein.
Dann müssen Sie den lokalen master
aus beiden Remotes aktualisieren. Zusätzlich
wollen Sie Ihren aktuellen Themenbranch auf den aktuellen master
rebasen.
$ git checkout master # Workingcopy auf master
$ git pull upstream master # Aktualisiere lokalen master mit master aus Vorgabe-Repo
$ git pull origin master # Aktualisiere lokalen master mit master aus Team-Repo
$ git push origin master # Pushe lokalen master in das Team-Repo zurück
$ git rebase master feature # Rebase feature auf den aktuellen lokalen master
$ git push -f origin feature # Push aktuellen feature ins Team-Repo ("-f" wg. geänderter IDs durch rebase)
Anmerkung: Dabei können in Ihrem master
die unschönen "Rauten" entstehen. Wenn
Sie das vermeiden wollen, tauschen Sie den zweiten und den dritten Schritt und führen
den Pull gegen den Upstream-master
als pull --rebase
durch. Dann müssen Sie Ihren
lokalen master
allerdings auch force-pushen in Ihr Team-Repo und die anderen
Team-Mitglieder sollten darüber informiert werden, dass sich der master
auf
inkompatible Weise geändert hat ...
Kommunikation: Merge- bzw. Pull-Requests
Mergen kann man auf der Konsole (oder in der IDE) und anschließend die (neuen) Branches
auf den Server pushen.
Die verschiedenen Git-Server erlauben ebenfalls ein GUI-gestütztes Mergen von Branches:
"Merge-Requests" (MR, Gitlab) bzw. "Pull-Requests" (PR, Github). Das hat gegenüber
dem lokalen Mergen wichtige Vorteile: Andere Entwickler sehen den beabsichtigten Merge
(frühzeitig) und können direkt den Code kommentieren und die vorgeschlagenen Änderungen
diskutieren, aber auch allgemeine Kommentare abgeben.
Falls möglich, sollte man einen MR/PR immer dem Entwickler zuweisen, der sich weiter
um diesen MR/PR kümmern wird (also zunächst ist man das erstmal selbst). Zusätzlich
kann man einen Reviewer bestimmen, d.h. wer soll sich den Code ansehen.
Hier ein Screenshot der Änderungsansicht unseres Gitlab-Servers (SW-Labor):
Nachfolgend für den selben MR aus der letzten Abbildung noch die reine Diskussionsansicht:
Best Practices bei Merge-/Pull-Requests
- MR/PR so zeitig wie möglich aufmachen
- Am besten sofort, wenn ein neuer Branch auf den Server gepusht wird!
- Ggf. mit dem Präfix "WIP" im Titel gegen unbeabsichtigtes vorzeitiges Mergen
sperren ... (bei GitHub als "Draft"-PR öffnen)
- Auswahl Start- und Ziel-Branch (und ggf. Ziel-Repo)
- Es gibt verschiedene Stellen, um einen MR/PR zu erstellen. Manchmal kann man nur
noch den Ziel-Branch einstellen, manchmal beides.
- Bitte auch darauf achten, welches Ziel-Repo eingestellt ist! Bei Forks wird
hier immer das Original-Repo voreingestellt!
- Den Ziel-Branch kann man ggf. auch nachträglich durch Editieren des MR/PR
anpassen (Start-Branch und Ziel-Repo leider nicht, also beim Erstellen aufpassen!).
- Titel (Summary): Das ist das, was man in der Übersicht sieht!
- Per Default wird die letzte Commit-Message eingesetzt.
- Analog zur Commit-Message: Bitte hier unbedingt einen sinnvollen Titel
einsetzen: Was macht der MR/PR (kurz)?
- Beschreibung: Was passiert, wenn man diesen MR/PR akzeptiert (ausführlicher)?
- Analog zur Commit-Message sollte hier bei Bedarf die Summary ausformuliert
werden und beschreiben, was der MR/PR ändert.
- Assignee: Wer soll sich drum kümmern?
- Ein MR/PR sollte immer jemanden zugewiesen sein, d.h. nicht "unassigned"
sein. Ansonsten ist nicht klar, wer den Request durchführen/akzeptieren
soll.
- Außerdem taucht ein nicht zugewiesener MR/PR nicht in der Übersicht "meiner"
MR/PR auf, d.h. diese MR/PR haben die Tendenz, vergessen zu werden!
- Diskussion am (und neben) dem Code
- Nur die vorgeschlagenen Code-Änderungen diskutieren!
- Weitergehende Diskussionen (etwa über Konzepte o.ä.) besser in separaten Issues
erledigen, da sonst die Anzeige des MR/PR langsam wird (ist beispielsweise ein
Problem bei Gitlab).
- Weitere Commits auf dem zu mergenden Branch gehen automatisch mit in den Request
- Weitere Entwickler kann man mit "
@username
" in einem Kommentar auf "CC"
setzen und in die Diskussion einbinden
Anmerkung: Bei Gitlab (d.h. auch bei dem Gitlab-Server im SW-Labor) gibt es
"Merge-Requests" (MR). Bei Github gibt es "Pull-Requests" (PR) ...
Wrap-Up
-
Git-Workflows für die Zusammenarbeit:
- einfacher zentraler Ansatz für kleine Arbeitsgruppen vs.
- einfacher verteilter Ansatz mit einem "blessed" Repo (häufig in Open-Source-Projekten zu finden)
-
Aktualisieren Ihres Clones und Ihres Forks mit Änderungen aus dem "blessed" Repo
-
Unterschied zwischen einem Pull/Merge und einem Pull/Rebase
-
Erstellen von Beiträgen zu einem Projekt über Merge-Requests
- Welche Commits werden Bestandteil eines Merge-Requests (und warum)
- Diskussion über den Code in Merge-Requests
Git Worktree
TL;DR
Git Worktree erlaubt es, Branches in separaten Ordnern auszuchecken. Diese Ordner sind
mit der Workingcopy verknüpft, d.h. alle Änderungen über Git-Befehle werden automatisch
mit der Workingcopy "synchronisiert". Im Unterschied zum erneuten Clonen hat man in den
verknüpften Ordnern aber nicht die gesamte Historie noch einmal neu als .git
-Ordner,
sondern nur den Link auf die Workingcopy, wodurch viel Platz gespart wird. Damit bilden
Git Worktrees eine elegante Möglichkeit, parallel an verschiedenen Branches zu arbeiten.
Videos (HSBI-Medienportal)
Lernziele
- (K2) Vorteile von Git Worktree
- (K2) Prinzipielle Arbeitsweise von Git Worktree
- (K3) Anlegen von Worktrees
- (K3) Anzeigen von Worktrees
- (K3) Löschen von Worktrees
Git Worktree - Mehrere Branches parallel auschecken
Szenario
- Sie arbeiten an einem Projekt
- Großes Repo mit vielen Versionen und Branches
- Ungesicherte Änderungen im Featurebranch
- Wichtige Bugfixes an alter Version nötig
Lösungsansätze
git stash
nutzen und Branch wechseln
- Repo erneut in anderem Ordner auschecken
Probleme
-
git stash
und git switch
Funktioniert für die meisten Fälle relativ gut und ist daher die "Lösung to go".
Aber Sie müssen später aufpassen, dass Sie auch wirklich wieder im richtigen
Branch sind, wenn Sie die Änderungen im Stash anwenden (git stash pop
)! Und
wenn Sie mehrere Einträge in der Stash-Liste haben, kann es recht schnell recht
unübersichtlich werden - zu welchem Branch gehören welche Einträge in der
Stash-Liste?
Außerdem kann es gerade in größeren Projekten passieren, dass sich die Konfiguration
zwischenzeitlich ändert. Wenn Sie jetzt in der IDE einfach auf einen alten Stand
mit einer anderen Konfiguration wechseln, kann es schnell passieren, dass sich die
IDE "verschluckt" und Sie dadurch viel Arbeit haben.
-
Nochmal woanders auschecken
Im Prinzip ist das eine Möglichkeit. Sie können dann den anderen Ordner in Ihrer
IDE als neues Projekt öffnen und sofort starten.
Aber: Sie benötigen noch einmal den Platz auf der Festplatte/SSD/... wie für die
ursprüngliche Workingcopy! Das kann bei alten/großen Projekten schnell recht
groß werden und Probleme verursachen.
Außerdem ist die Synchronisierung zwischen den beiden Workingcopies (der ursprünglichen
und der neuen) nicht vorhanden bzw. das müssen Sie manuell per git push
und git pull
(in jeder Kopie des Repos!) erledigen!
Git Worktree kann helfen!
=> Mehrere Branches gleichzeitig auschecken (als neue Ordner im Dateisystem)
How to use Git Worktree
Worktree anlegen
git worktree add <path> <branch>
Legt neuen Ordner <path>
an und checkt darin <branch>
als "linked worktree" aus.
Mit git worktree add ../wuppie foo
würden Sie also parallel zum aktuellen Ordner
(wo Ihre Workingcopy enthalten ist) einen neuen Ordner wuppie/
anlegen und darin
den Branch foo
auschecken.
Wenn Sie in den Ordner wuppie
wechseln, finden Sie auch eine Datei .git
. Darin
ist lediglich der Pfad zur Workingcopy vermerkt, damit Git Änderungen auch in die
eigentliche Workingcopy spiegeln kann. Dies ist der sogenannte "linked worktree".
Im Vergleich dazu finden Sie in der eigentlichen Workingcopy einen Ordner .git
,
der üblicherweise die gesamte Historie etc. enthält und entsprechend groß werden kann.
Den Befehl git worktree add
gibt es in verschiedenen Versionen. In der Kurzform
git worktree add <path>
würde ein neuer Branch angelegt und ausgecheckt, der der
letzten Komponente von <path>
entspricht ...
Warnung: Nicht in selben Ordner oder in Unterordner auschecken!
Die neuen Worktrees sollten immer außerhalb der Workingcopy liegen! Sie können
Git sehr schnell sehr gründlich durcheinanderbringen, wenn Sie einen Worktree im
selben Ordner oder in einem Unterordner anlegen.
git worktree
sollte nach Möglichkeit nicht zusammen mit Git Submodules eingesetzt
werden (unstabiles Verhalten)!
Worktree wechseln
- Worktrees anzeigen:
git worktree list
- Worktree wechseln: Ordner wechseln (IDE: neues Projekt)
Die Worktrees sind aus Sicht des Dateisystems einfach Ordner. Die .git
-Datei verlinkt
für Git den Ordner mit der ursprünglichen Workingcopy.
Um also mit einem Worktree arbeiten zu können, wechseln Sie einfach das Verzeichnis. In
einer IDE würden Sie entsprechend ein neues Projekt anlegen. So können Sie gleichzeitig
in verschiedenen Branches arbeiten.
Änderungen in einem Worktree werden automatisch in die ursprüngliche Workingcopy gespiegelt.
Analog können Sie in einem Worktree auf die aktuelle Historie aus der ursprünglichen Workingcopy
zugreifen.
Hinweis: Sie können in den Ordnern zwar Branches wechseln, aber nicht auf einen Branch,
der bereits in einem anderen Ordner (Worktree) ausgecheckt ist. Es ist gute Praxis, dass
die Ordnernamen dem ausgecheckten Branch (linked Worktree) entsprechen, um Verwirrungen
zu vermeiden.
Worktree löschen
git worktree remove <worktree>
Sofern der Worktree "clean" ist, es also keine nicht comitteten Änderungen gibt, können
Sie mit git worktree remove <worktree>
einen Worktree <worktree>
wieder löschen.
Dabei bleibt der Ordner erhalten - Sie können ihn selbst löschen oder später wiederverwenden.
Wrap-Up
Git Worktree: Auschecken von Branches in separate Ordner