Git ist eine Software, die die zu einem “Projekt” gehörenden Dateien verwalten kann.
Unter “Projekt” kann man z.B. verstehen
Ein Projekt, das von Git verwaltet wird, heißt Repository.
Git kann “Speicherpunkte” anlegen, die den Zustand des Projekts zu einem Zeitpunkt festhalten. Die Speicherpunkte heißen Commits.
Zu jeder Zeit kann auf “alte” Commits des Projekts zurückgegriffen werden.
Git kann den Zustand des Projekts zwischen verschiedenen Commits, bzw. zwischen einem “alten” Commit und dem “aktuellen” Stand, vergleichen.
(Welche Dateien sind neu, wurden umbenannt oder gelöscht, welche Dateien wurden wie inhaltlich geändert, etc.)
Repositories können zwischen verschiedenen Computern (oder zwischen verschiedenenen Verzeichnissen auf einem Computer) vervielfältigt, und untereinander synchronisiert werden (auch über das Internet).
Wenn man Git sinnvoll einsetzt, kann es einem nützen:
verringert die Gefahr, Dateien versehentlich zu löschen oder zu verändern
ermöglicht “Experimente”, ohne die “gute” Version eines Projekts kaputtzumachen
ermöglicht einfaches Anlegen und Wiederherstellen von Backups
Zusammenarbeit zwischen Entwicklern/Autoren (gegenseitiger Austausch des Fortschritts)
hilft bei Dokumentation/Nachvollziehbarkeit der Entwicklung eines Projekts (wann, warum, und von wem wurde eine Änderung gemacht?)
Ein Commit repräsentiert den Zustand des Projekts zu einem bestimmten Zeitpunkt.
Zu jedem Commit speichert Git:
Name, Zugriffsrechte und Inhalt aller Dateien, die zum Projekt gehören
Zeitpunkt, zu dem der Commit erstellt wurde
Name desjenigen, der den Commit erstellt hat
eine Beschreibung des Commits (muss der Ersteller des Commits möglichst sinnvoll eingeben)
Jeder Commit erhält von Git automatisch einen Namen, der so gewählt ist, dass jeder Commit eindeutig identifiziert werden kann. (Nicht nur innerhalb eines Repositories, sondern praktisch über alle Repositories auf der Welt!)
Der Name eines Commits ist ein 40-stelliger Code aus Hexadezimalziffern und heißt auch Hash. z.B.: 64efe35c4535b472d2fe38a6f696adc56dc38ff4
Außerdem enthält jeder Commit:
Einen Verweis auf einen (oder mehrere) Vorgänger-Commit (“parent”)
(Nur der allererste Commit in einem Repository hat keinen Vorgänger)
Durch die Verweise auf die jeweiligen Vorgänger werden Commits untereinander verknüpft und repräsentieren gemeinsam den Entwicklungsfortschritt des Projekts.
Beispiele:
(A ← B
heißt: A
ist Vorgänger von B
)
(links ist “alt”, rechts ist “neu”)
Einfachster Fall, lineare Entwicklungsgeschichte:
A ← B ← C ← D
Verzweigung der Entwicklung:
A ← B ← C ← D
↑
└ E ← F ← G
B
ist Vorgänger von C
und auch von E
.
Entwicklung hat unterschiedlichen Verlauf genommen und wird wieder zusammengeführt:
A ← B ← C ←---┐
↑ |
└ D ← E ← F
F
hat zwei Vorgänger, nämlich C
und E
.F
ist ein “merge commit”.Dies ist nicht möglich:
A ← B ← C
↓ ↑
D --┘
Ein Commit kann nicht sein eigener Vorfahre sein.
Im Allgemeinen gilt: Die Commits bilden einen gerichteten, azyklischen Graphen. (DAG).
Von einem bestimmten Commit aus betrachtet sind nur seine Vorfahren “sichtbar”/“erreichbar”:
A ← B ← C ← D
↑
└ E ← F ← G
z.B. von E
aus:
A ← B ← E
C
, D
, F
, G
sind von E
aus nicht sichtbar/erreichbar.
Deshalb muss im Repository irgendwie gespeichert sein, welches der jeweils “neueste” Commit ist. (Als “Einstiegspunkt” in den Graphen)
Dazu gibt es Referenzen. Eine Referenz ist ein Verweis auf einen Commit.
Die gebräuchlichste Art von Referenz heißt Branch.
Analogie:
Beispiel:
A ← B [base]
↑
├ C ← D ← E [feature1]
|
└ F ← G [feature2] [feature3]
base
, feature1
, feature2
, feature3
base
verweist auf Commit B
feature1
verweist auf Commit E
feature2
als auch feature3
verweisen auf Commit G
Merke:
G
)base
/B
. Allerdings ist von einem Branch aus gesehen natürlich nur dessen “Vergangenheit” sichtbar (A ← B
).Mögliche Operationen mit Branches:
Umbenennen
z.B. von
... C ← D ← E [feature1]
zu
... C ← D ← E [my-feature]
Auf einen anderen Commit versetzen
z.B. von
A ← B [base]
↑
├ C ← D ← E [feature1]
|
└ F ← G [feature2] [feature3]
zu
A ← B [base] [feature3]
↑
├ C ← D ← E [feature1]
|
└ F ← G [feature2]
(feature3
von G
auf B
versetzt; feature2
bleibt unberührt)
Löschen
z.B. von
... F ← G [feature2]
zu
... F ← G
Wichtig:
Die genannten Operationen mit Branches ändern nur eine Referenz (“Lesezeichen”) auf einen Commit.
Andere Branches und die Commits selbst bleiben unberührt.
Es kann allerdings sein, dass durch Versetzen oder Löschen eines Branchs ein Commit nicht mehr erreichbar ist (z.B. G
oben)
Solange der Hash eines unerreichbaren Commits noch bekannt ist, kann problemlos wieder ein neuer Branch angelegt werden, der auf den Commit verweist.
Erst nach einiger Zeit (z.B. 2 Wochen) wird Git unerreichbare Commits tatsächlich löschen, z.B. von
A ← B [base] [feature3]
↑
├ C ← D ← E [feature1]
|
└ F ← G
zu
A ← B [base] [feature3]
↑
└ C ← D ← E [feature1]
(F
und G
wurden von keinem Branch aus mehr erreicht und wurden schließlich gelöscht)
Das Löschen von unerreichbaren Commits geschieht im Rahmen von “Aufräumarbeiten” (“garbage collection”), die Git regelmäßig im Repository durchführt, z.B. um Speicherplatz zu sparen.
Es gibt eine weitere, spezielle, Referenz namens HEAD
.
HEAD
verweist im Normalfall auf einen Branch-Namen (im Gegensatz zu einem Branch, der direkt auf einen Commit-Hash verweist).
Der Branch, auf den HEAD
verweist, gilt somit als der “gerade aktive” Branch (“current”/“checked out”).
HEAD
↓
A ← B [base] [feature3]
↑
└ C ← D ← E [feature1]
feature3
ist der aktive Branch.base
und feature1
sind inaktive Branches.HEAD
verweist indirekt auch auf Commit B
.Ausnahmsweise, oder vorübergehend, kann HEAD
auch direkt auf einen Commit-Hash anstatt einen Branch-Namen verweisen:
A ← B [base] [feature3]
↑
└ C ← D ← E [feature1]
↑
HEAD
Normalerweise sollte immer einer der Branches “aktiv” sein. Aktionen, die man im Repository durchführt, wirken sich dann meistens auf diesen Branch aus.
“Aktion” heißt hauptsächlich: neue Commits erzeugen und somit den Fortschritt des Projekts festhalten. Der aktive Branch “wächst”.
Git unterscheidet zu einem gegebenen Zeitpunkt drei konzeptuell verschiedene Zustände der zu einem Projekt gehörenden Dateien:
Den Zustand, der in dem Commit gespeichert ist, auf den HEAD
gerade verweist (der “neueste Speicherpunkt”).
Den Zustand, in dem sich die Dateien im Arbeitsverzeichnis (“working directory”) gerade tatsächlich befinden (für den Benutzer ganz normal sichtbar).
Den sogenannten Index.
Der Index repräsentiert den Zustand des Projekts, der in einem neuen Commit gespeichert werden soll. (“staging area”)
Sind die drei Zustände HEAD
, Arbeitsverzeichnis und Index identisch, ist das Repository “clean”. Dies ist normalerweise der Ausgangszustand, bevor man an dem Projekt weiterarbeitet, bzw. nachdem man einen Fortschritt am Projekt abgeschlossen hat.
Um einen neuen Commit zu erzeugen, müssen Inhalte zum Index hinzugefügt werden.
Das geschieht im einfachsten Fall mit folgendem, sich pro Commit wiederholendem, Ablauf:
Repository “clean”
Der Commit-Graph könnte so aussehen: HEAD
verweist auf den “aktiven” Branch feature1
, dieser wiederum auf den Commit, der den “aktuellen” Zustand C
des Projekts speichert.
HEAD
↓
A ← B ← C [feature1]
Arbeitsverzeichnis, Index und HEAD
sind alle im gleichen Zustand C
:
Arbeitsverzeichnis Index HEAD
------------------ ----- ----
C C C
Der Inhalt des Arbeitsverzeichnisses wird vom Benutzer verändert (die eigentliche Arbeit an dem Projekt, d.h. neue Dateien oder Inhalte werden hinzugefügt, bestehendes wird verbessert, modifiziert, etc.).
Nun sind die Zustände von Index und HEAD
immer noch identisch, aber das Arbeitsverzeichnis ist davon verschieden. (“unstaged changes”)
(Hier repräsentiert durch Zustand C
→ Zustand D
).
Arbeitsverzeichnis Index HEAD
------------------ ----- ----
D C C
Der geänderte Inhalt des Arbeitsverzeichnis wird in den Index übertragen.
Arbeitsverzeichnis Index HEAD
------------------ ----- ----
D --> D C
Jetzt sind Arbeitsverzeichnis und Index identisch (“staged changes”, “changes to be committed”), aber HEAD
ist noch auf dem “alten” Stand.
Ein neuer Commit wird erstellt, der den aktuellen Inhalt des Index speichert. Danach ist auch HEAD
im Zustand D
.
Arbeitsverzeichnis Index HEAD
------------------ ----- ----
D D --> D
Das Repository ist jetzt wieder “clean”.
Beim letzten Schritt (dem “committen”) führt Git intern mehrere Teilschritte aus, die sich anhand des Commit-Graphen darstellen lassen:
Ausgangssituation:
HEAD
verweist auf den “aktiven” Branch feature1
, dieser wiederum auf den Commit, der den “aktuellen” Zustand C
des Projekts speichert.
HEAD
↓
A ← B ← C [feature1]
Aus dem Inhalt des Index (Zustand D
) wird ein neuer Commit erzeugt, als dessen Vorgänger der momentan aktuelle Commit eingetragen wird.
HEAD
↓
A ← B ← C [feature1]
↑
└ D
Der Branch feature1
und somit indirekt auch HEAD
verweist zunächst noch auf Commit C
.
Der Branch feature1
wird aktualisiert, so dass er nicht mehr auf Commit C
, sondern auf Commit D
verweist.
HEAD
↓
A ← B ← C ← D [feature1]
HEAD
wird selbst garnicht direkt modifiziert, sondern verweist die ganze Zeit auf den Branch-Namen feature1
! Nur indirekt ändert sich der Branch, auf den HEAD
verweist.
Auf den ersten Blick erscheint der Index unnötig. Man könnte ja auch direkt den Zustand des Arbeitsverzeichnisses in einem Commit speichern.
Dank des Index ist es aber möglich, nur einen Teil der Änderungen aus dem Arbeitsverzeichnis in einen neuen Commit zu übernehmen, bzw. schließlich alle Änderungen auf mehrere Commits zu verteilen.
Das kann bedeuten, nur Änderungen aus bestimmten Dateien zu übernehmen, oder sogar nur bestimmte Änderungen, die man an einer einzelnen Datei gemacht hat.
Änderungen auf mehrere Commits aufzuteilen kann sinnvoll sein, wenn man noch einmal den Commit-Graphen als Repräsentation des Entwicklungsfortschritts eines Projekts betrachtet:
Man kann Änderungen, die konzeptuell voneinander unabhängig sind, in verschiedenen Commits speichern. Dadurch fällt es leichter, zu jedem Commit eine passende Beschreibung zu finden, und später lässt sich somit einfacher nachvollziehen, was man getan hat.
Vor allem im Zusammenhang mit Softwareprojekten ist es sinnvoll, wenn die in einem Commit enthaltenen Änderungen nur relativ kleine, in sich abgeschlossene Einheiten bilden. Sollte man später in der Software einen Fehler finden, lässt sich dann einfacher feststellen, durch welche Änderung genau der Fehler verursacht worden ist.
Beispiel (alternative zu oben):
Arbeitsverzeichnis Index HEAD
------------------ ----- ----
C C C
D C C
D --> C1 C
D C1 --> C1
D --> C2 C1
D C2 --> C2
D --> D C2
D D --> D
Resultierender Commit-Graph:
A ← B ← C ← C1 ← C2 ← D
Die Änderung von Zustand C
auf Zustand D
ist hier auf die Commits C1
, C2
und D
aufgeteilt.
HEAD
markiert den aktiven BranchGit benutzt man durch Eingabe von Befehlen im Terminal.
Für die meisten Befehle (die sich auf ein bestimmtes Repository beziehen), muss das Arbeitsverzeichnis innerhalb des Repositories sein.
Die Befehle haben normalerweise die Form
$ git <Unterbefehl> <Argumente>
Die Dokumentation der Unterbefehle erhält man mit
$ man git <Unterbefehl>
oder auf https://git-scm.com/docs.
Bevor man Git das erste Mal auf einem Rechner benutzt, muss man u.U. noch ein paar Einstellungen machen:
Eigenen Namen und Mailadresse angeben (kann auch fiktiv sein, wird von Git in Commits eingetragen, die der Benutzer erstellt)
$ git config --global user.name <Vorname> <Nachname>
$ git config --global user.email <E-Mail-Adresse>
Farbige Ausgabe auf dem Terminal aktivieren (erhöht die Übersicht)
$ git config --global color.ui auto
Damit sieht die Ausgabe von Git übersichtlicher aus.
Texteditor festlegen
$ git config --global core.editor <Name des Editors>
Manchmal öffnet Git einen Texteditor um eine Eingabe vom Benutzer zu verlangen, der hier angegebene wird verwendet.
Beispiele: gedit
, nano
Mit dem Befehl
$ git init
wird im Arbeitsverzeichnis ein neues Git-Repository angelegt.
Das kann man daran erkennen, dass ein Unterverzeichnis namens .git
erzeugt wurde. Es enthält alle Daten, die zu dem Repository gehören.
(Falls man git init
in einem schon bestehenden Repository ausführt, wird dadurch kein Schaden angerichtet.)
Mit dem Befehl
$ git clone <Adresse>
wird im Arbeitsverzeichnis ein neues Unterverzeichnis angelegt und darin ein Klon eines vorhandenen Repositories erstellt.
Das vorhandene Repository wird an der angegebenen Adresse gesucht. Bei der Adresse kann es sich um einen Verzeichnispfad im eigenen Dateisystem oder um eine URL im Internet handeln, wie z.B.
https://sus.ziti.uni-heidelberg.de/Lehre/WS1718_Tools/GIT/uebung.git
$ git status
zeigt Informationen über den aktuellen Zustand des Repositories an, z.B.:
$ git log
zeigt vom aktuellen Commit ausgehend (worauf HEAD
verweist) den Verlauf aller Vorgänger-Commits.
$ git show <Commit>
zeigt die Details zu einem Commit, und die Unterschiede der Dateien des Projekts im Vergleich zum Vorgänger-Commit.
Die Angabe des Commits kann ein Commit-Hash, oder ein Branch-Name (oder eine sonstige Referenz) sein.
Lässt man die Angabe des Commits weg (d.h. nur git show
), zeigt Git die Details des aktuellen Commits (HEAD
) an.
Anmerkung: Prinzipiell kann Git mit allen Arten von Dateien umgehen, aber alles was mit der Darstellung von Unterschieden zu tun hat, funktioniert nur mit reinen Textdateien (Quelltext in Programmiersprachen, LaTeX-Dokumente, etc.)
Die Darstellung der Unterschiede zwischen zwei Versionen einer Datei geschieht zeilenweise:
Zeilen, die vorher da waren und jetzt nicht mehr, wird ein -
vorangestellt.
Zeilen, die vorher nicht da waren, und jetzt neu sind, wird ein +
vorangestellt.
Wenn die Ausgabe länger als eine Bildschirmseite ist, verwendet Git automatisch less
, d.h. man kann wie gewohnt darin navigieren und kommt mit q
wieder heraus.
$ git diff <Commit 1> <Commit 2>
zeigt die Unterschiede der Dateien des Projekts zwischen den beiden angegebenen Commits.
Nützliche Optionen für alle Varianten von git diff
:
-w
: ignoriert geänderte Leerzeichen
--color-words
: zeigt Änderungen wortweise anstatt zeilenweise
Für die Betrachtung des Entwicklungsverlaufs des Projekts, der Details einzelner Commits und der Unterschiede zwischen Commits gibt es auch eine grafische Benutzerobefläche. Sie wird geöffnet mit
$ gitk
(falls auf dem System installiert)
Mit $ gitk –all
bekommt man die Commits aller Branches angezeigt. Ohne --all
nur die Commits des aktiven Branchs.
Inhalte aus dem Arbeitsverzeichnis werden mit dem Befehl
$ git add <Dateiname>
in den Index übertragen.
Mit dem Befehl
$ git reset <Dateiname>
werden Inhalte aus dem HEAD
-Zustand in den Index übertragen (macht git add <Dateiname>
wieder rückgängig).
Beispiel:
Arbeitsverzeichnis Index HEAD
------------------ ----- ----
D C C
D add --> D C
D C <-- reset C
Mit dem Befehl
$ git checkout -- <Dateiname>
wird der Zustand der Datei aus dem Index in das Arbeitsverzeichnis übertragen, d.h. die noch nicht im Repository gespeicherten Änderungen an der Datei werden rückgängig gemacht:
Arbeitsverzeichnis Index HEAD
------------------ ----- ----
D C C
C <-- checkout C C
Git kann die Unterschiede zwischen Arbeitsverzeichnis, Index und Commits anzeigen:
$ git diff
zeigt den Unterschied zwischen Arbeitskopie und Index (“unstaged changes”, könnte noch mit git add
zum Index hinzugefügt werden).
$ git diff <Commit>
zeigt den Unterschied zwischen Arbeitskopie und einem Commit (durch den Commit-Hash oder einen Branch-Namen oder eine andere Referenz angegeben).
$ git diff --cached [<Commit>]
zeigt den Unterschied zwischen Index und HEAD (“staged changes”, wäre der Unterschied zwischen dem aktuellen und einem neuen Commit), bzw. zwischen Index und einem anderen Commit als HEAD, falls angegeben.
Arbeitsverzeichnis Index HEAD
------------------ ----- ----
| | |
|<---- diff ---->|<- diff --cached ->|
| |
|<---------- diff HEAD ------------->|
Hat man mit git add
Inhalte zum Index hinzugefügt (git diff --cached
zeigt dann an, was der Unterschied zwischen dem aktuellen und einem neuen Commit wäre), wird mit
$ git commit
der Inhalt des Index zu einem neuen Commit gemacht. Git öffnet einen Texteditor, in dem man aufgefordert wird, die Commit-Message als Beschreibung der gemachten Änderungen einzugeben.
Um die eingegebene Beschreibung zu bestätigen, muss man die im Texteditor geöffnete Datei (namens COMMIT_EDITMSG
) speichern und den Editor danach beenden. Erst dann schließt Git das Anlegen des Commits ab.
Mit der Option -m
kann man die Message (in Anführungszeichen) auch direkt an den Befehl übergeben.
Ein Repository bekommen:
git init
– neues, leeres Repository erstellengit clone
– bestehendes Repository kopierenEin Repository betrachten:
git status
git log
git show
git diff <Commit 1> <Commit 2>
Oder gitk
als grafische Oberfläche benutzen.
Index manipulieren und neue Commits erstellen:
git add <Datei>
git reset <Datei>
git checkout -- <Datei>
git diff [--cached]
git commit
Die Befehle git add
, git reset
und git checkout
haben die Option -p
.
Damit kann man die jeweilige Aktion (Übertragung von Inhalt zwischen Index, Arbeitsverzeichnis und HEAD
) nur für Teile der Änderungen in einer Datei interaktiv durchführen.
Die bestehenden Unterschiede werden in sogenannte “Hunks” aufgeteilt, und man bekommt für jeden Hunk die Möglichkeit, die o.g. Aktion durchzuführen (y
) oder nicht (n
). Durch die Antwort q
bricht man die interaktive Abfrage ab.
Die Angabe eines Dateinamens (wie bei git add <Datei>
ohne -p
) ist hier möglich, um die Änderungen auf diese Datei zu beschränken, aber nicht nötig:
$ git add -p <Datei>
fragt nach den Änderungen in der Datei.
$ git add -p
fragt nach den Änderungen in allen Dateien.
Man kann die Hunks manchmal noch weiter aufteilen lassen, indem man die Antwort s
gibt.
In einem neu angelegten Repository gibt es standardmäßig einen Branch namens “master”.
Einen neuen Branch kann man mit dem Befehl
$ git branch <Name> <Commit>
erzeugen. Der neue Branch mit dem angegebenen Namen verweist dann auf den Commit, der durch seinen Hash, oder eine Referenz (z.B. existierender Branch-Name) angegeben wird.
(Oder in gitk im Kontextmenü durch Rechtsklick auf einen Commit.)
Eine Liste aller Branches bekommt man mit
$ git branch
Einen Branch kann man mit
$ git branch -m <alter Name> <neuer Name>
umbenennen. Worauf der Branch verweist, bleibt unverändert.
Einen Branch kann man mit
$ git branch -d <Branch>
löschen. Es wird nur die Referenz (“Lesezeichen”) auf einen Commit gelöscht, alle Commits selbst bleiben erhalten.
Falls durch das Löschen des Branchs Commits unerreichbar werden würden, verweigert Git das Löschen. Man kann es aber mit -D
anstatt -d
erzwingen.
Um einen Branch von einem Commit auf einen anderen Commit zu versetzen dient ebenfalls der Befehl git reset
, in drei “Abstufungen”:
Soft
$ git reset --soft <Commit>
Versetzt den aktiven Branch (auf den HEAD
gerade zeigt) auf den angegebenen Commit.
Index und Arbeitsverzeichnis bleiben unverändert.
Mixed (ist Standard)
$ git reset --mixed <Commit>
$ git reset <Commit>
Versetzt den aktiven Branch (auf den HEAD
gerade zeigt) auf den angegebenen Commit.
Überträgt den Zustand des angegebenen Commits in den Index.
Das Arbeitsverzeichnis bleibt unverändert.
Hard
$ git reset --hard <Commit>
Versetzt den aktiven Branch (auf den HEAD
gerade zeigt) auf den angegebenen Commit.
Überträgt den Zustand des angegebenen Commits in den Index und ins Arbeitsverzeichnis.
Mit dem Befehl
$ git checkout <Branch>
wird HEAD auf den angegeben Branch gesetzt, sowie der Zustand des Projekts, der mit dem Commit gespeichert ist, auf den der Branch gerade verweist, sowohl in den Index als auch das Arbeitsverzeichnis übertragen.
(Das Repository sollte zuvor “clean” sein)
Somit kann man auch leicht eine beliebige Version des Projekts wiederherstellen:
$ git branch <Name> <gewünschte Version>
$ git checkout <Name>
Diese beiden Befehle kann man auch abkürzen mit:
$ git checkout -b <Name> <gewünschte Version>
Ein Commit kann mehr als einen Vorgänger haben.
Wie kommt man zu so einem Commit? Durch git commit
wird immer nur ein Commit erstellt, der einen Vorgänger hat.
Einen Commit mit mehr als einem Vorgänger (meistens zwei) bekommt man unter Umständen mit dem Befehl
$ git merge <anderer Branch>
in dem kein neuer Commit erzeugt wird:
Wenn der aktuelle Branch (HEAD
) ein Vorfahre des anderen Branchs ist, kann der aktuelle Branch einfach auf den neuesten Commit im anderen Branch vorgerückt werden (“fast-forward”):
Situation vorher, “master” ist der aktive Branch:
Wird durch
$ git merge hotfix
zu
der eintritt, wenn der aktuelle Branch kein Vorfahre des anderen Branchs ist, wie z.B. (“master” ist aktiver Branch):
Dann wird mit
$ git merge iss53
ein neuer Commit erstellt, der auf
als Vorgänger verweist.
Git versucht, die Änderungen, die in den Commits des anderen Branch seit dem gemeinsamen Vorfahren (im Beispiel: C3, C5) gemacht wurden, in den eigenen Branch zu übernehmen.
Wenn die Änderungen in beiden Branches sich nicht überlappen, klappt das normalerweise sehr gut.
Wenn in beiden Branches Änderungen an der selben Stelle in einer Datei gemacht worden sind, kann Git nicht wissen, welche Version die “richtige” sein soll.
Das nennt man einen Merge-Konflikt. Man muss als Benutzer selbst den Konflikt auflösen. Sieht man sich dazu nicht in der Lage, kann man mit
$ git merge --abort
den Merge-Vorgang abbrechen. Der Zustand des Repositories ist dann wieder wie vor dem versuchten Merge.
Die Auflösung eines Konflikts läuft so ab:
In git status
ist zu sehen, welche Dateien von einem Konflikt betroffen sind.
Die betroffenen Dateien wurden von Git modifiziert, in dem die Stelle, an der die Datei in beiden Branches seit dem gemeinsamen Vorfahren verändert wurde, markiert ist:
<<<<<<<
Inhalt der Datei im aktuellen Branch
=======
Inhalt der Datei im anderen Branch
>>>>>>>
Man muss die markierte Stelle durch den Inhalt ersetzen, den man als “gemeinsame Version” erachtet.
Mit git add
fügt man den neuen Inhalt zum Index hinzu und teilt Git dadurch mit, dass der Konflikt aufgelöst ist.
Jetzt sollte man sich mit git status
oder git diff --cached
vergewissern, dass die Änderungen aus beiden Branches wie gewünscht im Index sind.
Mit git commit
schließt man den Merge-Vorgang ab.
Hier ist es hilfreich, sich mit gitk
das Ergebnis des Merges zu veranschaulichen.
Die Übungsaufgaben sind hier.
Generell: Versuche, Git in Zukunft sinnvoll einzusetzen. Lege für jedes scheinbar zu kleine “Projekt” ein Repository an und mache einen neuen Commit, wenn sich etwas geändert hat.