-
1. Erste Schritte
-
2. Git Grundlagen
-
3. Git Branching
- 3.1 Branches auf einen Blick
- 3.2 Einfaches Branching und Merging
- 3.3 Branch-Management
- 3.4 Branching-Workflows
- 3.5 Remote-Branches
- 3.6 Rebasing
- 3.7 Zusammenfassung
-
4. Git auf dem Server
- 4.1 Die Protokolle
- 4.2 Git auf einem Server einrichten
- 4.3 Erstellung eines SSH-Public-Keys
- 4.4 Einrichten des Servers
- 4.5 Git-Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Von Drittanbietern gehostete Optionen
- 4.10 Zusammenfassung
-
5. Verteiltes Git
-
6. GitHub
-
7. Git Tools
- 7.1 Revisions-Auswahl
- 7.2 Interaktives Stagen
- 7.3 Stashen und Bereinigen
- 7.4 Ihre Arbeit signieren
- 7.5 Suchen
- 7.6 Den Verlauf umschreiben
- 7.7 Reset entzaubert
- 7.8 Fortgeschrittenes Merging
- 7.9 Rerere
- 7.10 Debuggen mit Git
- 7.11 Submodule
- 7.12 Bundling
- 7.13 Replace (Ersetzen)
- 7.14 Anmeldeinformationen speichern
- 7.15 Zusammenfassung
-
8. Git einrichten
- 8.1 Git Konfiguration
- 8.2 Git-Attribute
- 8.3 Git Hooks
- 8.4 Beispiel für Git-forcierte Regeln
- 8.5 Zusammenfassung
-
9. Git und andere Systeme
- 9.1 Git als Client
- 9.2 Migration zu Git
- 9.3 Zusammenfassung
-
10. Git Interna
-
A1. Anhang A: Git in anderen Umgebungen
- A1.1 Grafische Schnittstellen
- A1.2 Git in Visual Studio
- A1.3 Git in Visual Studio Code
- A1.4 Git in IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.5 Git in Sublime Text
- A1.6 Git in Bash
- A1.7 Git in Zsh
- A1.8 Git in PowerShell
- A1.9 Zusammenfassung
-
A2. Anhang B: Git in Ihre Anwendungen einbetten
- A2.1 Die Git-Kommandozeile
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Anhang C: Git Kommandos
- A3.1 Setup und Konfiguration
- A3.2 Projekte importieren und erstellen
- A3.3 Einfache Snapshot-Funktionen
- A3.4 Branching und Merging
- A3.5 Projekte gemeinsam nutzen und aktualisieren
- A3.6 Kontrollieren und Vergleichen
- A3.7 Debugging
- A3.8 Patchen bzw. Fehlerkorrektur
- A3.9 E-mails
- A3.10 Externe Systeme
- A3.11 Administration
- A3.12 Basisbefehle
7.13 Git Tools - Replace (Ersetzen)
Replace (Ersetzen)
Wie wir bereits betont haben, sind die Objekte in der Objektdatenbank von Git unveränderbar, aber Git bietet eine interessante Möglichkeit, so zu tun, als ob man Objekte in der Datenbank durch andere Objekte ersetzen würde.
Der Befehl replace
ermöglicht es Ihnen, ein Objekt in Git zu bestimmen und zu sagen „jedes Mal, wenn ich auf dieses Objekt verweise, behandle es so, als wäre es ein anderes Objekt“.
Das wird am häufigsten gebraucht zum Ersetzen eines Commits in Ihrem Verlauf durch einen anderen, ohne dass Sie die gesamte Historie neu aufbauen müssen, wie z.B. mit git filter-branch
.
Nehmen wir zum Beispiel an, Sie haben einen riesigen Code-Verlauf und möchten Ihr Repository aufsplitten in einen kurzen Verlauf für neue Entwickler und einen viel längeren und ausführlicheren Verlauf für Leute, die sich für Data Mining interessieren. Sie können eine Historie auf eine andere aufpfropfen, indem Sie den frühesten Commit in der neuen Zeile durch den neuesten Commit in der älteren Zeile „ersetzen“. Das ist angenehm, weil es bedeutet, dass Sie nicht wirklich jeden Commit in der neuen Geschichte neu erstellen müssen, wie Sie es normalerweise tun müssten, um sie zusammenzufügen (weil die Elterngeneration die SHA-1-Werte beeinflussen).
Probieren wir das einmal aus.
Nehmen wir ein vorhandenes Repository, teilen es in zwei Repositorys auf, ein aktuelles und ein altes. Dann untersuchen wir, wie wir sie rekombinieren können, ohne die aktuellen SHA-1-Werte des Repositorys durch replace
zu verändern.
Wir werden ein kleines Repository mit fünf einfachen Commits verwenden:
$ git log --oneline
ef989d8 Fifth commit
c6e1e95 Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
Wir wollen dieses in zwei unterschiedliche Historien aufteilen. Eine Linie geht von „commit one“ bis „commit four“ – das wird die historische Linie sein. Die zweite Linie wird nur aus den Commits vier und fünf bestehen – das wird die jüngere Historie sein.
Nun, die Erstellung des historischen Verlaufs ist einfach, wir können einen Branch in den Verlauf einfügen und dann diesen Branch auf den master
Branch eines neuen Remote-Repositorys speichern ('push').
$ git branch history c6e1e95
$ git log --oneline --decorate
ef989d8 (HEAD, master) Fifth commit
c6e1e95 (history) Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
Jetzt können wir den neuen Branch history
in den master
Branch unseres neuen Repositorys speichern ('push'):
$ git remote add project-history https://github.com/schacon/project-history
$ git push project-history history:master
Counting objects: 12, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (12/12), 907 bytes, done.
Total 12 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (12/12), done.
To git@github.com:schacon/project-history.git
* [new branch] history -> master
OK, damit ist unsere Historie veröffentlicht. Nun ist der schwierigere Teil, unsere jüngste Historie nach hinten zu kürzen, damit sie kleiner wird. Wir brauchen eine Überlappung, damit wir einen Commit in der einen durch einen gleichwertigen Commit in der anderen ersetzen können. Deshalb werden wir diesen Teil auf die Commits vier und fünf kürzen (so dass sich Commit vier überlappt).
$ git log --oneline --decorate
ef989d8 (HEAD, master) Fifth commit
c6e1e95 (history) Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
In diesem Fall ist es nützlich, einen Basis-Commit zu erstellen, der Anweisungen zum Erweitern der Historie enthält, damit andere Entwickler wissen, was zu tun ist, wenn sie auf den ersten Commit in der getrennten Historie treffen und mehr brauchen. Was wir also vornehmen werden, ist, ein erstes Commit-Objekt als unseren Basispunkt mit Anweisungen zu erstellen und dann die restlichen Commits (vier und fünf) darauf zu rebasieren.
Dazu müssen wir einen Punkt wählen, an dem wir abspalten möchten, der in unserem Beispiel der dritte Commit ist. Er lautet 9c68fdc
in der SHA-Sprechweise.
Unser Basis-Commit wird also auf diesem Baum basieren.
Wir können unseren Basis-Commit mit dem Befehl commit-tree
erstellen, der einfach einen Baum nimmt und uns ein brandneues, elternloses SHA-1-Commit-Objekt zurückgibt.
$ echo 'Get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf
Anmerkung
|
Das Kommando |
OK, jetzt, wo wir einen Basis-Commit haben, können wir den Rest unseres Verlaufs mit git rebase --onto
auf die Basis zurückführen.
Das Argument --onto
wird der SHA-1 sein, den wir gerade von commit-tree
zurückbekommen haben, und der Rebase-Punkt wird der dritte Commit sein (das Elternteil des ersten Commits, den wir behalten wollen, 9c68fdc
):
$ git rebase --onto 622e88 9c68fdc
First, rewinding head to replay your work on top of it...
Applying: fourth commit
Applying: fifth commit
Gut, nun haben wir also unseren jüngsten Verlauf auf einer Übergabebasis neu geschrieben, die jetzt Anweisungen enthält, wie wir die gesamte Historie rekonstruieren könnten, wenn wir es wollen. Wir können diesen neuen Verlauf auf ein neues Projekt übertragen, und wenn die Anwender jetzt dieses Repository klonen, sehen sie nur die beiden letzten Commits und dann einen Basis-Commit mit Anweisungen.
Lassen Sie uns nun die Rolle tauschen mit jemandem, der das Projekt zum ersten Mal klont und den gesamten Verlauf des Projekts haben will. Um die Verlaufsdaten nach dem Klonen dieses abgetrennten Repositorys zu erhalten, müsste man einen zweiten Remote für das historische Repository hinzufügen und holen ('fetch'):
$ git clone https://github.com/schacon/project
$ cd project
$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
622e88e Get history from blah blah blah
$ git remote add project-history https://github.com/schacon/project-history
$ git fetch project-history
From https://github.com/schacon/project-history
* [new branch] master -> project-history/master
Nun würde die andere Person ihre jüngsten Commits im master
Branch und die historischen Commits im project-history/master
Branch erhalten.
$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
622e88e Get history from blah blah blah
$ git log --oneline project-history/master
c6e1e95 Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
Um sie zu vereinigen, können Sie einfach git replace
mit dem Commit, den Sie ersetzen wollen, und dann den Commit, mit dem Sie ihn ersetzen wollen, aufrufen.
Wir wollen also den „vierten“ Commit im master
Branch durch den "vierten" Commit im project-history/master
Branch ersetzen:
$ git replace 81a708d c6e1e95
Wenn man sich nun den Verlauf des master
Branchs anschaut, sieht er so aus:
$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
Klasse, oder? Ohne alle SHA-1s im Upstream ändern zu müssen, konnten wir einen Commit in unserer Geschichte durch einen ganz anderen ersetzen, und alle normalen Werkzeuge (bisect
, blame
, usw.) werden so funktionieren, wie wir es erwarten dürfen.
Interessanterweise zeigt das Protokoll ('log') immer noch 81a708d
als SHA-1, obwohl es tatsächlich die c6e1e95
-Commit-Daten verwendet, durch die wir es ersetzt haben.
Selbst wenn Sie einen Befehl wie cat-file
ausführen, zeigt er Ihnen die ersetzten Daten an:
$ git cat-file -p 81a708d
tree 7bc544cf438903b65ca9104a1e30345eee6c083d
parent 9c68fdceee073230f19ebb8b5e7fc71b479c0252
author Scott Chacon <schacon@gmail.com> 1268712581 -0700
committer Scott Chacon <schacon@gmail.com> 1268712581 -0700
fourth commit
Vergessen Sie nicht, dass das eigentliche Elternteil von 81a708d
unser Platzhalter-Commit (622e88e
) war, nicht 9c68fdce
, wie es hier steht.
Eine weitere wichtige Tatsache ist, dass diese Daten in unseren Referenzen gespeichert sind:
$ git for-each-ref
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/heads/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/remotes/history/master
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/HEAD
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/replace/81a708dd0e167a3f691541c7a6463343bc457040
Das bedeutet, dass es einfach ist, unseren Ersatz mit anderen zu teilen, weil wir diesen auf unseren Server speichern ('push') können und andere Anwender ihn leicht herunterladen können. Das ist in dem Szenario zur Verlaufsoptimierung, das wir hier durchgespielt haben, nicht so hilfreich (da jeder sowieso beide Historien herunterladen würde. Warum also beide trennen?). Es kann aber unter anderen Umständen sinnvoll sein.