Subsections of Versionierung mit Git
Intro: Versionskontrolle in der Softwareentwicklung
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.
- (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
-
git init
=> Erzeugt neues Repository im akt. Verzeichnis
-
git clone <url>
=> Erzeugt (verlinkte) Kopie des Repos unter
<url>
Wrap-Up
- Git: Versionsmanagement mit dezentralen Repositories
- Anlegen eines lokalen Repos mit
git init
- Clonen eines existierenden Repos mit
git clone <url>
- [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 1 und 2 - [GitCheatSheet] Git Cheat Sheets
Github Inc., 2022.
Basics der Versionsverwaltung mit Git (lokale Repos)
Ä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.
- (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 .
(odergit 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
-
Liste aller Commits:
git log
git log -<n>
odergit log --since="3 days ago"
Meldungen eingrenzen ...git log --stat
Statistik ...git log --author="pattern"
Commits eines Autorsgit log <file>
Änderungen einer Datei
-
Inhalt eines Commits:
git show
Ä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, zeigtgit 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
- generierte 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>
odergit restore <file>
- Änderungen in Stage:
git reset HEAD <file>
odergit restore --staged <file>
=> Hinweise von
git status
beachten! - Änderungen nicht in Stage:
-
Datei aus altem Stand holen:
git checkout <commit> <file>
, odergit 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
undgit 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 ...)
Lesen (und beachten) Sie unbedingt auch "How to Write a Git Commit Message"!
Ausflug "Conventional Commits"
Die Commit-Messages dienen vor allem der Dokumentation und werden von Entwicklern gelesen.
Wenn man die Messages ein wenig stärker formalisieren würde, dann könnte man diese aber auch mit Tools verarbeiten und beispielsweise automatisiert Changelogs oder Release-Texte verfassen!
Betrachten Sie einmal das Projekt ConventionalCommits.org. Dies ist ein solcher Versuch, die Commit-Messages (a) einheitlicher und lesbarer zu gestalten und (b) auch eine Tool-gestützte Auswertung zu erlauben.
Das Projekt schlägt als Erweitung der üblichen Regeln zum Formatieren von Commit-Messages vor, dass in der ersten Zeile der Summary noch eine Abkürzung für die in diesem Commit erfolgte Änderung (Bug-Fix, neues Feature, ...) vorangestellt wird. Dieser Abkürzung kann in Klammern noch der Scope der Änderung hinzugefügt werden, beispielsweise den Bereich im Projekt, der von diesem Commit berührt wird. Wenn es eine breaking change ist, also alter Code nach dieser Änderung sich anders verhält oder vielleicht sogar nicht mehr kompiliert, wird noch ein "!" hinter dem Typ der Änderung ergänzt.
Beispiel: Stellen Sie sich vor, im Dungeon-Projekt wurde ein neues Verhalten hinzugefügt.
-
Normalerweise hätten Sie vielleicht diese Message geschrieben (angepasste Version aus Dungeon-CampusMinden/Dungeon/pull/469):
add fight skill - `DamageProjectileSkill` creates a new entity which causes `HealthDamage` when hitting another entity - `FireballSkill` is a more concrete implementation of this - Melee skills can be created with `DamageProjectileSkill` using a customised range - Example: the `FireballSkill` has a range of 10, a melee would have a considerably smaller range fixes #24 fixes #126 fixes #224
-
Mit ConventionalCommits.org könnte das dann so aussehen:
feat: add fight skill - `DamageProjectileSkill` creates a new entity which causes `HealthDamage` when hitting another entity - `FireballSkill` is a more concrete implementation of this - Melee skills can be created with `DamageProjectileSkill` using a customised range - Example: the `FireballSkill` has a range of 10, a melee would have a considerably smaller range fixes #24 fixes #126 fixes #224
Da es sich um ein neues Feature handelt, wurde der Summary in der ersten Zeile ein
feat:
vorangestellt.Die zu verwendenden Typen/Abkürzungen sind im Prinzip frei definierbar. Das Projekt ConventionalCommits.org schlägt eine Reihe von Abkürzungen vor. Auf diese Weise sollen in möglichst allen Projekten, die Conventional Commits nutzen, die selben Abkürzungen/Typen eingesetzt werden und so eine Tool-gestützte Auswertung möglich werden.
-
Oder zusätzlich mit dem Scope der Änderung:
feat(game): add fight skill - `DamageProjectileSkill` creates a new entity which causes `HealthDamage` when hitting another entity - `FireballSkill` is a more concrete implementation of this - Melee skills can be created with `DamageProjectileSkill` using a customised range - Example: the `FireballSkill` has a range of 10, a melee would have a considerably smaller range fixes #24 fixes #126 fixes #224
Der Typ
feat
wurde hier noch ergänzt um einen frei definierbaren Identifier für den Projektbereich. Dieser wird in Klammern direkt hinter den Typ notiert (hierfeat(game):
).Im Beispiel habe ich als Bereich "game" genommen, weil die Änderung sich auf den Game-Aspekt des Projekts bezieht. Im konkreten Projekt wären andere Bereiche eventuell "dsl" (für die im Projekt entwickelte Programmiersprache plus Interpreter) und "blockly" (für die Integration von Google Blockly zur Programmierung des Dungeons mit LowCode-Ansätzen). Das ist aber letztlich vom Projekt abhängig und weitestgehend Geschmackssache.
-
Oder zusätzlich noch als Auszeichnung "breaking change" (hier mit scope, geht aber auch ohne scope):
feat(game)!: add fight skill - `DamageProjectileSkill` creates a new entity which causes `HealthDamage` when hitting another entity - `FireballSkill` is a more concrete implementation of this - Melee skills can be created with `DamageProjectileSkill` using a customised range - Example: the `FireballSkill` has a range of 10, a melee would have a considerably smaller range fixes #24 fixes #126 fixes #224
Angenommen, das neue Feature muss in der API etwas ändern, so dass existierender Code nun nicht mehr funktionieren würde. Dies wird mit dem extra Ausrufezeichen hinter dem Typ/Scope kenntlich gemacht (hier
feat(game)!:
).Zusätzlich kann man einen "Footer" in die Message einbauen, also eine extra Zeile am Ende, die mit dem String "BREAKING CHANGE:" eingeleitet wird. (vgl. Conventional Commits > Examples)
Es gibt noch viele weitere Initiativen, Commit-Messages lesbarer zu gestalten und zu vereinheitlichen. Schauen Sie sich beispielsweise einmal gitmoji.dev an. (Mit einem Einsatz in einem professionellen Umfeld wäre ich hier aber sehr ... vorsichtig.)
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
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.
- [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 2 - [GitCheatSheet] Git Cheat Sheets
Github Inc., 2022.
Git Branches: Features unabhängig entwickeln und mit Git verwalten
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.
- (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
odergit 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 denmaster
gehen, d.h. dermaster
würde sich ab CommitC
parallel zuwuppie
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 vonmaster
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ührtA
undB
zusammen:B
wird inA
gemergt- Wichtig: Der Merge-Commit (sofern nötig) findet hierbei in
A
statt!
In der Abbildung ist
A
dermaster
undB
derfix
. - Die Workingcopy ist auf
-
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ührtA
undB
zusammen:B
wird inA
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
-
Branches sind wie Zeiger auf letzten Stand (Commit) eines Zweiges
-
HEAD
: Spezieller Pointer- Zeigt auf den aktuellen Branch der Workingcopy
-
Früheren Commit auschecken (ohne Branch): "headless state"
-
Workingcopy ist auf früherem Commit
-
Kein Branch => Änderungen gehen verloren!
Eventuelle Änderungen würden ganz normal als Commits auf dem
HEAD
-Branch aufgezeichnet. Sobald man aber einen anderen Branch auscheckt, wird derHEAD
auf diesen anderen Branch gesetzt, so dass die eben gemachten Commits "in der Luft hängen". Sofern man die SHA's kennt, kommt man noch auf die Commits zurück. Allerdings laufen von Zeit zu Zeit interne Aufräum-Aktionen, so dass die Chance gut steht, dass die "kopflosen" Commits irgendwann tatsächlich verschwinden.
-
Wrap-Up
- Anlegen von Branches mit
git branch
- Umschalten der Workingcopy auf anderen Branch:
git checkout
odergit switch
- Mergen von Branches und Auflösen von Konflikten:
git merge
- Verschieben von Branches mit
git rebase
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?
Mergen am Beispiel
Sie verwalten Ihr Projekt mit Git. Es existieren zwei Branches: master
(zeigt
auf Commit $C$) und feature
(zeigt auf Version $F$). In Ihrer Workingcopy
haben Sie den Branch feature
ausgecheckt:
(1) Mit welcher Befehlsfolge können Sie den Branch feature
in den Branch master
mergen, so dass nach dem Merge die im folgenden Bild dargestellte Situation
entsteht?
(Der Merge läuft ohne Konflikte ab. Es ist irrelevant, welcher Branch am Ende in der Workingcopy ausgecheckt ist.)
(2) Wie können Sie erreichen, dass es keinen Merge-Commit gibt, sondern dass die Änderungen
in $D$ und $F$ im master
als eine lineare Folge von Commits erscheinen?
Interaktive Git-Tutorials: Schaffen Sie die Rätsel?
- [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.
Branching-Strategien mit Git
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.
- (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.
-
Vorteil: Die Entwicklung im Themenbranch ist in sich gekapselt und stört nicht die Entwicklung in anderen Branches (und diese stören umgekehrt nicht die Entwicklung im Themenbranch).
-
Nachteil:
- Mangelnder Überblick durch viele Branches
- Ursprung der Themenbranches muss überlegt gewählt werden, d.h. alle dort benötigten Features müssen zu dem Zeitpunkt im Hauptzweig vorhanden sein
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):
- Git-Flow-Modell von Vincent Driessen
- Kurzer Überblick über das GitHub-Flow-Modell
- Diskussion des GitHub-Flow-Modells (Github)
- Luca Mezzalira: "Git-Flow vs Github Flow"
- Scott Schacon, Autor des Pro-Git-Buchs
- Noch eine (längere) Betrachtung (Robin Daugherty)
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
- [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)
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.
- (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/Prog2-Lecture
---C---D---E master
=> git clone https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture
./Prog2-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 Branchorigin/master
verbunden ("Tracking Branch", s.u.), der den Stand desmaster
-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/Prog2-Lecture
---C---D---E---F---G master
./Prog2-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/Prog2-Lecture
---C---D---E---F---G master
=> git fetch origin
./Prog2-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 dermaster
auf dem Server auf den CommitG
. - 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 eigenenmaster
umstellen - Mit
git merge origin/master
Änderungen amorigin/master
in eigenenmaster
mergen - Mit
git push origin master
eigene Änderungen ins remote Repo pushen
https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture
---C---D---E---H---I master
\ /
F---G
./Prog2-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
-
Eigenen (neuen) lokalen Branch ins remote Repo schicken
git push <remote> <branch>
-
Neuer Branch im remote Repo
git fetch <remote>
holt (auch) alle neuen Branches- Lokale Änderungen an remote Branches nicht möglich! => Remote Branch in lokalen Branch mergen (oder auschecken)
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 alternativgit 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
-
Tracking Branch: lokaler Branch, der remote Branch "verfolgt"
- Beispiel: lokaler
master
-Branch folgtorigin/master
per Default
- Beispiel: lokaler
-
Vereinfachung im Workflow:
git pull
entsprichtgit fetch <remote>
plusgit merge <remote>/<branch>
git push
entsprichtgit push <remote> <branch>
Vorsicht: pull
und push
beziehen sich nur auf ausgecheckten Tracking Branch
Einrichten von Tracking Branches
-
git clone
: lokalermaster
trackt automatischorigin/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)
- Änderungen aus remote Repo holen:
-
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)
- Lokalen Branch erzeugen:
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 beiclone
automatisch angelegt - Ansehen der Remotes mit
git remote -v
fetch
,push
undpull
jeweils über den vergebenen Namen
Beispiel: git fetch andi
oder git push origin master
Wrap-Up
-
Synchronisierung des lokalen Repos mit anderen Repos
- Repo kopieren:
git clone <url>
- Interner Name fürs fremde Repo:
origin
- Änderungen vom fremden Repo holen:
git fetch <remote>
- Änderungen in lokalen Branch einpflegen:
git merge <remote>/<branch>
- Eigene Änderungen ins fremde Repo schieben:
git push <remote> <branch>
- Repo kopieren:
-
Tracking Branches (Konzept, Anwendung)
- Remote Branches können lokal nicht verändert werden:
- In lokale Branches mergen, oder
- Tracking Branches anlegen => einfaches
pull
undpush
nutzen
- Tracking Branches sind lokale Branches, die remote Branches verfolgen ("tracken")
- Remote Branches können lokal nicht verändert werden:
Synchronisierung mit Remote-Repos
Sie haben ein Repo von github.com geklont. Beide Repos, das Original auf dem Server als auch Ihre lokale Kopie, haben sich danach unabhängig voneinander weiter entwickelt (siehe Skizze).
Wie können Sie Ihre Änderung im lokalen Repo auf den Server pushen? Analysieren Sie die Situation und erklären Sie zwei verschiedene Lösungsansätze und geben Sie jeweils die entsprechenden Git-Befehle an.
Interaktive Git-Tutorials: Schaffen Sie die Rätsel?
- [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.
Zusammenarbeit: Git-Workflows und Merge-/Pull-Requests
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.
- (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.
-
Es existiert ein geschütztes ("blessed") Master-Repo
- Stellt die Referenz für das Projekt dar
- Push-Zugriff nur für ausgewählte Personen ("Integrationsmanager")
-
Entwickler
- Forken das Master-Repo auf dem Server und klonen ihren Fork lokal
- Arbeiten auf lokalem Klon: Unabhängige Entwicklung eines Features
- Pushen ihren Stand in ihren Fork (ihr eigenes öffentliches Repo): Veröffentlichung des Beitrags zum Projekt (sobald fertig bzw. diskutierbar)
- Lösen Pull- bzw. Merge-Request gegen das Master-Repo aus: Beitrag soll geprüft und ins Projekt aufgenommen werden (Merge ins Master-Repo durch den Integrationsmanager)
-
Integrationsmanager
- Prüft die Änderungen im Pull- bzw. Merge-Request und fordert ggf. Nacharbeiten an bzw. lehnt Integration ab (technische oder politische Gründe)
- Führt Merge der Entwickler-Zweige mit den Hauptzweigen durch Akzeptieren der Pull- bzw. Merge-Requests durch: Beitrag der Entwickler ist im Projekt angekommen und ist beim nächsten Pull in deren lokalen Repos vorhanden
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
- [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 5 (Distributed Git) sowie Abschnitt 4.8 (Gitlab) und Kapitel 6 (GitHub) - [GitCheatSheet] Git Cheat Sheets
Github Inc., 2022.