Einrichtung und Konfiguration
Projekte holen und erstellen
Grundlegende Snapshots
Branching und Merging
Projekte teilen und aktualisieren
Inspektion und Vergleich
Patching
Debugging
Externe Systeme
Server-Administration
Anleitungen
- gitattributes
- Konventionen der Kommandozeile
- Tägliches Git
- Häufig gestellte Fragen (FAQ)
- Glossar
- Hooks
- gitignore
- gitmodules
- Revisionen
- Submodule
- Tutorial
- Workflows
- Alle Anleitungen...
Administration
Plumbing-Befehle
- 2.51.1 → 2.52.0 keine Änderungen
-
2.51.0
2025-08-18
- 2.48.1 → 2.50.1 keine Änderungen
-
2.48.0
2025-01-10
- 2.46.1 → 2.47.3 keine Änderungen
-
2.46.0
2024-07-29
- 2.45.1 → 2.45.4 keine Änderungen
-
2.45.0
2024-04-29
- 2.44.1 → 2.44.4 keine Änderungen
-
2.44.0
2024-02-23
- 2.43.2 → 2.43.7 keine Änderungen
-
2.43.1
2024-02-09
-
2.43.0
2023-11-20
- 2.42.1 → 2.42.4 keine Änderungen
-
2.42.0
2023-08-21
- 2.39.1 → 2.41.3 keine Änderungen
-
2.39.0
2022-12-12
- 2.38.1 → 2.38.5 keine Änderungen
-
2.38.0
2022-10-02
- 2.36.1 → 2.37.7 keine Änderungen
-
2.36.0
2022-04-18
- 2.34.1 → 2.35.8 keine Änderungen
-
2.34.0
2021-11-15
- 2.33.1 → 2.33.8 keine Änderungen
-
2.33.0
2021-08-16
- 2.32.1 → 2.32.7 keine Änderungen
-
2.32.0
2021-06-06
- 2.30.1 → 2.31.8 keine Änderungen
-
2.30.0
2020-12-27
- 2.28.1 → 2.29.3 keine Änderungen
-
2.28.0
2020-07-27
- 2.25.1 → 2.27.1 keine Änderungen
-
2.25.0
2020-01-13
- 2.24.1 → 2.24.4 keine Änderungen
-
2.24.0
2019-11-04
- 2.23.1 → 2.23.4 keine Änderungen
-
2.23.0
2019-08-16
- 2.22.1 → 2.22.5 keine Änderungen
-
2.22.0
2019-06-07
- 2.21.1 → 2.21.4 keine Änderungen
-
2.21.0
2019-02-24
- 2.19.1 → 2.20.5 keine Änderungen
-
2.19.0
2018-09-10
- 2.18.1 → 2.18.5 keine Änderungen
-
2.18.0
2018-06-21
- 2.17.0 → 2.17.6 keine Änderungen
-
2.16.6
2019-12-06
-
2.15.4
2019-12-06
-
2.14.6
2019-12-06
-
2.13.7
2018-05-22
-
2.12.5
2017-09-22
- 2.9.5 → 2.11.4 keine Änderungen
-
2.8.6
2017-07-30
-
2.7.6
2017-07-30
-
2.6.7
2017-05-05
- 2.5.6 keine Änderungen
-
2.4.12
2017-05-05
- 2.3.10 keine Änderungen
-
2.2.3
2015-09-04
-
2.1.4
2014-12-17
-
2.0.5
2014-12-17
Einführung
Git ist ein schnelles, verteiltes Revisionskontrollsystem.
Dieses Handbuch ist so konzipiert, dass es von jemandem mit grundlegenden UNIX-Befehlszeilenkenntnissen, aber ohne vorherige Kenntnisse von Git, gelesen werden kann.
Repositories und Branches und Git-Verlauf erkunden erklären, wie ein Projekt mit Git abgerufen und studiert wird – lesen Sie diese Kapitel, um zu lernen, wie man eine bestimmte Version eines Softwareprojekts erstellt und testet, nach Regressionen sucht und so weiter.
Personen, die tatsächliche Entwicklung durchführen müssen, werden auch Entwickeln mit Git und Entwicklung mit anderen teilen lesen wollen.
Weitere Kapitel behandeln speziellere Themen.
Umfassende Referenzdokumentation ist über die Manpages oder den Befehl git-help[1] verfügbar. Zum Beispiel für den Befehl git clone <repo> können Sie entweder
$ man git-clone
oder
$ git help clone
Mit letzterem können Sie den manuellen Betrachter Ihrer Wahl verwenden; weitere Informationen finden Sie unter git-help[1].
Siehe auch Git-Schnellreferenz für einen kurzen Überblick über Git-Befehle, ohne Erklärungen.
Schließlich finden Sie unter Notizen und To-Do-Liste für dieses Handbuch Möglichkeiten, wie Sie helfen können, dieses Handbuch vollständiger zu gestalten.
Repositories und Branches
Wie man ein Git-Repository erhält
Es wird nützlich sein, ein Git-Repository zum Experimentieren zu haben, während Sie dieses Handbuch lesen.
Der beste Weg, eines zu erhalten, ist die Verwendung des Befehls git-clone[1], um eine Kopie eines bestehenden Repositorys herunterzuladen. Wenn Sie noch kein Projekt im Sinn haben, hier einige interessante Beispiele
# Git itself (approx. 40MB download): $ git clone git://git.kernel.org/pub/scm/git/git.git # the Linux kernel (approx. 640MB download): $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Der anfängliche Klon kann bei einem großen Projekt zeitaufwendig sein, aber Sie müssen nur einmal klonen.
Der Klon-Befehl erstellt ein neues Verzeichnis, das nach dem Projekt benannt ist (git oder linux in den obigen Beispielen). Nachdem Sie in dieses Verzeichnis gewechselt sind (`cd`), werden Sie sehen, dass es eine Kopie der Projektdateien enthält, die als Arbeitskopie (working tree) bezeichnet wird, zusammen mit einem speziellen Verzeichnis auf oberster Ebene namens .git, das alle Informationen über den Verlauf des Projekts enthält.
Wie man eine andere Version eines Projekts auscheckt
Git sollte am besten als Werkzeug zum Speichern des Verlaufs einer Sammlung von Dateien betrachtet werden. Es speichert den Verlauf als komprimierte Sammlung von zusammenhängenden Schnappschüssen des Projektinhalts. In Git wird jede solche Version als Commit bezeichnet.
Diese Schnappschüsse sind nicht notwendigerweise alle in einer einzigen Zeile von alt nach neu angeordnet; stattdessen kann die Arbeit gleichzeitig entlang paralleler Entwicklungslinien, sogenannter Branches, fortschreiten, die sich zusammenführen und verzweigen können.
Ein einzelnes Git-Repository kann die Entwicklung mehrerer Branches verfolgen. Dies geschieht durch die Führung einer Liste von Heads, die auf den neuesten Commit jedes Branches verweisen; der Befehl git-branch[1] zeigt Ihnen die Liste der Branch-Heads.
$ git branch * master
Ein frisch geklontes Repository enthält standardmäßig einen einzigen Branch-Head namens "master", wobei das Arbeitsverzeichnis auf den Zustand des Projekts initialisiert ist, auf den dieser Branch-Head verweist.
Die meisten Projekte verwenden auch Tags. Tags sind, wie Heads, Referenzen auf den Verlauf des Projekts und können mit dem Befehl git-tag[1] aufgelistet werden.
$ git tag -l v2.6.11 v2.6.11-tree v2.6.12 v2.6.12-rc2 v2.6.12-rc3 v2.6.12-rc4 v2.6.12-rc5 v2.6.12-rc6 v2.6.13 ...
Tags sollen immer auf dieselbe Projektversion zeigen, während Heads voraussichtlich mit fortschreitender Entwicklung vorrücken.
Erstellen Sie einen neuen Branch-Head, der auf eine dieser Versionen verweist, und checken Sie ihn mit git-switch[1] aus.
$ git switch -c new v2.6.13
Das Arbeitsverzeichnis spiegelt dann den Inhalt wider, den das Projekt zum Zeitpunkt des Tagging mit v2.6.13 hatte, und git-branch[1] zeigt zwei Branches an, wobei ein Sternchen den aktuell ausgecheckten Branch markiert.
$ git branch master * new
Wenn Sie entscheiden, dass Sie stattdessen Version 2.6.17 sehen möchten, können Sie den aktuellen Branch so modifizieren, dass er stattdessen auf v2.6.17 zeigt, mit
$ git reset --hard v2.6.17
Beachten Sie, dass, wenn der aktuelle Branch-Head Ihre einzige Referenz auf einen bestimmten Punkt im Verlauf war, das Zurücksetzen dieses Branches dazu führen kann, dass Sie keine Möglichkeit mehr haben, den Verlauf zu finden, auf den er früher zeigte; verwenden Sie diesen Befehl also mit Vorsicht.
Verlauf verstehen: Commits
Jede Änderung im Verlauf eines Projekts wird durch einen Commit dargestellt. Der Befehl git-show[1] zeigt den neuesten Commit im aktuellen Branch.
$ git show
commit 17cf781661e6d38f737f15f53ab552f1e95960d7
Author: Linus Torvalds <torvalds@ppc970.osdl.org.(none)>
Date: Tue Apr 19 14:11:06 2005 -0700
Remove duplicate getenv(DB_ENVIRONMENT) call
Noted by Tony Luck.
diff --git a/init-db.c b/init-db.c
index 65898fa..b002dc6 100644
--- a/init-db.c
+++ b/init-db.c
@@ -7,7 +7,7 @@
int main(int argc, char **argv)
{
- char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
+ char *sha1_dir, *path;
int len, i;
if (mkdir(".git", 0755) < 0) {
Wie Sie sehen können, zeigt ein Commit, wer die letzte Änderung vorgenommen hat, was er getan hat und warum.
Jeder Commit hat eine 40-stellige Hexadezimal-ID, manchmal auch "Objektname" oder "SHA-1-ID" genannt, die in der ersten Zeile der Ausgabe von git show angezeigt wird. Sie können normalerweise auf einen Commit mit einem kürzeren Namen verweisen, wie z. B. einem Tag oder einem Branch-Namen, aber dieser längere Name kann auch nützlich sein. Am wichtigsten ist, dass es ein global eindeutiger Name für diesen Commit ist: Wenn Sie also jemandem den Objektname mitteilen (z. B. per E-Mail), sind Sie garantiert, dass dieser Name denselben Commit in seinem Repository wie in Ihrem bezeichnet (vorausgesetzt, sein Repository hat diesen Commit überhaupt). Da der Objektname als Hash über den Inhalt des Commits berechnet wird, sind Sie garantiert, dass sich der Commit nie ändern kann, ohne dass sich auch sein Name ändert.
Tatsächlich werden wir in Git-Konzepte sehen, dass alles, was im Git-Verlauf gespeichert ist, einschließlich Dateidaten und Verzeichnisinhalte, in einem Objekt mit einem Namen gespeichert wird, der ein Hash seines Inhalts ist.
Verlauf verstehen: Commits, Eltern und Erreichbarkeit
Jeder Commit (außer dem allerersten Commit in einem Projekt) hat auch einen übergeordneten Commit, der zeigt, was vor diesem Commit geschah. Das Verfolgen der Kette von Eltern führt Sie schließlich zum Anfang des Projekts zurück.
Die Commits bilden jedoch keine einfache Liste; Git erlaubt es, Entwicklungslinien zu verzweigen und dann wieder zusammenzuführen, und der Punkt, an dem sich zwei Entwicklungslinien wieder treffen, wird als "Merge" bezeichnet. Der Commit, der einen Merge repräsentiert, kann daher mehr als einen Elternteil haben, wobei jeder Elternteil den neuesten Commit in einer der Entwicklungslinien darstellt, die zu diesem Punkt geführt haben.
Der beste Weg, dies zu sehen, ist die Verwendung des Befehls gitk[1]; wenn Sie gitk jetzt in einem Git-Repository ausführen und nach Merge-Commits suchen, hilft dies zu verstehen, wie Git den Verlauf organisiert.
Im Folgenden sagen wir, dass Commit X von Commit Y "erreichbar" ist, wenn Commit X ein Vorfahre von Commit Y ist. Äquivalent könnten Sie sagen, dass Y ein Nachfahre von X ist oder dass es eine Kette von Eltern gibt, die von Commit Y zu Commit X führt.
Verlauf verstehen: Verlaufdiagramme
Wir werden den Git-Verlauf manchmal mit Diagrammen wie dem untenstehenden darstellen. Commits werden als "o" dargestellt und die Verbindungen zwischen ihnen mit Linien, die mit - / und \ gezeichnet sind. Die Zeit vergeht von links nach rechts.
o--o--o <-- Branch A
/
o--o--o <-- master
\
o--o--o <-- Branch B
Wenn wir über einen bestimmten Commit sprechen müssen, kann der Buchstabe "o" durch einen anderen Buchstaben oder eine Zahl ersetzt werden.
Verlauf verstehen: Was ist ein Branch?
Wenn wir präzise sein müssen, verwenden wir das Wort "Branch", um eine Entwicklungslinie zu bedeuten, und "Branch-Head" (oder einfach "Head"), um eine Referenz auf den neuesten Commit in einem Branch zu bedeuten. Im obigen Beispiel ist der Branch-Head namens "A" ein Zeiger auf einen bestimmten Commit, aber wir bezeichnen die Linie von drei Commits, die bis zu diesem Punkt führen, als Teil von "Branch A".
Wenn jedoch keine Verwechslung entsteht, verwenden wir oft den Begriff "Branch" sowohl für Branches als auch für Branch-Heads.
Branches manipulieren
Das Erstellen, Löschen und Ändern von Branches ist schnell und einfach; hier ist eine Zusammenfassung der Befehle.
gitbranch-
Listet alle Branches auf.
gitbranch<branch>-
Erstellt einen neuen Branch namens <branch>, der auf denselben Punkt im Verlauf wie der aktuelle Branch verweist.
gitbranch<branch> <start-point>-
Erstellt einen neuen Branch namens <branch>, der auf <start-point> verweist, was auf jede beliebige Weise angegeben werden kann, einschließlich der Verwendung eines Branch-Namens oder eines Tag-Namens.
gitbranch-d<branch>-
Löscht den Branch <branch>; wenn der Branch nicht vollständig in seinen Upstream-Branch integriert oder im aktuellen Branch enthalten ist, schlägt dieser Befehl mit einer Warnung fehl.
gitbranch-D<branch>-
Löscht den Branch <branch> unabhängig von seinem Merge-Status.
gitswitch<branch>-
Macht den aktuellen Branch zu <branch> und aktualisiert das Arbeitsverzeichnis, um die von <branch> referenzierte Version widerzuspiegeln.
gitswitch-c<new> <start-point>-
Erstellt einen neuen Branch <new>, der auf <start-point> verweist, und checkt ihn aus.
Das spezielle Symbol "HEAD" kann immer verwendet werden, um sich auf den aktuellen Branch zu beziehen. Tatsächlich verwendet Git eine Datei namens HEAD im Verzeichnis .git, um sich zu merken, welcher Branch aktuell ist.
$ cat .git/HEAD ref: refs/heads/master
Eine alte Version untersuchen, ohne einen neuen Branch zu erstellen
Der Befehl git switch erwartet normalerweise einen Branch-Head, akzeptiert aber auch einen beliebigen Commit, wenn er mit `--detach` aufgerufen wird; Sie können zum Beispiel den von einem Tag referenzierten Commit auschecken.
$ git switch --detach v2.6.17 Note: checking out 'v2.6.17'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another switch. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command again. Example: git switch -c new_branch_name HEAD is now at 427abfa Linux v2.6.17
HEAD bezieht sich dann auf die SHA-1 des Commits anstelle eines Branches, und `git branch` zeigt an, dass Sie sich nicht mehr in einem Branch befinden.
$ cat .git/HEAD 427abfa28afedffadfca9dd8b067eb6d36bac53f $ git branch * (detached from v2.6.17) master
In diesem Fall sagen wir, dass HEAD "detached" (getrennt) ist.
Dies ist eine einfache Möglichkeit, eine bestimmte Version auszuchecken, ohne einen Namen für den neuen Branch erfinden zu müssen. Sie können später immer noch einen neuen Branch (oder Tag) für diese Version erstellen, wenn Sie sich entscheiden.
Branches von einem Remote-Repository untersuchen
Der Branch "master", der zum Zeitpunkt des Klonens erstellt wurde, ist eine Kopie des HEAD im Repository, von dem Sie geklont haben. Dieses Repository kann jedoch auch andere Branches enthalten haben, und Ihr lokales Repository speichert Branches, die jeden dieser Remote-Branches verfolgen, sogenannte Remote-Tracking-Branches, die Sie mit der Option -r von git-branch[1] anzeigen können.
$ git branch -r origin/HEAD origin/html origin/maint origin/man origin/master origin/next origin/seen origin/todo
In diesem Beispiel wird "origin" als Remote-Repository oder kurz "remote" bezeichnet. Die Branches dieses Repositorys werden aus unserer Sicht als "Remote-Branches" bezeichnet. Die oben aufgeführten Remote-Tracking-Branches wurden basierend auf den Remote-Branches zum Zeitpunkt des Klonens erstellt und werden durch git fetch (daher git pull) und git push aktualisiert. Einzelheiten finden Sie unter Ein Repository mit git fetch aktualisieren.
Sie möchten möglicherweise einen dieser Remote-Tracking-Branches auf einem eigenen Branch aufbauen, genauso wie Sie es für einen Tag tun würden.
$ git switch -c my-todo-copy origin/todo
Sie können auch origin/todo direkt auschecken, um ihn zu untersuchen oder einen einmaligen Patch zu schreiben. Siehe getrennter HEAD.
Beachten Sie, dass der Name "origin" nur der Name ist, den Git standardmäßig verwendet, um auf das Repository zu verweisen, von dem Sie geklont haben.
Benennung von Branches, Tags und anderen Referenzen
Branches, Remote-Tracking-Branches und Tags sind alles Referenzen auf Commits. Alle Referenzen werden mit einem schrägstrichgetrennten Pfadnamen benannt, der mit refs beginnt; die bisher von uns verwendeten Namen sind eigentlich Abkürzungen.
-
Der Branch
testist eine Kurzform fürrefs/heads/test. -
Der Tag
v2.6.18ist eine Kurzform fürrefs/tags/v2.6.18. -
origin/masterist eine Kurzform fürrefs/remotes/origin/master.
Der vollständige Name ist gelegentlich nützlich, falls zum Beispiel jemals ein Tag und ein Branch mit demselben Namen existieren.
(Neu erstellte Refs werden tatsächlich im Verzeichnis .git/refs unter dem Pfad ihres Namens gespeichert. Aus Effizienzgründen können sie jedoch auch in einer einzigen Datei zusammengefasst werden; siehe git-pack-refs[1]).
Als weitere nützliche Abkürzung kann der "HEAD" eines Repositorys einfach mit dem Namen dieses Repositorys bezeichnet werden. So ist zum Beispiel "origin" normalerweise eine Abkürzung für den HEAD-Branch im Repository "origin".
Die vollständige Liste der Pfade, die Git für Referenzen prüft, und die Reihenfolge, nach der es entscheidet, welche es wählt, wenn mehrere Referenzen denselben Kurznamen haben, finden Sie im Abschnitt "SPECIFYING REVISIONS" von gitrevisions[7].
Ein Repository mit git fetch aktualisieren
Nachdem Sie ein Repository geklont und einige Ihrer eigenen Änderungen committet haben, möchten Sie möglicherweise das Original-Repository auf Aktualisierungen überprüfen.
Der Befehl git-fetch aktualisiert ohne Argumente alle Remote-Tracking-Branches auf die neueste Version, die im Original-Repository gefunden wurde. Er berührt keinen Ihrer eigenen Branches – nicht einmal den "master"-Branch, der für Sie beim Klonen erstellt wurde.
Branches von anderen Repositories abrufen
Sie können auch Branches von anderen Repositories als dem, von dem Sie geklont haben, verfolgen, mit git-remote[1].
$ git remote add staging git://git.kernel.org/.../gregkh/staging.git $ git fetch staging ... From git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging * [new branch] master -> staging/master * [new branch] staging-linus -> staging/staging-linus * [new branch] staging-next -> staging/staging-next
Neue Remote-Tracking-Branches werden unter dem Kurznamen gespeichert, den Sie git remote add gegeben haben, in diesem Fall staging.
$ git branch -r origin/HEAD -> origin/master origin/master staging/master staging/staging-linus staging/staging-next
Wenn Sie später git fetch <remote> ausführen, werden die Remote-Tracking-Branches für den benannten <remote> aktualisiert.
Wenn Sie die Datei .git/config untersuchen, werden Sie sehen, dass Git einen neuen Abschnitt hinzugefügt hat.
$ cat .git/config ... [remote "staging"] url = git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git fetch = +refs/heads/*:refs/remotes/staging/* ...
Dies ist es, was Git dazu veranlasst, die Branches des Remotes zu verfolgen; Sie können diese Konfigurationsoptionen ändern oder löschen, indem Sie .git/config mit einem Texteditor bearbeiten. (Siehe den Abschnitt "CONFIGURATION FILE" von git-config[1] für Details.)
Git-Verlauf erkunden
Git sollte am besten als Werkzeug zum Speichern des Verlaufs einer Sammlung von Dateien betrachtet werden. Es tut dies, indem es komprimierte Schnappschüsse des Inhalts einer Dateihierarchie zusammen mit "Commits" speichert, die die Beziehungen zwischen diesen Schnappschüssen zeigen.
Git bietet äußerst flexible und schnelle Werkzeuge zur Erkundung des Projektverlaufs.
Wir beginnen mit einem spezialisierten Werkzeug, das nützlich ist, um den Commit zu finden, der einen Fehler in ein Projekt eingeführt hat.
Wie man Bisect verwendet, um eine Regression zu finden
Angenommen, Version 2.6.18 Ihres Projekts funktionierte, aber die Version bei "master" stürzt ab. Manchmal ist der beste Weg, die Ursache einer solchen Regression zu finden, eine Brute-Force-Suche durch den Projektverlauf, um den spezifischen Commit zu finden, der das Problem verursacht hat. Der Befehl git-bisect[1] kann Ihnen dabei helfen.
$ git bisect start $ git bisect good v2.6.18 $ git bisect bad master Bisecting: 3537 revisions left to test after this [65934a9a028b88e83e2b0f8b36618fe503349f8e] BLOCK: Make USB storage depend on SCSI rather than selecting it [try #6]
Wenn Sie zu diesem Zeitpunkt git branch ausführen, werden Sie sehen, dass Git Sie vorübergehend in "(no branch)" versetzt hat. HEAD ist nun von jedem Branch getrennt und zeigt direkt auf einen Commit (mit Commit-ID 65934), der von "master", aber nicht von v2.6.18 erreichbar ist. Kompilieren und testen Sie ihn und prüfen Sie, ob er abstürzt. Nehmen wir an, er stürzt ab. Dann
$ git bisect bad Bisecting: 1769 revisions left to test after this [7eff82c8b1511017ae605f0c99ac275a7e21b867] i2c-core: Drop useless bitmaskings
checkt eine ältere Version aus. Machen Sie so weiter, indem Sie Git bei jeder Stufe mitteilen, ob die von ihm bereitgestellte Version gut oder schlecht ist, und bemerken Sie, dass sich die Anzahl der zu testenden Revisionen jedes Mal ungefähr halbiert.
Nach etwa 13 Tests (in diesem Fall) wird die Commit-ID des schuldigen Commits ausgegeben. Sie können den Commit dann mit git-show[1] untersuchen, herausfinden, wer ihn geschrieben hat, und ihm Ihren Fehlerbericht mit der Commit-ID per E-Mail senden. Führen Sie schließlich
$ git bisect reset
aus, um Sie zu dem Branch zurückzubringen, auf dem Sie vorher waren.
Beachten Sie, dass die Version, die git bisect Ihnen bei jedem Schritt auscheckt, nur ein Vorschlag ist, und Sie können frei eine andere Version ausprobieren, wenn Sie denken, dass es eine gute Idee wäre. Gelegentlich können Sie beispielsweise auf einen Commit stoßen, der etwas anderes kaputt gemacht hat; führen Sie
$ git bisect visualize
aus, was gitk ausführt und den von ihm gewählten Commit mit einem Marker "bisect" kennzeichnet. Wählen Sie einen unauffälligen Commit in der Nähe, notieren Sie seine Commit-ID und checken Sie ihn mit
$ git reset --hard fb47ddb2db
aus, testen Sie dann, führen Sie bisect good oder bisect bad entsprechend aus und fahren Sie fort.
Anstelle von git bisect visualize und dann git reset --hard fb47ddb2db möchten Sie Git vielleicht einfach mitteilen, dass Sie den aktuellen Commit überspringen möchten.
$ git bisect skip
In diesem Fall kann Git jedoch möglicherweise nicht feststellen, welcher erste schlechte Commit zwischen einigen zuerst übersprungenen Commits und einem späteren schlechten Commit liegt.
Es gibt auch Möglichkeiten, den Bisecting-Prozess zu automatisieren, wenn Sie ein Testskript haben, das einen guten von einem schlechten Commit unterscheiden kann. Weitere Informationen zu dieser und anderen Funktionen von git bisect finden Sie unter git-bisect[1].
Commits benennen
Wir haben bereits mehrere Möglichkeiten gesehen, Commits zu benennen.
-
40-stelliger Hexadezimal-Objektname
-
Branch-Name: bezieht sich auf den Commit am Kopf des angegebenen Branches.
-
Tag-Name: bezieht sich auf den Commit, auf den der angegebene Tag zeigt (wir haben gesehen, dass Branches und Tags Sonderfälle von Referenzen sind).
-
HEAD: bezieht sich auf den Kopf des aktuellen Branches.
Es gibt noch viele mehr; siehe den Abschnitt "SPECIFYING REVISIONS" der Manpage gitrevisions[7] für die vollständige Liste der Möglichkeiten, Revisionen zu benennen. Einige Beispiele:
$ git show fb47ddb2 # the first few characters of the object name # are usually enough to specify it uniquely $ git show HEAD^ # the parent of the HEAD commit $ git show HEAD^^ # the grandparent $ git show HEAD~4 # the great-great-grandparent
Denken Sie daran, dass Merge-Commits mehr als einen Elternteil haben können; standardmäßig folgen ^ und ~ dem ersten in der Commit-Liste aufgeführten Elternteil, aber Sie können auch wählen.
$ git show HEAD^1 # show the first parent of HEAD $ git show HEAD^2 # show the second parent of HEAD
Zusätzlich zu HEAD gibt es mehrere andere spezielle Namen für Commits.
Merges (die später besprochen werden) sowie Operationen wie git reset, die den aktuell ausgecheckten Commit ändern, setzen in der Regel ORIG_HEAD auf den Wert, den HEAD vor der aktuellen Operation hatte.
Die Operation git fetch speichert immer den Head des zuletzt abgerufenen Branches in FETCH_HEAD. Wenn Sie zum Beispiel git fetch ausführen, ohne einen lokalen Branch als Ziel der Operation anzugeben.
$ git fetch git://example.com/proj.git theirbranch
die abgerufenen Commits sind immer noch von FETCH_HEAD aus verfügbar.
Wenn wir Merges besprechen, werden wir auch den speziellen Namen MERGE_HEAD sehen, der sich auf den anderen Branch bezieht, den wir in den aktuellen Branch mergen.
Der Befehl git-rev-parse[1] ist ein Low-Level-Befehl, der gelegentlich nützlich ist, um einen Namen für einen Commit in den Objektname für diesen Commit zu übersetzen.
$ git rev-parse origin e05db0fd4f31dde7005f075a84f96b360d05984b
Tags erstellen
Wir können auch ein Tag erstellen, um auf einen bestimmten Commit zu verweisen; nach Ausführung von
$ git tag stable-1 1b2e1d63ff
Sie können stable-1 verwenden, um auf den Commit 1b2e1d63ff zu verweisen.
Dies erstellt ein "leichtgewichtigen" Tag. Wenn Sie auch einen Kommentar mit dem Tag versehen und ihn möglicherweise kryptografisch signieren möchten, sollten Sie stattdessen ein Tag-Objekt erstellen; Einzelheiten finden Sie auf der Manpage git-tag[1].
Revisionen durchsuchen
Der Befehl git-log[1] kann Listen von Commits anzeigen. Allein zeigt er alle Commits an, die vom Eltern-Commit erreichbar sind; Sie können aber auch spezifischere Anfragen stellen.
$ git log v2.5.. # commits since (not reachable from) v2.5 $ git log test..master # commits reachable from master but not test $ git log master..test # ...reachable from test but not master $ git log master...test # ...reachable from either test or master, # but not both $ git log --since="2 weeks ago" # commits from the last 2 weeks $ git log Makefile # commits which modify Makefile $ git log fs/ # ... which modify any file under fs/ $ git log -S'foo()' # commits which add or remove any file data # matching the string 'foo()'
Und natürlich können Sie all diese kombinieren; das Folgende findet Commits seit v2.5, die die Makefile oder eine Datei unter fs betreffen.
$ git log v2.5.. Makefile fs/
Sie können git log auch bitten, Patches anzuzeigen.
$ git log -p
Siehe die Option --pretty in der Manpage git-log[1] für weitere Anzeigeoptionen.
Beachten Sie, dass git log mit dem neuesten Commit beginnt und rückwärts durch die Eltern geht; da der Git-Verlauf jedoch mehrere unabhängige Entwicklungslinien enthalten kann, kann die spezifische Reihenfolge, in der Commits aufgelistet werden, etwas willkürlich sein.
Diffs generieren
Sie können Diffs zwischen zwei beliebigen Versionen mit git-diff[1] generieren.
$ git diff master..test
Dies erzeugt den Diff zwischen den Spitzen der beiden Branches. Wenn Sie stattdessen den Diff von ihrem gemeinsamen Vorfahren ermitteln möchten, können Sie drei Punkte anstelle von zwei verwenden.
$ git diff master...test
Manchmal ist stattdessen eine Reihe von Patches das, was Sie wollen; dafür können Sie git-format-patch[1] verwenden.
$ git format-patch master..test
generiert eine Datei mit einem Patch für jeden Commit, der von `test` erreichbar ist, aber nicht von `master`.
Alte Dateiversionen anzeigen
Sie können jederzeit eine alte Version einer Datei anzeigen, indem Sie einfach die richtige Revision auschecken. Aber manchmal ist es bequemer, eine alte Version einer einzelnen Datei anzeigen zu können, ohne etwas auszuchecken; dieser Befehl tut das.
$ git show v2.5:fs/locks.c
Vor dem Doppelpunkt kann alles stehen, was einen Commit benennt, und danach kann ein beliebiger Pfad zu einer von Git verfolgten Datei stehen.
Beispiele
Anzahl der Commits auf einem Branch zählen
Angenommen, Sie möchten wissen, wie viele Commits Sie auf mybranch gemacht haben, seit es sich von origin abgespalten hat.
$ git log --pretty=oneline origin..mybranch | wc -l
Alternativ können Sie diese Art von Aufgabe oft mit dem Low-Level-Befehl git-rev-list[1] durchführen, der nur die SHA-1s aller angegebenen Commits auflistet.
$ git rev-list origin..mybranch | wc -l
Prüfen, ob zwei Branches auf denselben Verlauf zeigen
Angenommen, Sie möchten prüfen, ob zwei Branches auf denselben Punkt im Verlauf zeigen.
$ git diff origin..master
sagt Ihnen, ob die Inhalte des Projekts an den beiden Branches gleich sind; theoretisch ist es jedoch möglich, dass dieselben Projektinhalte über zwei verschiedene historische Routen erreicht wurden. Sie könnten die Objekt-IDs vergleichen.
$ git rev-list origin e05db0fd4f31dde7005f075a84f96b360d05984b $ git rev-list master e05db0fd4f31dde7005f075a84f96b360d05984b
Oder Sie können sich daran erinnern, dass der Operator ... alle Commits auswählt, die von einem der beiden Referenzen, aber nicht von beiden erreichbar sind; daher
$ git log origin...master
gibt keine Commits zurück, wenn die beiden Branches gleich sind.
Ersten getaggten Release finden, der einen bestimmten Fix enthält
Angenommen, Sie wissen, dass der Commit e05db0fd ein bestimmtes Problem behoben hat. Sie möchten die früheste getaggte Version finden, die diesen Fix enthält.
Natürlich kann es mehr als eine Antwort geben – wenn sich der Verlauf nach Commit e05db0fd verzweigt hat, könnte es mehrere "früheste" getaggte Releases geben.
Sie könnten einfach die Commits seit e05db0fd visuell inspizieren.
$ gitk e05db0fd..
oder Sie können git-name-rev[1] verwenden, der dem Commit einen Namen gibt, der auf einem Tag basiert, den er auf einen Nachfahren des Commits zeigt.
$ git name-rev --tags e05db0fd e05db0fd tags/v1.5.0-rc1^0~23
Der Befehl git-describe[1] macht das Gegenteil und benennt die Revision unter Verwendung eines Tags, auf dem der angegebene Commit basiert.
$ git describe e05db0fd v1.5.0-rc0-260-ge05db0f
aber das kann Ihnen manchmal helfen zu erraten, welche Tags nach dem angegebenen Commit kommen könnten.
Wenn Sie nur überprüfen möchten, ob eine bestimmte getaggte Version einen bestimmten Commit enthält, könnten Sie git-merge-base[1] verwenden.
$ git merge-base e05db0fd v1.5.0-rc1 e05db0fd4f31dde7005f075a84f96b360d05984b
Der Merge-Base-Befehl findet einen gemeinsamen Vorfahren der angegebenen Commits und gibt im Falle, dass einer ein Nachfahre des anderen ist, immer einen davon zurück; die obige Ausgabe zeigt also, dass e05db0fd tatsächlich ein Vorfahre von v1.5.0-rc1 ist.
Alternativ beachten Sie, dass
$ git log v1.5.0-rc1..e05db0fd
gibt eine leere Ausgabe aus, wenn und nur wenn v1.5.0-rc1 e05db0fd enthält, da er nur Commits ausgibt, die von v1.5.0-rc1 nicht erreichbar sind.
Als weitere Alternative listet der Befehl git-show-branch[1] die von seinen Argumenten erreichbaren Commits mit einer Anzeige auf der linken Seite auf, die angibt, von welchen Argumenten dieser Commit erreichbar ist. Wenn Sie also etwas wie
$ git show-branch e05db0fd v1.5.0-rc0 v1.5.0-rc1 v1.5.0-rc2 ! [e05db0fd] Fix warnings in sha1_file.c - use C99 printf format if available ! [v1.5.0-rc0] GIT v1.5.0 preview ! [v1.5.0-rc1] GIT v1.5.0-rc1 ! [v1.5.0-rc2] GIT v1.5.0-rc2 ...
ausführen, dann eine Zeile wie
+ ++ [e05db0fd] Fix warnings in sha1_file.c - use C99 printf format if available
zeigt, dass e05db0fd von sich selbst, von v1.5.0-rc1 und von v1.5.0-rc2 erreichbar ist und nicht von v1.5.0-rc0.
Commits anzeigen, die für einen bestimmten Branch eindeutig sind
Angenommen, Sie möchten alle Commits sehen, die vom Branch-Head namens master erreichbar sind, aber nicht von einem anderen Head in Ihrem Repository.
Wir können alle Heads in diesem Repository mit git-show-ref[1] auflisten.
$ git show-ref --heads bf62196b5e363d73353a9dcf094c59595f3153b7 refs/heads/core-tutorial db768d5504c1bb46f63ee9d6e1772bd047e05bf9 refs/heads/maint a07157ac624b2524a059a3414e99f6f44bebc1e7 refs/heads/master 24dbc180ea14dc1aebe09f14c8ecf32010690627 refs/heads/tutorial-2 1e87486ae06626c2f31eaa63d26fc0fd646c8af2 refs/heads/tutorial-fixes
Wir können nur die Branch-Head-Namen erhalten und master entfernen, mit Hilfe der Standardprogramme `cut` und `grep`.
$ git show-ref --heads | cut -d' ' -f2 | grep -v '^refs/heads/master' refs/heads/core-tutorial refs/heads/maint refs/heads/tutorial-2 refs/heads/tutorial-fixes
Und dann können wir bitten, alle Commits anzuzeigen, die von `master` erreichbar sind, aber nicht von diesen anderen Heads.
$ gitk master --not $( git show-ref --heads | cut -d' ' -f2 | grep -v '^refs/heads/master' )
Offensichtlich sind unendliche Variationen möglich; zum Beispiel, um alle Commits anzuzeigen, die von einem Head erreichbar sind, aber nicht von einem Tag im Repository.
$ gitk $( git show-ref --heads ) --not $( git show-ref --tags )
(Siehe gitrevisions[7] für Erklärungen zur Commit-Auswahl-Syntax wie --not.)
Changelog und Tarball für eine Software-Release erstellen
Der Befehl git-archive[1] kann ein Tar- oder Zip-Archiv aus jeder Version eines Projekts erstellen; zum Beispiel
$ git archive -o latest.tar.gz --prefix=project/ HEAD
verwendet HEAD, um ein gziptes Tar-Archiv zu erstellen, bei dem jedem Dateinamen project/ vorangestellt ist. Das Ausgabeformat wird, wenn möglich, aus der Dateiendung abgeleitet. Details finden Sie unter git-archive[1].
Versionen von Git älter als 1.7.7 kennen das tar.gz-Format nicht, Sie müssen gzip explizit verwenden.
$ git archive --format=tar --prefix=project/ HEAD | gzip >latest.tar.gz
Wenn Sie eine neue Version eines Softwareprojekts veröffentlichen, möchten Sie vielleicht gleichzeitig ein Changelog erstellen, das Sie in die Release-Ankündigung aufnehmen.
Linus Torvalds erstellt beispielsweise neue Kernel-Releases, indem er sie taggt und dann Folgendes ausführt:
$ release-script 2.6.12 2.6.13-rc6 2.6.13-rc7
wobei `release-script` ein Shell-Skript ist, das so aussieht:
#!/bin/sh stable="$1" last="$2" new="$3" echo "# git tag v$new" echo "git archive --prefix=linux-$new/ v$new | gzip -9 > ../linux-$new.tar.gz" echo "git diff v$stable v$new | gzip -9 > ../patch-$new.gz" echo "git log --no-merges v$new ^v$last > ../ChangeLog-$new" echo "git shortlog --no-merges v$new ^v$last > ../ShortLog" echo "git diff --stat --summary -M v$last v$new > ../diffstat-$new"
und dann kopiert und fügt er die Ausgabe-Befehle einfach ein, nachdem er überprüft hat, ob sie in Ordnung aussehen.
Commits finden, die auf eine Datei mit gegebenem Inhalt verweisen
Jemand gibt Ihnen eine Kopie einer Datei und fragt, welche Commits eine Datei modifiziert haben, sodass sie vor oder nach dem Commit den gegebenen Inhalt enthielt. Das können Sie damit herausfinden.
$ git log --raw --abbrev=40 --pretty=oneline | grep -B 1 `git hash-object filename`
Warum das funktioniert, wird dem (fortgeschrittenen) Studenten als Übung überlassen. Die Manpages git-log[1], git-diff-tree[1] und git-hash-object[1] können hilfreich sein.
Entwicklung mit Git
Git Ihren Namen mitteilen
Bevor Sie Commits erstellen, sollten Sie sich bei Git vorstellen. Der einfachste Weg dazu ist die Verwendung von git-config[1]
$ git config --global user.name 'Your Name Comes Here' $ git config --global user.email 'you@yourdomain.example.com'
Dies fügt Folgendes zu einer Datei namens .gitconfig in Ihrem Home-Verzeichnis hinzu
[user] name = Your Name Comes Here email = you@yourdomain.example.com
Weitere Details zur Konfigurationsdatei finden Sie im Abschnitt "CONFIGURATION FILE" von git-config[1]. Die Datei ist eine einfache Textdatei, sodass Sie sie auch mit Ihrem bevorzugten Editor bearbeiten können.
Ein neues Repository erstellen
Das Erstellen eines neuen Repositorys von Grund auf ist sehr einfach
$ mkdir project $ cd project $ git init
Wenn Sie über einige anfängliche Inhalte verfügen (z. B. ein Tarball)
$ tar xzvf project.tar.gz $ cd project $ git init $ git add . # include everything below ./ in the first commit: $ git commit
Einen Commit erstellen
Das Erstellen eines neuen Commits erfolgt in drei Schritten
-
Änderungen am Arbeitsverzeichnis mit Ihrem bevorzugten Editor vornehmen.
-
Git über Ihre Änderungen informieren.
-
Den Commit mit dem Inhalt erstellen, den Sie Git in Schritt 2 mitgeteilt haben.
In der Praxis können Sie die Schritte 1 und 2 beliebig oft ineinander verschachteln und wiederholen: Um zu verfolgen, was in Schritt 3 committed werden soll, verwaltet Git einen Snapshot des Inhalts des Baums in einem speziellen Staging-Bereich, der als "Index" bezeichnet wird.
Anfangs ist der Inhalt des Index identisch mit dem von HEAD. Der Befehl git diff --cached, der die Differenz zwischen HEAD und dem Index anzeigt, sollte daher zu diesem Zeitpunkt keine Ausgabe erzeugen.
Das Ändern des Index ist einfach
Um den Index mit dem Inhalt einer neuen oder geänderten Datei zu aktualisieren, verwenden Sie
$ git add path/to/file
Um eine Datei aus dem Index und dem Arbeitsverzeichnis zu entfernen, verwenden Sie
$ git rm path/to/file
Nach jedem Schritt können Sie überprüfen, dass
$ git diff --cached
immer die Differenz zwischen HEAD und der Indexdatei anzeigt – dies ist das, was committet würde, wenn Sie den Commit jetzt erstellen – und dass
$ git diff
die Differenz zwischen dem Arbeitsverzeichnis und der Indexdatei anzeigt.
Beachten Sie, dass git add immer nur den aktuellen Inhalt einer Datei zum Index hinzufügt; weitere Änderungen an derselben Datei werden ignoriert, es sei denn, Sie führen git add erneut für die Datei aus.
Wenn Sie bereit sind, führen Sie einfach aus
$ git commit
und Git wird Sie zur Commit-Nachricht auffordern und dann den neuen Commit erstellen. Überprüfen Sie mit, ob alles wie erwartet aussieht
$ git show
Als besonderer Shortcut,
$ git commit -a
aktualisiert den Index mit allen Dateien, die Sie geändert oder entfernt haben, und erstellt einen Commit in einem Schritt.
Eine Reihe von Befehlen ist nützlich, um zu verfolgen, was Sie gerade committen möchten
$ git diff --cached # difference between HEAD and the index; what # would be committed if you ran "commit" now. $ git diff # difference between the index file and your # working directory; changes that would not # be included if you ran "commit" now. $ git diff HEAD # difference between HEAD and working tree; what # would be committed if you ran "commit -a" now. $ git status # a brief per-file summary of the above.
Sie können auch git-gui[1] verwenden, um Commits zu erstellen, Änderungen im Index und in Arbeitsverzeichnisdateien anzuzeigen und einzelne Diff-Hunks zur Aufnahme in den Index auszuwählen (indem Sie mit der rechten Maustaste auf den Diff-Hunk klicken und "Stage Hunk For Commit" auswählen).
Gute Commit-Nachrichten erstellen
Obwohl nicht zwingend erforderlich, ist es eine gute Idee, die Commit-Nachricht mit einer einzigen kurzen Zeile (nicht mehr als 50 Zeichen) zu beginnen, die die Änderung zusammenfasst, gefolgt von einer Leerzeile und dann einer ausführlicheren Beschreibung. Der Text bis zur ersten Leerzeile in einer Commit-Nachricht wird als Commit-Titel behandelt, und dieser Titel wird in ganz Git verwendet. Zum Beispiel wandelt git-format-patch[1] einen Commit in eine E-Mail um, und er verwendet den Titel in der Betreffzeile und den Rest des Commits im Körper.
Dateien ignorieren
Ein Projekt wird oft Dateien generieren, die Sie *nicht* mit Git verfolgen möchten. Dies umfasst typischerweise Dateien, die durch einen Build-Prozess generiert werden, oder temporäre Sicherungsdateien, die von Ihrem Editor erstellt werden. Natürlich ist es *keine* Verfolgung von Dateien mit Git nur eine Frage davon, git add nicht auf sie anzuwenden. Aber es wird schnell ärgerlich, diese nicht verfolgten Dateien herumliegen zu haben; z. B. machen sie git add . praktisch nutzlos und sie erscheinen immer wieder in der Ausgabe von git status.
Sie können Git anweisen, bestimmte Dateien zu ignorieren, indem Sie eine Datei namens .gitignore im obersten Verzeichnis Ihres Arbeitsverzeichnisses erstellen, mit Inhalten wie
# Lines starting with '#' are considered comments. # Ignore any file named foo.txt. foo.txt # Ignore (generated) html files, *.html # except foo.html which is maintained by hand. !foo.html # Ignore objects and archives. *.[oa]
Eine ausführliche Erklärung der Syntax finden Sie in gitignore[5]. Sie können .gitignore-Dateien auch in anderen Verzeichnissen Ihres Arbeitsverzeichnisses platzieren, und sie gelten für diese Verzeichnisse und deren Unterverzeichnisse. Die .gitignore-Dateien können wie jede andere Datei in Ihr Repository aufgenommen werden (führen Sie einfach git add .gitignore und git commit wie üblich aus), was praktisch ist, wenn die Ausschlussmuster (wie z. B. Muster, die Build-Ausgabedateien abgleichen) auch für andere Benutzer sinnvoll sind, die Ihr Repository klonen.
Wenn Sie möchten, dass die Ausschlussmuster nur bestimmte Repositories betreffen (und nicht jedes Repository für ein bestimmtes Projekt), können Sie sie stattdessen in einer Datei in Ihrem Repository namens .git/info/exclude oder in einer beliebigen Datei, die durch die Konfigurationsvariable core.excludesFile angegeben wird, ablegen. Einige Git-Befehle können Ausschlussmuster auch direkt auf der Befehlszeile entgegennehmen. Details finden Sie in gitignore[5].
Zusammenführen
Sie können zwei sich verzweigende Entwicklungszweige mit git-merge[1] wieder zusammenführen
$ git merge branchname
führt die Entwicklung im Branch branchname in den aktuellen Branch zusammen.
Eine Zusammenführung erfolgt durch die Kombination der Änderungen, die in branchname vorgenommen wurden, und der Änderungen, die bis zum letzten Commit in Ihrem aktuellen Branch seit der Verzweigung ihrer Historien vorgenommen wurden. Das Arbeitsverzeichnis wird durch das Ergebnis der Zusammenführung überschrieben, wenn diese Kombination sauber erfolgt, oder durch halb zusammengeführte Ergebnisse, wenn diese Kombination zu Konflikten führt. Daher wird Git, wenn Sie uncommittete Änderungen haben, die dieselben Dateien betreffen wie die von der Zusammenführung betroffenen, die Fortsetzung verweigern. Meistens möchten Sie Ihre Änderungen committen, bevor Sie zusammenführen können, und wenn nicht, dann kann git-stash[1] diese Änderungen vorübergehend sichern, während Sie die Zusammenführung durchführen, und sie danach wieder anwenden.
Wenn die Änderungen ausreichend unabhängig sind, schließt Git die Zusammenführung automatisch ab und committet das Ergebnis (oder verwendet einen vorhandenen Commit im Fall von Fast-Forward, siehe unten). Wenn es andererseits Konflikte gibt – zum Beispiel, wenn dieselbe Datei in zwei verschiedenen Arten im Remote-Branch und im lokalen Branch geändert wird – dann werden Sie gewarnt; die Ausgabe könnte etwa so aussehen
$ git merge next 100% (4/4) done Auto-merged file.txt CONFLICT (content): Merge conflict in file.txt Automatic merge failed; fix conflicts and then commit the result.
Konfliktmarker bleiben in den problematischen Dateien zurück, und nachdem Sie die Konflikte manuell gelöst haben, können Sie den Index mit den Inhalten aktualisieren und Git commit ausführen, wie Sie es normalerweise beim Erstellen einer neuen Datei tun würden.
Wenn Sie den resultierenden Commit mit gitk untersuchen, werden Sie sehen, dass er zwei Elternteile hat, einer zeigt auf die Spitze des aktuellen Branches und einer auf die Spitze des anderen Branches.
Eine Zusammenführung auflösen
Wenn eine Zusammenführung nicht automatisch aufgelöst wird, lässt Git den Index und das Arbeitsverzeichnis in einem speziellen Zustand, der Ihnen alle Informationen liefert, die Sie zur Lösung der Zusammenführung benötigen.
Dateien mit Konflikten sind im Index speziell markiert, sodass git-commit[1] fehlschlägt, bis Sie das Problem gelöst und den Index aktualisiert haben
$ git commit file.txt: needs merge
Außerdem listet git-status[1] diese Dateien als "unmerged" auf, und die Dateien mit Konflikten enthalten Konfliktmarker, wie folgt:
<<<<<<< HEAD:file.txt Hello world ======= Goodbye >>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt
Alles, was Sie tun müssen, ist die Dateien zu bearbeiten, um die Konflikte zu lösen, und dann
$ git add file.txt $ git commit
Beachten Sie, dass die Commit-Nachricht bereits mit einigen Informationen zur Zusammenführung für Sie vorausgefüllt ist. Normalerweise können Sie diese Standardnachricht unverändert verwenden, aber Sie können bei Bedarf zusätzliche Kommentare hinzufügen.
Das Obige ist alles, was Sie wissen müssen, um eine einfache Zusammenführung zu lösen. Aber Git bietet auch weitere Informationen zur Konfliktlösung an
Hilfe zur Konfliktlösung während einer Zusammenführung erhalten
Alle Änderungen, die Git automatisch zusammenführen konnte, sind bereits in die Indexdatei aufgenommen, sodass git-diff[1] nur die Konflikte anzeigt. Es verwendet eine ungewöhnliche Syntax
$ git diff diff --cc file.txt index 802992c,2b60207..0000000 --- a/file.txt +++ b/file.txt @@@ -1,1 -1,1 +1,5 @@@ ++<<<<<<< HEAD:file.txt +Hello world ++======= + Goodbye ++>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt
Erinnern Sie sich, dass der Commit, der nach der Lösung dieses Konflikts committet wird, anstelle des üblichen einen zwei Elternteile haben wird: ein Elternteil wird HEAD sein, die Spitze des aktuellen Branches; der andere wird die Spitze des anderen Branches sein, der temporär in MERGE_HEAD gespeichert ist.
Während der Zusammenführung enthält der Index drei Versionen jeder Datei. Jede dieser drei "Dateistufen" repräsentiert eine andere Version der Datei
$ git show :1:file.txt # the file in a common ancestor of both branches $ git show :2:file.txt # the version from HEAD. $ git show :3:file.txt # the version from MERGE_HEAD.
Wenn Sie git-diff[1] bitten, die Konflikte anzuzeigen, führt es einen Drei-Wege-Vergleich zwischen den zusammengeführten Ergebnissen im Arbeitsverzeichnis mit den Stufen 2 und 3 durch, um nur die Hunks anzuzeigen, deren Inhalte von beiden Seiten stammen und gemischt sind (mit anderen Worten, wenn die Hunk-Ergebnisse nur von Stufe 2 stammen, ist dieser Teil nicht widersprüchlich und wird nicht angezeigt. Gleiches gilt für Stufe 3).
Der obige Diff zeigt die Unterschiede zwischen der Arbeitsverzeichnisversion von file.txt und den Stufen 2 und 3. Anstatt jede Zeile mit einem einzelnen + oder - zu versehen, werden nun zwei Spalten verwendet: die erste Spalte wird für Unterschiede zwischen dem ersten Elternteil und der Arbeitskopie verwendet, und die zweite für Unterschiede zwischen dem zweiten Elternteil und der Arbeitskopie. (Einzelheiten zum Format finden Sie im Abschnitt "COMBINED DIFF FORMAT" von git-diff-files[1].)
Nachdem der Konflikt auf offensichtliche Weise gelöst wurde (aber bevor der Index aktualisiert wird), sieht der Diff wie folgt aus
$ git diff diff --cc file.txt index 802992c,2b60207..0000000 --- a/file.txt +++ b/file.txt @@@ -1,1 -1,1 +1,1 @@@ - Hello world -Goodbye ++Goodbye world
Dies zeigt, dass unsere gelöste Version "Hello world" vom ersten Elternteil gelöscht hat, "Goodbye" vom zweiten Elternteil gelöscht hat und "Goodbye world" hinzugefügt hat, was zuvor in beiden fehlte.
Einige spezielle Diff-Optionen erlauben das Vergleichen des Arbeitsverzeichnisses mit jeder dieser Stufen
$ git diff -1 file.txt # diff against stage 1 $ git diff --base file.txt # same as the above $ git diff -2 file.txt # diff against stage 2 $ git diff --ours file.txt # same as the above $ git diff -3 file.txt # diff against stage 3 $ git diff --theirs file.txt # same as the above.
Bei Verwendung der *ort*-Zusammenführungsstrategie (Standard) schreibt Git, bevor das Arbeitsverzeichnis mit dem Ergebnis der Zusammenführung aktualisiert wird, einen Ref namens AUTO_MERGE, der den Zustand des Baums widerspiegelt, den es gerade schreiben wird. Konfliktpfade mit Textkonflikten, die nicht automatisch zusammengeführt werden konnten, werden mit Konfliktmarkierungen in diesen Baum geschrieben, genau wie im Arbeitsverzeichnis. AUTO_MERGE kann somit mit git-diff[1] verwendet werden, um die Änderungen anzuzeigen, die Sie bisher zur Lösung von Konflikten vorgenommen haben. Unter Verwendung desselben Beispiels wie oben, nach der Lösung des Konflikts, erhalten wir
$ git diff AUTO_MERGE diff --git a/file.txt b/file.txt index cd10406..8bf5ae7 100644 --- a/file.txt +++ b/file.txt @@ -1,5 +1 @@ -<<<<<<< HEAD:file.txt -Hello world -======= -Goodbye ->>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt +Goodbye world
Beachten Sie, dass der Diff zeigt, dass wir die Konfliktmarker und beide Versionen der Inhaltszeile gelöscht und stattdessen "Goodbye world" geschrieben haben.
Die Befehle git-log[1] und gitk[1] bieten ebenfalls spezielle Hilfe für Zusammenführungen
$ git log --merge $ gitk --merge
Diese zeigen alle Commits an, die nur auf HEAD oder auf MERGE_HEAD existieren und eine nicht zusammengeführte Datei betreffen.
Sie können auch git-mergetool[1] verwenden, mit dem Sie nicht zusammengeführte Dateien mit externen Tools wie Emacs oder kdiff3 zusammenführen können.
Jedes Mal, wenn Sie die Konflikte in einer Datei lösen und den Index aktualisieren
$ git add file.txt
werden die verschiedenen Stufen dieser Datei "zusammengefasst", und danach zeigt git diff (standardmäßig) keine Diffs mehr für diese Datei an.
Eine Zusammenführung rückgängig machen
Wenn Sie feststecken und entscheiden, das ganze Durcheinander wegzuwerfen, können Sie jederzeit mit folgendem Befehl zum Zustand vor der Zusammenführung zurückkehren:
$ git merge --abort
Oder, wenn Sie bereits die Zusammenführung committet haben, die Sie verwerfen möchten,
$ git reset --hard ORIG_HEAD
Der letzte Befehl kann jedoch in einigen Fällen gefährlich sein – werfen Sie niemals einen Commit weg, den Sie bereits committet haben, wenn dieser Commit möglicherweise selbst in einen anderen Branch gemergt wurde, da dies weitere Zusammenführungen verwirren kann.
Fast-Forward Merges
Es gibt einen Sonderfall, der oben nicht erwähnt wurde und anders behandelt wird. Normalerweise führt eine Zusammenführung zu einem Merge-Commit mit zwei Eltern, einer, der auf jede der beiden Entwicklungslinien zeigt, die zusammengeführt wurden.
Wenn jedoch der aktuelle Branch ein Vorfahre des anderen ist – sodass jeder Commit im aktuellen Branch bereits im anderen Branch enthalten ist –, dann führt Git einfach einen "Fast-Forward" durch; der Kopf des aktuellen Branches wird nach vorne verschoben, um auf den Kopf des zusammengeführten Branches zu zeigen, ohne dass neue Commits erstellt werden.
Fehler beheben
Wenn Sie das Arbeitsverzeichnis durcheinandergebracht haben, aber Ihren Fehler noch nicht committet haben, können Sie mit folgendem Befehl das gesamte Arbeitsverzeichnis in den letzten committeten Zustand zurückversetzen:
$ git restore --staged --worktree :/
Wenn Sie einen Commit erstellen, den Sie später bereuen, gibt es zwei grundlegend unterschiedliche Möglichkeiten, das Problem zu beheben
-
Sie können einen neuen Commit erstellen, der das rückgängig macht, was durch den alten Commit getan wurde. Dies ist das Richtige, wenn Ihr Fehler bereits öffentlich gemacht wurde.
-
Sie können den alten Commit zurückgehen und ändern. Dies sollten Sie niemals tun, wenn Sie die Historie bereits öffentlich gemacht haben; Git erwartet normalerweise nicht, dass sich die "Historie" eines Projekts ändert, und kann wiederholte Zusammenführungen von einem Branch, dessen Historie geändert wurde, nicht korrekt durchführen.
Einen Fehler mit einem neuen Commit beheben
Das Erstellen eines neuen Commits, der eine frühere Änderung rückgängig macht, ist sehr einfach; übergeben Sie dem Befehl git-revert[1] einfach eine Referenz auf den fehlerhaften Commit; um beispielsweise den letzten Commit rückgängig zu machen
$ git revert HEAD
Dies erstellt einen neuen Commit, der die Änderung in HEAD rückgängig macht. Sie erhalten die Möglichkeit, die Commit-Nachricht für den neuen Commit zu bearbeiten.
Sie können auch eine frühere Änderung rückgängig machen, zum Beispiel die vorletzte
$ git revert HEAD^
In diesem Fall versucht Git, die alte Änderung rückgängig zu machen und gleichzeitig alle seitdem vorgenommenen Änderungen beizubehalten. Wenn neuere Änderungen mit den rückgängig zu machenden Änderungen überlappen, werden Sie gebeten, Konflikte manuell zu beheben, genau wie im Fall von Auflösen einer Zusammenführung.
Einen Fehler durch Umschreiben der Historie beheben
Wenn der fehlerhafte Commit der letzte Commit ist und Sie diesen Commit noch nicht öffentlich gemacht haben, können Sie ihn einfach mit git reset zerstören.
Alternativ können Sie das Arbeitsverzeichnis bearbeiten und den Index aktualisieren, um Ihren Fehler zu beheben, genau so, als ob Sie einen neuen Commit erstellen würden, und dann ausführen
$ git commit --amend
Dies ersetzt den alten Commit durch einen neuen Commit, der Ihre Änderungen enthält, und gibt Ihnen die Möglichkeit, die alte Commit-Nachricht zuerst zu bearbeiten.
Auch hier sollten Sie dies niemals mit einem Commit tun, der möglicherweise bereits in einen anderen Branch gemergt wurde; verwenden Sie in diesem Fall stattdessen git-revert[1].
Es ist auch möglich, Commits weiter hinten in der Historie zu ersetzen, aber dies ist ein fortgeschrittenes Thema, das einem anderen Kapitel überlassen werden sollte.
Eine alte Version einer Datei auschecken
Beim Rückgängigmachen einer früheren fehlerhaften Änderung kann es nützlich sein, mit git-restore[1] eine ältere Version einer bestimmten Datei auszuchecken. Der Befehl
$ git restore --source=HEAD^ path/to/file
ersetzt path/to/file durch den Inhalt, den es im Commit HEAD^ hatte, und aktualisiert auch den Index entsprechend. Er ändert keine Branches.
Wenn Sie nur eine alte Version der Datei ansehen möchten, ohne das Arbeitsverzeichnis zu ändern, können Sie dies mit git-show[1] tun
$ git show HEAD^:path/to/file
Dies zeigt die angegebene Version der Datei an.
Arbeit in Bearbeitung vorübergehend beiseitelegen
Während Sie an etwas Kompliziertem arbeiten, finden Sie einen irrelevanten, aber offensichtlichen und trivialen Fehler. Sie möchten ihn beheben, bevor Sie fortfahren. Sie können git-stash[1] verwenden, um den aktuellen Zustand Ihrer Arbeit zu speichern, und nachdem Sie den Fehler behoben haben (oder optional, nachdem Sie dies in einem anderen Branch getan und sind zurückgekehrt), die Änderungen in Bearbeitung wiederherstellen.
$ git stash push -m "work in progress for foo feature"
Dieser Befehl speichert Ihre Änderungen im stash und setzt Ihr Arbeitsverzeichnis und den Index auf den Stand der Spitze Ihres aktuellen Branches zurück. Dann können Sie Ihre Korrektur wie gewohnt vornehmen.
... edit and test ... $ git commit -a -m "blorpl: typofix"
Danach können Sie mit git stash pop zu dem zurückkehren, woran Sie gearbeitet haben.
$ git stash pop
Gute Leistung sicherstellen
Bei großen Repositories ist Git auf Kompression angewiesen, um zu verhindern, dass die Verlaufsdaten zu viel Speicherplatz auf der Festplatte oder im Arbeitsspeicher beanspruchen. Einige Git-Befehle führen möglicherweise automatisch git-gc[1] aus, sodass Sie sich keine Sorgen machen müssen, ihn manuell auszuführen. Das Komprimieren eines großen Repositorys kann jedoch eine Weile dauern, sodass Sie gc möglicherweise explizit aufrufen möchten, um zu vermeiden, dass die automatische Komprimierung eintritt, wenn es unpraktisch ist.
Zuverlässigkeit sicherstellen
Repository auf Beschädigung prüfen
Der Befehl git-fsck[1] führt eine Reihe von Selbstkonsistenzprüfungen im Repository durch und meldet Probleme. Dies kann einige Zeit dauern.
$ git fsck dangling commit 7281251ddd2a61e38657c827739c57015671a6b3 dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63 dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5 dangling blob 218761f9d90712d37a9c5e36f406f92202db07eb dangling commit bf093535a34a4d35731aa2bd90fe6b176302f14f dangling commit 8e4bec7f2ddaa268bef999853c25755452100f8e dangling tree d50bb86186bf27b681d25af89d3b5b68382e4085 dangling tree b24c2473f1fd3d91352a624795be026d64c8841f ...
Sie sehen informative Meldungen über hängende Objekte. Dies sind Objekte, die noch im Repository vorhanden sind, aber von keinem Ihrer Branches mehr referenziert werden und nach einiger Zeit mit gc entfernt werden können (und werden). Sie können git fsck --no-dangling ausführen, um diese Meldungen zu unterdrücken und weiterhin echte Fehler anzuzeigen.
Verlorene Änderungen wiederherstellen
Reflogs
Nehmen wir an, Sie ändern einen Branch mit git reset --hard und stellen dann fest, dass der Branch die einzige Referenz war, die Sie auf diesen Punkt in der Historie hatten.
Glücklicherweise hält Git auch ein Protokoll, genannt "Reflog", aller früheren Werte jedes Branches. So können Sie in diesem Fall immer noch die alte Historie finden, z. B. mit
$ git log master@{1}
Dies listet die Commits auf, die von der vorherigen Version des master Branch-Kopfes erreichbar sind. Diese Syntax kann mit jedem Git-Befehl verwendet werden, der einen Commit akzeptiert, nicht nur mit git log. Einige andere Beispiele:
$ git show master@{2} # See where the branch pointed 2,
$ git show master@{3} # 3, ... changes ago.
$ gitk master@{yesterday} # See where it pointed yesterday,
$ gitk master@{"1 week ago"} # ... or last week
$ git log --walk-reflogs master # show reflog entries for master
Ein separates Reflog wird für HEAD geführt, also
$ git show HEAD@{"1 week ago"}
zeigt, worauf HEAD vor einer Woche gezeigt hat, nicht worauf Ihr aktueller Branch vor einer Woche gezeigt hat. Dies ermöglicht es Ihnen, die Historie dessen zu sehen, was Sie ausgecheckt haben.
Die Reflogs werden standardmäßig 30 Tage lang aufbewahrt, danach können sie bereinigt werden. Lesen Sie git-reflog[1] und git-gc[1], um zu erfahren, wie Sie dieses Bereinigen steuern können, und lesen Sie den Abschnitt "SPECIFYING REVISIONS" von gitrevisions[7] für Details.
Beachten Sie, dass die Reflog-Historie sehr unterschiedlich von der normalen Git-Historie ist. Während die normale Historie von jedem Repository geteilt wird, das am selben Projekt arbeitet, wird die Reflog-Historie nicht geteilt: Sie sagt Ihnen nur, wie sich die Branches in Ihrem lokalen Repository im Laufe der Zeit geändert haben.
Hängende Objekte untersuchen
In einigen Situationen kann Ihnen das Reflog nicht helfen. Angenommen, Sie löschen einen Branch und stellen dann fest, dass Sie die Historie benötigen, die er enthielt. Das Reflog wird ebenfalls gelöscht; wenn Sie das Repository jedoch noch nicht bereinigt haben, können Sie die verlorenen Commits möglicherweise immer noch in den hängenden Objekten finden, die git fsck meldet. Details finden Sie unter Hängende Objekte.
$ git fsck dangling commit 7281251ddd2a61e38657c827739c57015671a6b3 dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63 dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5 ...
Sie können einen dieser hängenden Commits beispielsweise mit folgendem untersuchen:
$ gitk 7281251ddd --not --all
Dies tut, was es sagt: Es bedeutet, dass Sie die Commit-Historie sehen möchten, die durch die hängenden Commit(s) beschrieben wird, aber nicht die Historie, die durch alle Ihre vorhandenen Branches und Tags beschrieben wird. So erhalten Sie genau die Historie, die von diesem Commit erreichbar ist und verloren gegangen ist. (Und beachten Sie, dass es möglicherweise nicht nur ein Commit ist: Wir melden nur die "Spitze der Linie" als hängend, aber es könnte eine ganze tiefe und komplexe Commit-Historie geben, die fallen gelassen wurde.)
Wenn Sie entscheiden, dass Sie die Historie zurückhaben möchten, können Sie immer eine neue Referenz darauf erstellen, z. B. einen neuen Branch
$ git branch recovered-branch 7281251ddd
Andere Arten von hängenden Objekten (Blobs und Trees) sind ebenfalls möglich, und hängende Objekte können in anderen Situationen auftreten.
Entwicklung mit anderen teilen
Updates mit git pull erhalten
Nachdem Sie ein Repository geklont und einige eigene Änderungen committet haben, möchten Sie möglicherweise das ursprüngliche Repository auf Updates überprüfen und diese in Ihre eigene Arbeit integrieren.
Wir haben bereits gesehen, wie Remote-Tracking-Branches auf dem neuesten Stand gehalten werden mit git-fetch[1], und wie zwei Branches zusammengeführt werden. Sie können also Änderungen aus dem Master-Branch des ursprünglichen Repositorys mit
$ git fetch $ git merge origin/master
Der Befehl git-pull[1] bietet jedoch eine Möglichkeit, dies in einem Schritt zu tun
$ git pull origin master
Tatsächlich, wenn Sie master ausgecheckt haben, dann wurde dieser Branch von git clone so konfiguriert, dass er Änderungen vom HEAD-Branch des Origin-Repositorys bezieht. Daher können Sie oft das oben Genannte mit einem einfachen
$ git pull
Dieser Befehl holt Änderungen von den Remote-Branches in Ihre Remote-Tracking-Branches origin/* und führt den Standard-Branch in den aktuellen Branch zusammen.
Allgemeiner gilt, dass ein Branch, der von einem Remote-Tracking-Branch erstellt wurde, standardmäßig von diesem Branch zieht. Lesen Sie die Beschreibungen der Optionen branch.<name>.remote und branch.<name>.merge in git-config[1] und die Diskussion der Option --track in git-checkout[1], um zu erfahren, wie diese Standardwerte gesteuert werden.
Neben der Einsparung von Tastendrücken hilft Ihnen git pull auch, indem es eine Standard-Commit-Nachricht erstellt, die den Branch und das Repository dokumentiert, von dem Sie gezogen haben.
(Beachten Sie jedoch, dass im Fall eines Fast-Forward kein solcher Commit erstellt wird; stattdessen wird Ihr Branch einfach aktualisiert, um auf den neuesten Commit vom Upstream-Branch zu zeigen.)
Der Befehl git pull kann auch mit . als "Remote"-Repository angegeben werden, in diesem Fall führt er einfach einen Branch aus dem aktuellen Repository zusammen; daher sind die Befehle
$ git pull . branch $ git merge branch
ungefähr gleichwertig.
Patches an ein Projekt übermitteln
Wenn Sie nur wenige Änderungen haben, ist der einfachste Weg, diese einzureichen, sie als Patches per E-Mail zu senden
Verwenden Sie zunächst git-format-patch[1]; zum Beispiel
$ git format-patch origin
erzeugt eine nummerierte Reihe von Dateien im aktuellen Verzeichnis, eine für jeden Patch im aktuellen Branch, aber nicht in origin/HEAD.
git format-patch kann ein einleitendes "Cover Letter" enthalten. Sie können Kommentare zu einzelnen Patches nach der Dreistrich-Linie einfügen, die format-patch nach der Commit-Nachricht, aber vor dem Patch selbst platziert. Wenn Sie git notes verwenden, um Ihr Cover-Letter-Material zu verfolgen, wird git format-patch --notes die Notizen des Commits auf ähnliche Weise einfügen.
Sie können diese dann in Ihren Mail-Client importieren und manuell senden. Wenn Sie jedoch viel auf einmal senden müssen, können Sie das Skript git-send-email[1] verwenden, um den Prozess zu automatisieren. Konsultieren Sie zuerst die Mailingliste Ihres Projekts, um deren Anforderungen für die Einreichung von Patches zu ermitteln.
Patches in ein Projekt importieren
Git bietet auch ein Werkzeug namens git-am[1] (am steht für "apply mailbox") zum Importieren einer solchen per E-Mail gesendeten Patch-Serie. Speichern Sie einfach alle Patch-enthaltenden Nachrichten in der richtigen Reihenfolge in einer einzigen Mailbox-Datei, z. B. patches.mbox, und führen Sie dann aus
$ git am -3 patches.mbox
Git wendet jeden Patch in der Reihenfolge an; wenn Konflikte gefunden werden, stoppt es, und Sie können die Konflikte wie in "Eine Zusammenführung auflösen" beschrieben beheben. (Die Option -3 weist Git an, eine Zusammenführung durchzuführen; wenn Sie es vorziehen, dass es einfach abbricht und Ihren Baum und Index unverändert lässt, können Sie diese Option weglassen.)
Sobald der Index mit den Ergebnissen der Konfliktlösung aktualisiert ist, führen Sie anstelle der Erstellung eines neuen Commits einfach Folgendes aus:
$ git am --continue
und Git erstellt den Commit für Sie und fährt mit der Anwendung der verbleibenden Patches aus der Mailbox fort.
Das Endergebnis ist eine Reihe von Commits, einer für jeden Patch in der ursprünglichen Mailbox, mit Autorschaft und Commit-Log-Nachricht jeweils aus der Nachricht übernommen, die jeden Patch enthält.
Öffentliche Git-Repositories
Eine weitere Möglichkeit, Änderungen an einem Projekt zu übermitteln, ist, dem Maintainer dieses Projekts mitzuteilen, die Änderungen aus Ihrem Repository mit git-pull[1] zu ziehen. Im Abschnitt "Updates mit git pull erhalten" haben wir dies als Möglichkeit beschrieben, Updates vom "Haupt"-Repository zu erhalten, aber es funktioniert genauso gut in die andere Richtung.
Wenn Sie und der Maintainer beide Konten auf derselben Maschine haben, können Sie Änderungen direkt aus den Repositories des jeweils anderen ziehen; Befehle, die Repository-URLs als Argumente akzeptieren, akzeptieren auch einen lokalen Verzeichnisnamen
$ git clone /path/to/repository $ git pull /path/to/other/repository
oder eine SSH-URL
$ git clone ssh://yourhost/~you/repository
Für Projekte mit wenigen Entwicklern oder zur Synchronisation einiger privater Repositories ist dies möglicherweise alles, was Sie brauchen.
Der gebräuchlichere Weg ist jedoch, ein separates öffentliches Repository (normalerweise auf einem anderen Host) zu unterhalten, von dem andere ziehen können. Dies ist normalerweise praktischer und ermöglicht es Ihnen, private Arbeiten im Gange sauber von öffentlich sichtbarer Arbeit zu trennen.
Sie werden Ihre tägliche Arbeit in Ihrem persönlichen Repository fortsetzen, aber periodisch Änderungen von Ihrem persönlichen Repository in Ihr öffentliches Repository "pushen", wodurch andere Entwickler von diesem Repository ziehen können. Der Fluss der Änderungen sieht also in einer Situation, in der es einen anderen Entwickler mit einem öffentlichen Repository gibt, wie folgt aus:
you push
your personal repo ------------------> your public repo
^ |
| |
| you pull | they pull
| |
| |
| they push V
their public repo <------------------- their repo
Wir erklären, wie dies in den folgenden Abschnitten geschieht.
Ein öffentliches Repository einrichten
Angenommen, Ihr persönliches Repository befindet sich im Verzeichnis ~/proj. Wir erstellen zuerst einen neuen Klon des Repositorys und teilen git daemon mit, dass es öffentlich sein soll.
$ git clone --bare ~/proj proj.git $ touch proj.git/git-daemon-export-ok
Das resultierende Verzeichnis proj.git enthält ein "bare" Git-Repository – es sind nur die Inhalte des Verzeichnisses .git, ohne dass Dateien darum herum ausgecheckt sind.
Kopieren Sie als Nächstes proj.git auf den Server, auf dem Sie das öffentliche Repository hosten möchten. Sie können scp, rsync oder was auch immer am praktischsten ist verwenden.
Exportieren eines Git-Repositorys über das Git-Protokoll
Dies ist die bevorzugte Methode.
Wenn ein anderer Administrator den Server verwaltet, sollte er Ihnen mitteilen, in welches Verzeichnis Sie das Repository legen sollen und unter welcher git://-URL es erscheinen wird. Sie können dann zum Abschnitt "Änderungen in ein öffentliches Repository pushen" springen.
Andernfalls müssen Sie nur git-daemon[1] starten; es wird auf Port 9418 lauschen. Standardmäßig erlaubt es den Zugriff auf jedes Verzeichnis, das wie ein Git-Verzeichnis aussieht und die magische Datei git-daemon-export-ok enthält. Die Übergabe einiger Verzeichnispfade als Argumente von git daemon schränkt den Export weiter auf diese Pfade ein.
Sie können git daemon auch als inetd-Dienst ausführen; Details finden Sie in der Manpage von git-daemon[1]. (Siehe insbesondere den Abschnitt mit Beispielen.)
Exportieren eines Git-Repositorys über HTTP
Das Git-Protokoll bietet eine bessere Leistung und Zuverlässigkeit, aber auf einem Host mit einem eingerichteten Webserver kann der HTTP-Export einfacher einzurichten sein.
Alles, was Sie tun müssen, ist, das neu erstellte Bare-Git-Repository in einem Verzeichnis zu platzieren, das vom Webserver exportiert wird, und einige Anpassungen vorzunehmen, um den Webclients zusätzliche Informationen zu geben, die sie benötigen
$ mv proj.git /home/you/public_html/proj.git $ cd proj.git $ git --bare update-server-info $ mv hooks/post-update.sample hooks/post-update
(Eine Erklärung der letzten beiden Zeilen finden Sie in git-update-server-info[1] und githooks[5].)
Werben Sie für die URL von proj.git. Jeder andere sollte dann in der Lage sein, von dieser URL zu klonen oder zu ziehen, zum Beispiel mit einer Befehlszeile wie
$ git clone http://yourserver.com/~you/proj.git
(Siehe auch setup-git-server-over-http für ein etwas ausgefeilteres Setup mit WebDAV, das auch das Pushen über HTTP ermöglicht.)
Änderungen in ein öffentliches Repository pushen
Beachten Sie, dass die beiden oben beschriebenen Techniken (Export über HTTP oder Git) es anderen Maintainern ermöglichen, Ihre neuesten Änderungen abzurufen, aber sie erlauben keinen Schreibzugriff, den Sie benötigen, um das öffentliche Repository mit den neuesten Änderungen aus Ihrem privaten Repository zu aktualisieren.
Der einfachste Weg, dies zu tun, ist die Verwendung von git-push[1] und SSH; um den Remote-Branch namens master mit dem neuesten Stand Ihres Branches namens master zu aktualisieren, führen Sie aus:
$ git push ssh://yourserver.com/~you/proj.git master:master
oder einfach
$ git push ssh://yourserver.com/~you/proj.git master
Wie bei git fetch wird git push sich beschweren, wenn dies nicht zu einem Fast-Forward führt; siehe den folgenden Abschnitt für Details zur Behandlung dieses Falls.
Beachten Sie, dass das Ziel eines push normalerweise ein bare Repository ist. Sie können auch in ein Repository pushen, das einen ausgecheckten Arbeitsbaum hat, aber ein Push zum Aktualisieren des aktuell ausgecheckten Branches ist standardmäßig untersagt, um Verwirrung zu vermeiden. Details finden Sie in der Beschreibung der Option receive.denyCurrentBranch in git-config[1].
Wie bei git fetch können Sie auch Konfigurationsoptionen einrichten, um Tipparbeit zu sparen; so zum Beispiel
$ git remote add public-repo ssh://yourserver.com/~you/proj.git
fügt Folgendes zu .git/config hinzu
[remote "public-repo"] url = yourserver.com:proj.git fetch = +refs/heads/*:refs/remotes/example/*
damit Sie denselben Push mit nur
$ git push public-repo master
Weitere Details finden Sie in den Erläuterungen zu den Optionen remote.<name>.url, branch.<name>.remote und remote.<name>.push in git-config[1].
Was tun, wenn ein Push fehlschlägt
Wenn ein Push nicht zu einem Fast-Forward des Remote-Branches führen würde, wird er mit einer Fehlermeldung wie dieser fehlschlagen
! [rejected] master -> master (non-fast-forward) error: failed to push some refs to '...' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Dies kann zum Beispiel passieren, wenn Sie
-
gitreset--hardverwenden, um bereits veröffentlichte Commits zu entfernen, oder -
gitcommit--amendverwenden, um bereits veröffentlichte Commits zu ersetzen (wie in Einen Fehler durch Umschreiben der Historie beheben), oder -
gitrebaseverwenden, um bereits veröffentlichte Commits zu rebasen (wie in Eine Patch-Serie mit git rebase aktuell halten).
Sie können git push erzwingen, die Aktualisierung trotzdem durchzuführen, indem Sie dem Branch-Namen ein Pluszeichen voranstellen
$ git push ssh://yourserver.com/~you/proj.git +master
Beachten Sie die Hinzufügung des + Zeichens. Alternativ können Sie das Flag -f verwenden, um die Remote-Aktualisierung zu erzwingen, wie in
$ git push -f ssh://yourserver.com/~you/proj.git master
Normalerweise, wenn ein Branch-Kopf in einem öffentlichen Repository geändert wird, wird er so geändert, dass er auf einen Nachfolger des Commits zeigt, auf den er zuvor gezeigt hat. Durch das Erzwingen eines Pushes in dieser Situation brechen Sie diese Konvention. (Siehe Probleme beim Umschreiben der Historie.)
Dennoch ist dies eine gängige Praxis für Leute, die eine einfache Möglichkeit benötigen, eine in Arbeit befindliche Patch-Serie zu veröffentlichen, und es ist ein akzeptabler Kompromiss, solange Sie andere Entwickler davor warnen, dass Sie diesen Branch auf diese Weise verwalten möchten.
Es ist auch möglich, dass ein Push auf diese Weise fehlschlägt, wenn andere Personen das Recht haben, in dasselbe Repository zu pushen. In diesem Fall besteht die richtige Lösung darin, den Push erneut zu versuchen, nachdem Sie zuerst Ihre Arbeit aktualisiert haben: entweder durch einen Pull oder durch einen Fetch gefolgt von einem Rebase; siehe den nächsten Abschnitt und gitcvs-migration[7] für weitere Informationen.
Ein gemeinsames Repository einrichten
Eine weitere Möglichkeit der Zusammenarbeit ist die Verwendung eines Modells, das dem in CVS üblicherweise verwendeten ähnelt, bei dem mehrere Entwickler mit besonderen Rechten alle in ein einzelnes gemeinsames Repository pushen und daraus ziehen. Anleitungen dazu finden Sie in gitcvs-migration[7].
Obwohl es nichts Falsches an Git's Unterstützung für gemeinsame Repositories gibt, wird dieser Betriebsmodus generell nicht empfohlen, einfach weil der von Git unterstützte Kollaborationsmodus – durch den Austausch von Patches und das Ziehen aus öffentlichen Repositories – so viele Vorteile gegenüber dem zentralen gemeinsamen Repository hat
-
Git's Fähigkeit, Patches schnell zu importieren und zusammenzuführen, ermöglicht es einem einzelnen Maintainer, eingehende Änderungen auch bei sehr hohen Raten zu verarbeiten. Und wenn das zu viel wird, bietet
gitpulleine einfache Möglichkeit für diesen Maintainer, diese Aufgabe an andere Maintainer zu delegieren, während er weiterhin die optionale Überprüfung eingehender Änderungen zulässt. -
Da jedes Repository eines Entwicklers eine vollständige Kopie der Projektgeschichte auf dem neuesten Stand hat, ist kein Repository besonders, und es ist trivial für einen anderen Entwickler, die Wartung eines Projekts zu übernehmen, entweder durch gegenseitige Vereinbarung oder weil ein Maintainer nicht mehr reagiert oder schwierig zu handhaben ist.
-
Das Fehlen einer zentralen Gruppe von "Committern" bedeutet, dass weniger formelle Entscheidungen darüber getroffen werden müssen, wer "drin" und wer "draußen" ist.
Web-Browsing eines Repositorys ermöglichen
Das cgi-Skript gitweb bietet Benutzern eine einfache Möglichkeit, die Revisionen, Dateiinhalte und Protokolle Ihres Projekts zu durchsuchen, ohne Git installieren zu müssen. Funktionen wie RSS/Atom-Feeds und Blame/Annotation-Details können optional aktiviert werden.
Der Befehl git-instaweb[1] bietet eine einfache Möglichkeit, mit dem Durchsuchen des Repositorys mithilfe von gitweb zu beginnen. Der Standardserver bei der Verwendung von instaweb ist lighttpd.
Anweisungen zur Einrichtung einer dauerhaften Installation mit einem CGI- oder Perl-fähigen Server finden Sie in der Datei gitweb/INSTALL im Git-Quellbaum und in gitweb[1].
Wie man ein Git-Repository mit minimaler Historie erhält
Ein Shallow Clone mit seiner verkürzten Historie ist nützlich, wenn man nur an der jüngsten Historie eines Projekts interessiert ist und die vollständige Historie vom Upstream teuer ist.
Ein Shallow Clone wird durch Angabe des Schalters --depth von git-clone[1] erstellt. Die Tiefe kann später mit dem Schalter --depth von git-fetch[1] geändert oder die vollständige Historie mit --unshallow wiederhergestellt werden.
Das Mergen innerhalb eines Shallow Clone funktioniert, solange eine Merge-Basis in der jüngsten Historie vorhanden ist. Andernfalls ist es wie das Mergen von nicht verwandten Historien und kann zu riesigen Konflikten führen. Diese Einschränkung kann ein solches Repository für die Verwendung in Merge-basierten Workflows ungeeignet machen.
Beispiele
Pflege von Themen-Branches für einen Linux-Subsystem-Maintainer
Dies beschreibt, wie Tony Luck Git in seiner Rolle als Maintainer der IA64-Architektur für den Linux-Kernel verwendet.
Er verwendet zwei öffentliche Branches
-
Ein "Test"-Baum, in den Patches zunächst gelegt werden, damit sie beim Integrieren in andere laufende Entwicklungen etwas Aufmerksamkeit erhalten können. Dieser Baum steht Andrew zur Verfügung, um ihn nach Belieben in -mm zu ziehen.
-
Ein "Release"-Baum, in den getestete Patches zur endgültigen sanity-Überprüfung verschoben werden und als Vehikel, um sie an Linus weiterzuleiten (indem er ihm eine "Bitte ziehen"-Anfrage sendet).
Er verwendet auch eine Reihe von temporären Branches ("Themen-Branches"), die jeweils eine logische Gruppierung von Patches enthalten.
Um dies einzurichten, erstellen Sie zunächst Ihren Arbeitsbaum, indem Sie Linus' öffentlichen Baum klonen
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git work $ cd work
Linus' Baum wird im Remote-Tracking-Branch namens origin/master gespeichert und kann mit git-fetch[1] aktualisiert werden; Sie können andere öffentliche Bäume verfolgen, indem Sie git-remote[1] verwenden, um ein "Remote" einzurichten, und git-fetch[1], um sie aktuell zu halten; siehe Repositories und Branches.
Erstellen Sie nun die Branches, in denen Sie arbeiten werden; diese beginnen am aktuellen Tip des origin/master-Branches und sollten so eingerichtet werden (mit der Option --track von git-branch[1]), dass sie standardmäßig Änderungen von Linus zusammenführen.
$ git branch --track test origin/master $ git branch --track release origin/master
Diese können einfach mit git-pull[1] aktuell gehalten werden.
$ git switch test && git pull $ git switch release && git pull
Wichtiger Hinweis! Wenn Sie lokale Änderungen in diesen Branches haben, erstellt dieser Merge ein Commit-Objekt in der Historie (ohne lokale Änderungen wird Git einfach einen "Fast-Forward"-Merge durchführen). Viele Leute mögen den "Lärm", den dies in der Linux-Historie verursacht, daher sollten Sie dies im release-Branch nicht leichtfertig tun, da diese lärmenden Commits Teil der permanenten Historie werden, wenn Sie Linus bitten, von dem Release-Branch zu ziehen.
Einige Konfigurationsvariablen (siehe git-config[1]) können es einfach machen, beide Branches in Ihren öffentlichen Baum zu pushen. (Siehe Ein öffentliches Repository einrichten.)
$ cat >> .git/config <<EOF [remote "mytree"] url = master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux.git push = release push = test EOF
Dann können Sie sowohl die Test- als auch die Release-Bäume mit git-push[1] pushen
$ git push mytree
oder nur einen der Test- und Release-Branches mit
$ git push mytree test
oder
$ git push mytree release
Nun werden einige Patches von der Community angewendet. Denken Sie sich einen kurzen, prägnanten Namen für einen Branch aus, der diesen Patch (oder eine Gruppe verwandter Patches) enthalten soll, und erstellen Sie einen neuen Branch von einem aktuellen stabilen Tag von Linus' Branch. Die Auswahl einer stabilen Basis für Ihren Branch wird: 1) Ihnen helfen: indem Sie die Aufnahme von nicht verwandten und möglicherweise leicht getesteten Änderungen vermeiden. 2) zukünftigen Bug-Jägern helfen, die git bisect verwenden, um Probleme zu finden.
$ git switch -c speed-up-spinlocks v2.6.35
Wenden Sie nun den/die Patch(es) an, führen Sie einige Tests durch und committen Sie die/den Änderung(en). Wenn der Patch eine Multi-Part-Serie ist, sollten Sie jeden als separaten Commit auf diesem Branch anwenden.
$ ... patch ... test ... commit [ ... patch ... test ... commit ]*
Wenn Sie mit dem Zustand dieser Änderung zufrieden sind, können Sie sie in den "Test"-Branch mergen, um sie öffentlich zu machen
$ git switch test && git merge speed-up-spinlocks
Es ist unwahrscheinlich, dass Sie hier Konflikte haben... aber Sie könnten, wenn Sie eine Weile an diesem Schritt gearbeitet haben und auch neue Versionen von Upstream gezogen haben.
Einige Zeit später, nachdem genug Zeit vergangen ist und die Tests abgeschlossen sind, können Sie denselben Branch in den release-Baum ziehen, bereit für den Upstream. Hier sehen Sie den Wert, jeden Patch (oder jede Patch-Serie) in seinem eigenen Branch zu halten. Das bedeutet, dass die Patches in beliebiger Reihenfolge in den release-Baum verschoben werden können.
$ git switch release && git merge speed-up-spinlocks
Nach einer Weile werden Sie eine Reihe von Branches haben, und trotz der gut gewählten Namen, die Sie für jeden von ihnen gewählt haben, vergessen Sie vielleicht, wofür sie sind oder in welchem Zustand sie sich befinden. Um eine Erinnerung zu erhalten, welche Änderungen in einem bestimmten Branch enthalten sind, verwenden Sie
$ git log linux..branchname | git shortlog
Um zu sehen, ob er bereits in die Test- oder Release-Branches gemerged wurde, verwenden Sie
$ git log test..branchname
oder
$ git log release..branchname
(Wenn dieser Branch noch nicht gemerged wurde, sehen Sie einige Protokolleinträge. Wenn er gemerged wurde, gibt es keine Ausgabe.)
Sobald ein Patch den großen Kreislauf durchlaufen hat (vom Test zum Release, dann von Linus gezogen und schließlich zurück in Ihren lokalen origin/master-Branch), wird der Branch für diese Änderung nicht mehr benötigt. Sie erkennen dies, wenn die Ausgabe von
$ git log origin..branchname
leer ist. An diesem Punkt kann der Branch gelöscht werden
$ git branch -d branchname
Einige Änderungen sind so trivial, dass es nicht notwendig ist, einen separaten Branch zu erstellen und dann in jeden der Test- und Release-Branches zu mergen. Für diese Änderungen wenden Sie sie direkt auf den release-Branch an und mergen Sie diesen dann in den test-Branch.
Nachdem Sie Ihre Arbeit nach mytree gepusht haben, können Sie git-request-pull[1] verwenden, um eine "Bitte ziehen"-Anforderungsnachricht für Linus vorzubereiten.
$ git push mytree $ git request-pull origin mytree release
Hier sind einige Skripte, die all dies noch weiter vereinfachen.
==== update script ==== # Update a branch in my Git tree. If the branch to be updated # is origin, then pull from kernel.org. Otherwise merge # origin/master branch into test|release branch case "$1" in test|release) git checkout $1 && git pull . origin ;; origin) before=$(git rev-parse refs/remotes/origin/master) git fetch origin after=$(git rev-parse refs/remotes/origin/master) if [ $before != $after ] then git log $before..$after | git shortlog fi ;; *) echo "usage: $0 origin|test|release" 1>&2 exit 1 ;; esac
==== merge script ====
# Merge a branch into either the test or release branch
pname=$0
usage()
{
echo "usage: $pname branch test|release" 1>&2
exit 1
}
git show-ref -q --verify -- refs/heads/"$1" || {
echo "Can't see branch <$1>" 1>&2
usage
}
case "$2" in
test|release)
if [ $(git log $2..$1 | wc -c) -eq 0 ]
then
echo $1 already merged into $2 1>&2
exit 1
fi
git checkout $2 && git pull . $1
;;
*)
usage
;;
esac
==== status script ====
# report on status of my ia64 Git tree
gb=$(tput setab 2)
rb=$(tput setab 1)
restore=$(tput setab 9)
if [ `git rev-list test..release | wc -c` -gt 0 ]
then
echo $rb Warning: commits in release that are not in test $restore
git log test..release
fi
for branch in `git show-ref --heads | sed 's|^.*/||'`
do
if [ $branch = test -o $branch = release ]
then
continue
fi
echo -n $gb ======= $branch ====== $restore " "
status=
for ref in test release origin/master
do
if [ `git rev-list $ref..$branch | wc -c` -gt 0 ]
then
status=$status${ref:0:1}
fi
done
case $status in
trl)
echo $rb Need to pull into test $restore
;;
rl)
echo "In test"
;;
l)
echo "Waiting for linus"
;;
"")
echo $rb All done $restore
;;
*)
echo $rb "<$status>" $restore
;;
esac
git log origin/master..$branch | git shortlog
done
Historie umschreiben und Patch-Serien pflegen
Normalerweise werden Commits nur zu einem Projekt hinzugefügt, nie weggenommen oder ersetzt. Git ist mit dieser Annahme konzipiert, und die Verletzung dieser Regel führt dazu, dass Git's Merge-Mechanismen (zum Beispiel) das Falsche tun.
Es gibt jedoch eine Situation, in der es nützlich sein kann, diese Annahme zu verletzen.
Die perfekte Patch-Serie erstellen
Angenommen, Sie sind ein Mitwirkender an einem großen Projekt und möchten eine komplizierte Funktion hinzufügen und sie den anderen Entwicklern so präsentieren, dass sie Ihre Änderungen leicht lesen, verifizieren und verstehen können, warum Sie jede Änderung vorgenommen haben.
Wenn Sie alle Ihre Änderungen als einzelnen Patch (oder Commit) präsentieren, können sie feststellen, dass dies zu viel auf einmal zum Verdauen ist.
Wenn Sie ihnen die gesamte Historie Ihrer Arbeit präsentieren, komplett mit Fehlern, Korrekturen und Sackgassen, können sie überwältigt werden.
Das Ideal ist daher normalerweise, eine Reihe von Patches zu erstellen, sodass
-
Jeder Patch kann der Reihe nach angewendet werden.
-
Jeder Patch enthält eine einzelne logische Änderung zusammen mit einer Nachricht, die die Änderung erklärt.
-
Kein Patch führt eine Regression ein: Nach dem Anwenden eines beliebigen Anfangsteils der Serie kompiliert und funktioniert das resultierende Projekt immer noch und hat keine Fehler, die es vorher nicht hatte.
-
Die vollständige Serie liefert dasselbe Endergebnis wie Ihr eigener (wahrscheinlich viel unordentlicherer!) Entwicklungsprozess.
Wir werden einige Werkzeuge vorstellen, die Ihnen dabei helfen können, erklären, wie sie zu verwenden sind, und dann einige der Probleme erläutern, die auftreten können, weil Sie die Historie umschreiben.
Eine Patch-Serie mit git rebase aktuell halten
Angenommen, Sie erstellen einen Branch mywork auf einem Remote-Tracking-Branch origin und erstellen einige Commits darauf
$ git switch -c mywork origin $ vi file.txt $ git commit $ vi otherfile.txt $ git commit ...
Sie haben keine Merges in mywork durchgeführt, sodass es nur eine einfache lineare Sequenz von Patches über origin ist
o--o--O <-- origin
\
a--b--c <-- mywork
Im Upstream-Projekt wurde weitere interessante Arbeit geleistet und origin hat sich weiterentwickelt
o--o--O--o--o--o <-- origin
\
a--b--c <-- mywork
An diesem Punkt könnten Sie pull verwenden, um Ihre Änderungen zurückzumergen; das Ergebnis würde einen neuen Merge-Commit erstellen, wie hier
o--o--O--o--o--o <-- origin
\ \
a--b--c--m <-- mywork
Wenn Sie jedoch die Historie in mywork als einfache Serie von Commits ohne Merges beibehalten möchten, können Sie stattdessen git-rebase[1] verwenden
$ git switch mywork $ git rebase origin
Dies entfernt jeden Ihrer Commits aus mywork, speichert sie vorübergehend als Patches (in einem Verzeichnis namens .git/rebase-apply), aktualisiert mywork, sodass es auf die neueste Version von origin zeigt, und wendet dann jeden der gespeicherten Patches auf das neue mywork an. Das Ergebnis wird so aussehen
o--o--O--o--o--o <-- origin \ a'--b'--c' <-- mywork
Dabei kann es zu Konflikten kommen. In diesem Fall stoppt es und erlaubt Ihnen, die Konflikte zu beheben; nach der Behebung der Konflikte verwenden Sie git add, um den Index mit diesen Inhalten zu aktualisieren, und führen Sie dann anstelle von git commit einfach aus
$ git rebase --continue
und Git wird fortfahren, die restlichen Patches anzuwenden.
Sie können jederzeit die Option --abort verwenden, um diesen Vorgang abzubrechen und mywork in den Zustand zurückzuversetzen, den es vor Beginn des Rebasing hatte.
$ git rebase --abort
Wenn Sie eine Reihe von Commits in einem Branch neu anordnen oder bearbeiten müssen, kann es einfacher sein, git rebase -i zu verwenden, das es Ihnen ermöglicht, Commits neu anzuordnen und zu squashen sowie sie für die individuelle Bearbeitung während des Rebasing zu markieren. Weitere Details finden Sie unter Interaktives Rebasen und Alternativen unter Patch-Serien neu anordnen oder auswählen.
Einen einzelnen Commit umschreiben
Wie wir in Einen Fehler durch Umschreiben der Historie beheben gesehen haben, können Sie den letzten Commit mit
$ git commit --amend
ersetzen, der den alten Commit durch einen neuen Commit ersetzt, der Ihre Änderungen enthält, und Ihnen die Möglichkeit gibt, zuerst die alte Commit-Nachricht zu bearbeiten. Dies ist nützlich, um Tippfehler in Ihrem letzten Commit zu beheben oder den Patch-Inhalt eines schlecht gestagten Commits anzupassen.
Wenn Sie Commits tiefer in Ihrer Historie ändern müssen, können Sie die edit-Anweisung von interactive rebase verwenden.
Patch-Serien neu anordnen oder auswählen
Manchmal möchten Sie einen Commit tiefer in Ihrer Historie bearbeiten. Ein Ansatz ist die Verwendung von git format-patch, um eine Reihe von Patches zu erstellen und dann den Zustand auf den Zustand vor den Patches zurückzusetzen.
$ git format-patch origin $ git reset --hard origin
Modifizieren, neu anordnen oder eliminieren Sie dann die Patches nach Bedarf, bevor Sie sie erneut mit git-am[1] anwenden.
$ git am *.patch
Interaktives Rebasen
Sie können auch eine Patch-Serie mit einem interaktiven Rebase bearbeiten. Dies ist dasselbe wie das Neuanordnen einer Patch-Serie mit format-patch, also verwenden Sie die Oberfläche, die Ihnen am besten gefällt.
Rebasen Sie Ihren aktuellen HEAD auf den letzten Commit, den Sie unverändert beibehalten möchten. Wenn Sie zum Beispiel die letzten 5 Commits neu anordnen möchten, verwenden Sie
$ git rebase -i HEAD~5
Dies öffnet Ihren Editor mit einer Liste von Schritten, die zum Ausführen Ihres Rebase erforderlich sind.
pick deadbee The oneline of this commit pick fa1afe1 The oneline of the next commit ... # Rebase c0ffeee..deadbee onto c0ffeee # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # # However, if you remove everything, the rebase will be aborted. # # Note that empty commits are commented out
Wie in den Kommentaren erklärt, können Sie Commits neu anordnen, sie zusammenführen, Commit-Nachrichten bearbeiten usw., indem Sie die Liste bearbeiten. Sobald Sie zufrieden sind, speichern Sie die Liste, schließen Sie Ihren Editor und der Rebase beginnt.
Der Rebase stoppt, wo pick durch edit ersetzt wurde oder wenn ein Schritt in der Liste mechanische Konflikte nicht lösen kann und Ihre Hilfe benötigt. Wenn Sie mit der Bearbeitung und/oder der Behebung von Konflikten fertig sind, können Sie mit git rebase --continue fortfahren. Wenn Sie entscheiden, dass die Dinge zu kompliziert werden, können Sie jederzeit mit git rebase --abort aussteigen. Selbst nach Abschluss des Rebase können Sie den ursprünglichen Branch mit dem Reflog wiederherstellen.
Eine detailliertere Erörterung des Verfahrens und zusätzliche Tipps finden Sie im Abschnitt "INTERACTIVE MODE" von git-rebase[1].
Andere Werkzeuge
Es gibt zahlreiche andere Werkzeuge wie StGit, die zur Pflege einer Patch-Serie existieren. Diese liegen außerhalb des Rahmens dieses Handbuchs.
Probleme beim Umschreiben der Historie
Das Hauptproblem beim Umschreiben der Historie eines Branches hat mit dem Mergen zu tun. Angenommen, jemand holt Ihren Branch und mergt ihn in seinen eigenen Branch, mit einem Ergebnis wie diesem
o--o--O--o--o--o <-- origin
\ \
t--t--t--m <-- their branch:
Angenommen, Sie ändern dann die letzten drei Commits
o--o--o <-- new head of origin / o--o--O--o--o--o <-- old head of origin
Wenn wir all diese Historie zusammen in einem Repository untersuchen würden, würde sie so aussehen
o--o--o <-- new head of origin
/
o--o--O--o--o--o <-- old head of origin
\ \
t--t--t--m <-- their branch:
Git hat keine Möglichkeit zu wissen, dass der neue Kopf eine aktualisierte Version des alten Kopfes ist; es behandelt diese Situation genauso, als ob zwei Entwickler die Arbeit an den alten und neuen Köpfen parallel unabhängig voneinander durchgeführt hätten. An diesem Punkt wird Git, wenn jemand versucht, den neuen Kopf in seinen Branch zu mergen, versuchen, die beiden (alten und neuen) Entwicklungsstränge zusammenzuführen, anstatt zu versuchen, den alten durch den neuen zu ersetzen. Die Ergebnisse werden wahrscheinlich unerwartet sein.
Sie können sich dennoch dafür entscheiden, Branches zu veröffentlichen, deren Historie umgeschrieben wurde, und es kann für andere nützlich sein, diese Branches abrufen zu können, um sie zu untersuchen oder zu testen, aber sie sollten nicht versuchen, solche Branches in ihre eigene Arbeit zu ziehen.
Für echte verteilte Entwicklung, die ordnungsgemäßes Mergen unterstützt, sollten veröffentlichte Branches niemals umgeschrieben werden.
Warum das Bisecten von Merge-Commits schwieriger sein kann als das Bisecten von linearer Historie
Der Befehl git-bisect[1] behandelt korrekt eine Historie, die Merge-Commits enthält. Wenn der gefundene Commit jedoch ein Merge-Commit ist, muss der Benutzer möglicherweise mehr als üblich arbeiten, um herauszufinden, warum dieser Commit ein Problem eingeführt hat.
Stellen Sie sich diese Historie vor
---Z---o---X---...---o---A---C---D
\ /
o---o---Y---...---o---B
Angenommen, auf der oberen Entwicklungslinie wird die Bedeutung einer der Funktionen, die bei Z existiert, in Commit X geändert. Die Commits von Z bis A ändern sowohl die Implementierung der Funktion als auch alle Aufrufe, die bei Z existieren, sowie neu hinzugefügte Aufrufe, um sie konsistent zu machen. Bei A gibt es keinen Fehler.
Angenommen, in der Zwischenzeit wurde auf der unteren Entwicklungslinie bei Commit Y ein neuer Aufruf für diese Funktion hinzugefügt. Die Commits von Z bis B gehen alle von den alten Semantiken der Funktion aus, und die Aufrufer und die aufgerufene Funktion sind untereinander konsistent. Bei B gibt es auch keine Fehler.
Angenommen, weiter, dass die beiden Entwicklungslinien bei C sauber zusammengeführt werden, sodass keine Konfliktlösung erforderlich ist.
Dennoch ist der Code bei C fehlerhaft, da die auf der unteren Entwicklungslinie hinzugefügten Aufrufer nicht in die auf der oberen Entwicklungslinie eingeführten neuen Semantiken umgewandelt wurden. Wenn Sie also nur wissen, dass D schlecht ist, Z gut ist und git-bisect[1] C als Schuldigen identifiziert, wie finden Sie dann heraus, dass das Problem auf dieser Semantikänderung zurückzuführen ist?
Wenn das Ergebnis eines git bisect ein Nicht-Merge-Commit ist, sollten Sie normalerweise in der Lage sein, das Problem durch die Untersuchung dieses einzelnen Commits zu entdecken. Entwickler können dies einfach machen, indem sie ihre Änderungen in kleine, in sich geschlossene Commits aufteilen. Das hilft jedoch im obigen Fall nicht, da das Problem nicht aus der Untersuchung eines einzelnen Commits ersichtlich ist; stattdessen ist eine globale Sicht der Entwicklung erforderlich. Um die Sache noch schlimmer zu machen, kann die Semantikänderung in der problematischen Funktion nur ein kleiner Teil der Änderungen in der oberen Entwicklungslinie sein.
Wenn Sie stattdessen bei C gemerged hätten, anstatt die Historie zwischen Z und B auf A zu rebasen, hätten Sie diese lineare Historie erhalten
---Z---o---X--...---o---A---o---o---Y*--...---o---B*--D*
Bisecting zwischen Z und D* würde einen einzelnen Schuldigen-Commit Y* treffen, und das Verständnis, warum Y* fehlerhaft war, wäre wahrscheinlich einfacher.
Teilweise aus diesem Grund halten viele erfahrene Git-Benutzer, selbst wenn sie an einem ansonsten Merge-lastigen Projekt arbeiten, die Historie linear, indem sie vor der Veröffentlichung gegen die neueste Upstream-Version rebasen.
Fortgeschrittene Branch-Verwaltung
Einzelne Branches abrufen
Anstatt git-remote[1] zu verwenden, können Sie auch nur einen Branch auf einmal aktualisieren und ihn lokal unter einem beliebigen Namen speichern.
$ git fetch origin todo:my-todo-work
Das erste Argument, origin, weist Git lediglich an, aus dem Repository abzurufen, das Sie ursprünglich geklont haben. Das zweite Argument weist Git an, den Branch namens todo aus dem Remote-Repository abzurufen und ihn lokal unter dem Namen refs/heads/my-todo-work zu speichern.
Sie können auch Branches aus anderen Repositories abrufen; also
$ git fetch git://example.com/proj.git master:example-master
erstellt einen neuen Branch namens example-master und speichert darin den Branch namens master aus dem Repository unter der angegebenen URL. Wenn Sie bereits einen Branch namens example-master haben, wird versucht, ihn zum Commit zu Fast-Forwarden, der durch den master-Branch von example.com gegeben ist. Im Detail
git fetch und Fast-Forwards
Im vorherigen Beispiel prüft git fetch beim Aktualisieren eines vorhandenen Branches, ob der neueste Commit auf dem Remote-Branch ein Nachfahre des neuesten Commits auf Ihrer Kopie des Branches ist, bevor er Ihre Kopie des Branches aktualisiert, um auf den neuen Commit zu zeigen. Git nennt diesen Vorgang Fast-Forward.
Ein Fast-Forward sieht etwa so aus
o--o--o--o <-- old head of the branch
\
o--o--o <-- new head of the branch
In einigen Fällen ist es möglich, dass der neue Kopf tatsächlich **nicht** ein Nachfahre des alten Kopfes ist. Zum Beispiel hat der Entwickler möglicherweise erkannt, dass ein schwerwiegender Fehler gemacht wurde, und beschlossen, zurückzugehen, was zu einer Situation wie dieser führt
o--o--o--o--a--b <-- old head of the branch
\
o--o--o <-- new head of the branch
In diesem Fall schlägt git fetch fehl und gibt eine Warnung aus.
In diesem Fall können Sie Git immer noch zwingen, auf den neuen Kopf zu aktualisieren, wie im folgenden Abschnitt beschrieben. Beachten Sie jedoch, dass dies in der obigen Situation bedeuten kann, dass die Commits a und b verloren gehen, es sei denn, Sie haben bereits eine eigene Referenz darauf erstellt.
Git fetch zwingen, Nicht-Fast-Forward-Updates durchzuführen
Wenn git fetch fehlschlägt, weil der neue Kopf eines Branches kein Nachfahre des alten Kopfes ist, können Sie das Update mit
$ git fetch git://example.com/proj.git +master:refs/remotes/example/master
erzwingen. Beachten Sie die Hinzufügung des + Zeichens. Alternativ können Sie das Flag -f verwenden, um Updates aller abgerufenen Branches zu erzwingen, wie in
$ git fetch -f origin
Seien Sie sich bewusst, dass Commits, auf die die alte Version von example/master gezeigt hat, verloren gehen können, wie wir im vorherigen Abschnitt gesehen haben.
Remote-Tracking-Branches konfigurieren
Wir haben oben gesehen, dass origin nur eine Abkürzung ist, um auf das Repository zu verweisen, von dem Sie ursprünglich geklont haben. Diese Informationen werden in Git-Konfigurationsvariablen gespeichert, die Sie mit git-config[1] einsehen können.
$ git config -l core.repositoryformatversion=0 core.filemode=true core.logallrefupdates=true remote.origin.url=git://git.kernel.org/pub/scm/git/git.git remote.origin.fetch=+refs/heads/*:refs/remotes/origin/* branch.master.remote=origin branch.master.merge=refs/heads/master
Wenn es andere Repositories gibt, die Sie ebenfalls häufig verwenden, können Sie ähnliche Konfigurationsoptionen erstellen, um Tipparbeit zu sparen; zum Beispiel:
$ git remote add example git://example.com/proj.git
fügt Folgendes zu .git/config hinzu
[remote "example"] url = git://example.com/proj.git fetch = +refs/heads/*:refs/remotes/example/*
Beachten Sie auch, dass die obige Konfiguration durch direktes Bearbeiten der Datei .git/config anstatt der Verwendung von git-remote[1] erfolgen kann.
Nach der Konfiguration des Remotes führen die folgenden drei Befehle dasselbe aus.
$ git fetch git://example.com/proj.git +refs/heads/*:refs/remotes/example/* $ git fetch example +refs/heads/*:refs/remotes/example/* $ git fetch example
Siehe git-config[1] für weitere Details zu den oben genannten Konfigurationsoptionen und git-fetch[1] für weitere Details zur Refspec-Syntax.
Git-Konzepte
Git basiert auf einer kleinen Anzahl einfacher, aber leistungsfähiger Ideen. Obwohl es möglich ist, Dinge zu erledigen, ohne sie zu verstehen, werden Sie Git viel intuitiver finden, wenn Sie es tun.
Wir beginnen mit den wichtigsten: der Objektdatenbank und dem Index.
Die Objektdatenbank
Wir haben bereits in Verstehen der Historie: Commits gesehen, dass alle Commits unter einem 40-stelligen "Objektnamen" gespeichert sind. Tatsächlich werden alle Informationen, die zur Darstellung der Historie eines Projekts erforderlich sind, in Objekten mit solchen Namen gespeichert. In jedem Fall wird der Name durch Berechnung des SHA-1-Hashs des Objektinhalts ermittelt. Der SHA-1-Hash ist eine kryptografische Hash-Funktion. Was das für uns bedeutet, ist, dass es unmöglich ist, zwei verschiedene Objekte mit demselben Namen zu finden. Dies hat eine Reihe von Vorteilen; unter anderem
-
Git kann schnell feststellen, ob zwei Objekte identisch sind oder nicht, einfach durch Vergleichen der Namen.
-
Da Objektnamen in jedem Repository auf die gleiche Weise berechnet werden, wird derselbe Inhalt, der in zwei Repositories gespeichert ist, immer unter demselben Namen gespeichert.
-
Git kann Fehler erkennen, wenn es ein Objekt liest, indem es prüft, ob der Name des Objekts immer noch der SHA-1-Hash seines Inhalts ist.
(Siehe Objektspeicherformat für die Details des Objektformats und der SHA-1-Berechnung.)
Es gibt vier verschiedene Arten von Objekten: "blob", "tree", "commit" und "tag".
-
Ein "blob"-Objekt wird verwendet, um Dateidaten zu speichern.
-
Ein "tree"-Objekt bindet ein oder mehrere "blob"-Objekte zu einer Verzeichnisstruktur zusammen. Darüber hinaus kann ein Baum-Objekt auf andere Baum-Objekte verweisen und so eine Verzeichnishierarchie erstellen.
-
Ein "commit"-Objekt bindet solche Verzeichnishierarchien zu einem gerichteten azyklischen Graphen von Revisionen zusammen – jeder Commit enthält den Objektnamen eines Baums, der die Verzeichnishierarchie zum Zeitpunkt des Commits bezeichnet. Darüber hinaus verweist ein Commit auf "Eltern"-Commit-Objekte, die die Historie beschreiben, wie wir zu dieser Verzeichnishierarchie gelangt sind.
-
Ein "tag"-Objekt identifiziert symbolisch andere Objekte und kann zu deren Signierung verwendet werden. Es enthält den Objektnamen und den Typ eines anderen Objekts, einen symbolischen Namen (natürlich!) und optional eine Signatur.
Die Objekttypen im Detail
Commit-Objekt
Das "commit"-Objekt verknüpft einen physischen Zustand eines Baums mit einer Beschreibung, wie wir dorthin gelangt sind und warum. Verwenden Sie die Option --pretty=raw von git-show[1] oder git-log[1], um Ihren bevorzugten Commit zu untersuchen.
$ git show -s --pretty=raw 2be7fcb476
commit 2be7fcb4764f2dbcee52635b91fedb1b3dcf7ab4
tree fb3a8bdd0ceddd019615af4d57a53f43d8cee2bf
parent 257a84d9d02e90447b149af58b271c19405edb6a
author Dave Watson <dwatson@mimvista.com> 1187576872 -0400
committer Junio C Hamano <gitster@pobox.com> 1187591163 -0700
Fix misspelling of 'suppress' in docs
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Wie Sie sehen, wird ein Commit definiert durch
-
ein Baum: Der SHA-1-Name eines Baum-Objekts (wie unten definiert), das den Inhalt eines Verzeichnisses zu einem bestimmten Zeitpunkt darstellt.
-
Elternteil(e): Der SHA-1-Name(n) von einigen Commits, die die unmittelbar vorhergehenden Schritte in der Historie des Projekts darstellen. Das obige Beispiel hat einen Elternteil; Merge-Commits können mehr als einen haben. Ein Commit ohne Elternteil wird als "Root"-Commit bezeichnet und stellt die initiale Revision eines Projekts dar. Jedes Projekt muss mindestens einen Root haben. Ein Projekt kann auch mehrere Roots haben, obwohl dies nicht üblich (oder unbedingt eine gute Idee) ist.
-
ein Autor: Der Name der Person, die für diese Änderung verantwortlich ist, zusammen mit ihrem Datum.
-
ein Committer: Der Name der Person, die den Commit tatsächlich erstellt hat, mit dem Datum, an dem er gemacht wurde. Dies kann vom Autor abweichen, z. B. wenn der Autor jemand war, der einen Patch geschrieben und ihn an die Person gemailt hat, die ihn zur Erstellung des Commits verwendet hat.
-
ein Kommentar, der diesen Commit beschreibt.
Beachten Sie, dass ein Commit selbst keine Informationen darüber enthält, was sich tatsächlich geändert hat; alle Änderungen werden berechnet, indem der Inhalt des vom Commit referenzierten Baums mit den Bäumen verglichen wird, die mit seinen Eltern verbunden sind. Insbesondere versucht Git nicht, Dateiumbenennungen explizit zu erfassen, obwohl es Fälle identifizieren kann, in denen die Existenz derselben Dateidaten an wechselnden Pfaden auf eine Umbenennung hindeutet. (Siehe zum Beispiel die Option -M von git-diff[1]).
Ein Commit wird normalerweise von git-commit[1] erstellt, der einen Commit erstellt, dessen Elternteil normalerweise der aktuelle HEAD ist, und dessen Baum aus dem Inhalt stammt, der derzeit im Index gespeichert ist.
Baum-Objekt
Der immer vielseitige Befehl git-show[1] kann auch verwendet werden, um Baum-Objekte zu untersuchen, aber git-ls-tree[1] gibt Ihnen mehr Details.
$ git ls-tree fb3a8bdd0ce 100644 blob 63c918c667fa005ff12ad89437f2fdc80926e21c .gitignore 100644 blob 5529b198e8d14decbe4ad99db3f7fb632de0439d .mailmap 100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3 COPYING 040000 tree 2fb783e477100ce076f6bf57e4a6f026013dc745 Documentation 100755 blob 3c0032cec592a765692234f1cba47dfdcc3a9200 GIT-VERSION-GEN 100644 blob 289b046a443c0647624607d471289b2c7dcd470b INSTALL 100644 blob 4eb463797adc693dc168b926b6932ff53f17d0b1 Makefile 100644 blob 548142c327a6790ff8821d67c2ee1eff7a656b52 README ...
Wie Sie sehen, enthält ein Baum-Objekt eine Liste von Einträgen, jeweils mit einem Modus, einem Objekttyp, einem SHA-1-Namen und einem Namen, sortiert nach Namen. Es repräsentiert den Inhalt eines einzelnen Verzeichnisbaums.
Der Objekttyp kann ein Blob sein, der den Inhalt einer Datei repräsentiert, oder ein anderer Baum, der den Inhalt eines Unterverzeichnisses repräsentiert. Da Bäume und Blobs, wie alle anderen Objekte, durch den SHA-1-Hash ihres Inhalts benannt werden, haben zwei Bäume denselben SHA-1-Namen, wenn und nur wenn ihre Inhalte (einschließlich rekursiv der Inhalte aller Unterverzeichnisse) identisch sind. Dies ermöglicht es Git, die Unterschiede zwischen zwei verwandten Baum-Objekten schnell zu erkennen, da es alle Einträge mit identischen Objekt-Namen ignorieren kann.
(Hinweis: Bei Submodulen können Bäume auch Commits als Einträge enthalten. Siehe Submodules für die Dokumentation.)
Beachten Sie, dass die Dateien alle den Modus 644 oder 755 haben: Git achtet tatsächlich nur auf das ausführbare Bit.
Blob-Objekt
Sie können git-show[1] verwenden, um den Inhalt eines Blobs zu untersuchen; nehmen wir zum Beispiel den Blob im Eintrag für COPYING aus dem obigen Baum.
$ git show 6ff87c4664 Note that the only valid version of the GPL as far as this project is concerned is _this_ particular version of the license (ie v2, not v2.2 or v3.x or whatever), unless explicitly otherwise stated. ...
Ein "blob"-Objekt ist nichts anderes als ein binärer Datenblock. Es verweist auf nichts anderes und hat keine Attribute irgendeiner Art.
Da der Blob vollständig durch seine Daten definiert ist, teilen sich zwei Dateien in einem Verzeichnisbaum (oder in mehreren verschiedenen Versionen des Repositorys) denselben Blob-Objekt, wenn sie denselben Inhalt haben. Das Objekt ist völlig unabhängig von seinem Speicherort im Verzeichnisbaum, und das Umbenennen einer Datei ändert nicht das Objekt, mit dem die Datei verbunden ist.
Beachten Sie, dass jedes Baum- oder Blob-Objekt mit der Syntax <revision>:<path> von git-show[1] untersucht werden kann. Dies kann manchmal nützlich sein, um den Inhalt eines Baums zu durchsuchen, der gerade nicht ausgecheckt ist.
Vertrauen
Wenn Sie den SHA-1-Namen eines Blobs von einer Quelle und dessen Inhalt von einer anderen (möglicherweise nicht vertrauenswürdigen) Quelle erhalten, können Sie immer noch darauf vertrauen, dass dieser Inhalt korrekt ist, solange der SHA-1-Name übereinstimmt. Dies liegt daran, dass der SHA-1 so konzipiert ist, dass es unmöglich ist, unterschiedliche Inhalte zu finden, die denselben Hash ergeben.
Ebenso müssen Sie nur dem SHA-1-Namen eines übergeordneten Baumobjekts vertrauen, um dem Inhalt des gesamten Verzeichnisses zu vertrauen, auf das es verweist, und wenn Sie den SHA-1-Namen eines Commits von einer vertrauenswürdigen Quelle erhalten, können Sie leicht die gesamte Historie von Commits überprüfen, die über die Eltern dieses Commits erreichbar sind, sowie alle Inhalte der Bäume, auf die diese Commits verweisen.
Um also echtes Vertrauen in das System zu bringen, müssen Sie nur *einen* speziellen Hinweis digital signieren, der den Namen eines übergeordneten Commits enthält. Ihre digitale Signatur zeigt anderen, dass Sie diesem Commit vertrauen, und die Unveränderlichkeit der Commit-Historie sagt anderen, dass sie der gesamten Historie vertrauen können.
Mit anderen Worten, Sie können ein ganzes Archiv leicht validieren, indem Sie einfach eine einzige E-Mail versenden, die den Leuten den Namen (SHA-1-Hash) des übergeordneten Commits mitteilt und diese E-Mail mit etwas wie GPG/PGP digital signiert.
Um dies zu unterstützen, bietet Git auch das Tag-Objekt an...
Tag-Objekt
Ein Tag-Objekt enthält ein Objekt, den Objekttyp, den Tag-Namen, den Namen der Person ("Tagger"), die den Tag erstellt hat, und eine Nachricht, die eine Signatur enthalten kann, wie mit git-cat-file[1] zu sehen ist.
$ git cat-file tag v1.5.0 object 437b1b20df4b356c9342dac8d38849f24ef44f27 type commit tag v1.5.0 tagger Junio C Hamano <junkio@cox.net> 1171411200 +0000 GIT 1.5.0 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) iD8DBQBF0lGqwMbZpPMRm5oRAuRiAJ9ohBLd7s2kqjkKlq1qqC57SbnmzQCdG4ui nLE/L9aUXdWeTFPron96DLA= =2E+0 -----END PGP SIGNATURE-----
Siehe den Befehl git-tag[1], um zu erfahren, wie Tag-Objekte erstellt und verifiziert werden. (Beachten Sie, dass git-tag[1] auch zur Erstellung von "leichten Tags" verwendet werden kann, die überhaupt keine Tag-Objekte sind, sondern nur einfache Referenzen, deren Namen mit refs/tags/ beginnen).
Wie Git Objekte effizient speichert: Pack-Dateien
Neu erstellte Objekte werden zunächst in einer Datei gespeichert, die nach dem SHA-1-Hash des Objekts benannt ist (gespeichert in .git/objects).
Leider wird dieses System ineffizient, sobald ein Projekt viele Objekte hat. Versuchen Sie dies bei einem alten Projekt.
$ git count-objects 6930 objects, 47620 kilobytes
Die erste Zahl ist die Anzahl der Objekte, die in einzelnen Dateien aufbewahrt werden. Die zweite ist die Menge des Speicherplatzes, der von diesen "losen" Objekten belegt wird.
Sie können Speicherplatz sparen und Git beschleunigen, indem Sie diese losen Objekte in eine "Pack-Datei" verschieben, die eine Gruppe von Objekten in einem effizienten komprimierten Format speichert. Die Details zum Format von Pack-Dateien finden Sie in gitformat-pack[5].
Um die losen Objekte in ein Pack zu verschieben, führen Sie einfach git repack aus.
$ git repack Counting objects: 6020, done. Delta compression using up to 4 threads. Compressing objects: 100% (6020/6020), done. Writing objects: 100% (6020/6020), done. Total 6020 (delta 4070), reused 0 (delta 0)
Dies erstellt eine einzelne "Pack-Datei" in .git/objects/pack/, die alle derzeit entpackten Objekte enthält. Sie können dann
$ git prune
ausführen, um alle "losen" Objekte zu entfernen, die nun im Pack enthalten sind. Dies entfernt auch alle nicht referenzierten Objekte (die beispielsweise erstellt werden können, wenn Sie git reset verwenden, um einen Commit zu entfernen). Sie können überprüfen, ob die losen Objekte verschwunden sind, indem Sie das Verzeichnis .git/objects betrachten oder
$ git count-objects 0 objects, 0 kilobytes
ausführen. Obwohl die Objektdateien verschwunden sind, funktionieren alle Befehle, die sich auf diese Objekte beziehen, genau wie zuvor.
Der Befehl git-gc[1] führt das Packen, Bereinigen und mehr für Sie durch, ist also normalerweise der einzige Befehl auf hoher Ebene, den Sie benötigen.
Verwaiste Objekte
Der Befehl git-fsck[1] beklagt sich manchmal über verwaiste Objekte. Sie sind kein Problem.
Die häufigste Ursache für verwaiste Objekte ist, dass Sie einen Branch neu aufgesetzt haben oder von jemand anderem, der einen Branch neu aufgesetzt hat, gezogen haben – siehe Verlauf umschreiben und Patch-Serien pflegen. In diesem Fall existiert der alte Kopf des ursprünglichen Branches noch, ebenso wie alles, worauf er verwies. Der Branch-Zeiger selbst existiert nicht mehr, da Sie ihn durch einen anderen ersetzt haben.
Es gibt auch andere Situationen, die zu verwaisten Objekten führen. Zum Beispiel kann ein "verwaistes Blob" entstehen, weil Sie eine Datei mit git add hinzugefügt haben, dann aber, bevor Sie sie tatsächlich committen und zum Gesamtbild hinzufügen, etwas anderes in dieser Datei geändert und *diese aktualisierte* Sache committet haben – der ursprüngliche Zustand, den Sie hinzugefügt haben, wird nicht mehr von einem Commit oder Baum referenziert und ist somit ein verwaistes Blob-Objekt.
Ähnlich ist es, wenn die Merge-Strategie "ort" ausgeführt wird und feststellt, dass es Kreuz-Merges und damit mehr als eine Merge-Basis gibt (was ziemlich ungewöhnlich ist, aber vorkommt), wird sie einen temporären Zwischenbaum erstellen (oder sogar mehr, wenn Sie viele Kreuz-Merges und mehr als zwei Merge-Basen hatten) als temporäre interne Merge-Basis, und wieder, dies sind echte Objekte, aber das Endergebnis wird nicht darauf verweisen, so dass sie in Ihrem Repository "verwaist" enden.
Im Allgemeinen sind verwaiste Objekte kein Grund zur Sorge. Sie können sogar sehr nützlich sein: Wenn Sie etwas vermasseln, können die verwaisten Objekte die Möglichkeit sein, Ihren alten Baum wiederherzustellen (z. B. Sie haben einen Rebase durchgeführt und festgestellt, dass Sie ihn wirklich nicht wollten – Sie können sich die verwaisten Objekte ansehen und entscheiden, Ihren Kopf auf einen alten verwaisten Zustand zurückzusetzen).
Für Commits können Sie einfach
$ gitk <dangling-commit-sha-goes-here> --not --all
verwenden. Dies fragt nach der gesamten Historie, die von dem angegebenen Commit erreichbar ist, aber nicht von einem Branch, Tag oder einer anderen Referenz. Wenn Sie entscheiden, dass es etwas ist, das Sie möchten, können Sie jederzeit eine neue Referenz darauf erstellen, z. B.
$ git branch recovered-branch <dangling-commit-sha-goes-here>
Für Blobs und Bäume können Sie nicht dasselbe tun, aber Sie können sie trotzdem untersuchen. Sie können einfach
$ git show <dangling-blob/tree-sha-goes-here>
verwenden, um anzuzeigen, was der Inhalt des Blobs war (oder, für einen Baum, im Grunde, was das ls für dieses Verzeichnis war), und das kann Ihnen eine Vorstellung davon geben, welche Operation das verwaiste Objekt hinterlassen hat.
Normalerweise sind verwaiste Blobs und Bäume nicht sehr interessant. Sie sind fast immer das Ergebnis entweder einer halbfertigen Mergebase (der Blob wird oft sogar die Konfliktmarker eines Merges enthalten, wenn Sie handgeschriebene Konfliktmerges hatten) oder einfach nur, weil Sie einen git fetch mit ^C oder etwas Ähnlichem unterbrochen haben, wodurch *einige* der neuen Objekte in der Objektdatenbank verbleiben, aber eben verwaist und nutzlos.
Sobald Sie sicher sind, dass Sie an keinem verwaisten Zustand interessiert sind, können Sie einfach alle unerreichbaren Objekte bereinigen.
$ git prune
und sie werden verschwunden sein. (Sie sollten git prune nur in einem ruhigen Repository ausführen – es ist so etwas wie eine Dateisystem-fsck-Wiederherstellung: Sie wollen das nicht tun, während das Dateisystem gemountet ist. git prune ist so konzipiert, dass es in solchen Fällen von gleichzeitigen Zugriffen auf ein Repository keinen Schaden anrichtet, aber Sie könnten verwirrende oder beängstigende Meldungen erhalten).
Wiederherstellung bei Repository-Beschädigung
Git behandelt Daten, die ihm anvertraut werden, aus Designgründen mit Vorsicht. Selbst ohne Fehler in Git selbst ist es jedoch immer noch möglich, dass Hardware- oder Betriebssystemfehler Daten beschädigen.
Die erste Verteidigung gegen solche Probleme sind Backups. Sie können ein Git-Repository mit `clone` sichern, oder einfach mit `cp`, `tar` oder jedem anderen Sicherungsmechanismus.
Als letzte Möglichkeit können Sie nach beschädigten Objekten suchen und versuchen, sie manuell zu ersetzen. Sichern Sie Ihr Repository, bevor Sie dies versuchen, falls Sie dabei noch mehr beschädigen.
Wir gehen davon aus, dass das Problem ein einzelnes fehlendes oder beschädigtes Blob ist, was manchmal ein lösbares Problem ist. (Das Wiederherstellen fehlender Bäume und insbesondere Commits ist **viel** schwieriger).
Bevor Sie beginnen, verifizieren Sie, dass eine Beschädigung vorliegt, und ermitteln Sie mit git-fsck[1], wo sie sich befindet. Dies kann zeitaufwendig sein.
Nehmen wir an, die Ausgabe sieht so aus.
$ git fsck --full --no-dangling
broken link from tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
to blob 4b9458b3786228369c63936db65827de3cc06200
missing blob 4b9458b3786228369c63936db65827de3cc06200
Jetzt wissen Sie, dass das Blob 4b9458b fehlt und dass der Baum 2d9263c6 darauf verweist. Wenn Sie eine Kopie dieses fehlenden Blob-Objekts finden könnten, möglicherweise in einem anderen Repository, könnten Sie es in .git/objects/4b/9458b3... verschieben und wären fertig. Angenommen, Sie können das nicht. Sie können den Baum, der darauf verwies, immer noch mit git-ls-tree[1] untersuchen, was etwas wie Folgendes ausgeben könnte:
$ git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8 100644 blob 8d14531846b95bfa3564b58ccfb7913a034323b8 .gitignore 100644 blob ebf9bf84da0aab5ed944264a5db2a65fe3a3e883 .mailmap 100644 blob ca442d313d86dc67e0a2e5d584b465bd382cbf5c COPYING ... 100644 blob 4b9458b3786228369c63936db65827de3cc06200 myfile ...
Sie wissen also nun, dass das fehlende Blob die Daten für eine Datei namens myfile war. Und die Chancen stehen gut, dass Sie auch das Verzeichnis identifizieren können – sagen wir, es ist in somedirectory. Wenn Sie Glück haben, ist die fehlende Kopie dieselbe wie die Kopie, die Sie in Ihrem Arbeitsbaum unter somedirectory/myfile ausgecheckt haben. Sie können testen, ob das richtig ist, mit git-hash-object[1].
$ git hash-object -w somedirectory/myfile
Dies erstellt und speichert ein Blob-Objekt mit dem Inhalt von somedirectory/myfile und gibt den SHA-1 dieses Objekts aus. Wenn Sie extrem viel Glück haben, könnte es 4b9458b3786228369c63936db65827de3cc06200 sein, in diesem Fall haben Sie richtig geraten und die Beschädigung ist behoben!
Andernfalls benötigen Sie mehr Informationen. Wie stellen Sie fest, welche Version der Datei verloren gegangen ist?
Der einfachste Weg, dies zu tun, ist mit
$ git log --raw --all --full-history -- somedirectory/myfile
Da Sie eine Rohausgabe anfordern, erhalten Sie nun etwas wie
commit abc Author: Date: ... :100644 100644 4b9458b newsha M somedirectory/myfile commit xyz Author: Date: ... :100644 100644 oldsha 4b9458b M somedirectory/myfile
Dies sagt Ihnen, dass die unmittelbar folgende Version der Datei "newsha" war und dass die unmittelbar vorhergehende Version "oldsha" war. Sie kennen auch die Commit-Nachrichten, die mit der Änderung von oldsha zu 4b9458b und mit der Änderung von 4b9458b zu newsha einhergingen.
Wenn Sie kleine genug Änderungen committet haben, haben Sie jetzt vielleicht eine gute Chance, den Inhalt des Zwischenzustands 4b9458b zu rekonstruieren.
Wenn Sie das tun können, können Sie nun das fehlende Objekt mit
$ git hash-object -w <recreated-file>
neu erstellen und Ihr Repository ist wieder in Ordnung!
(Übrigens, Sie hätten die fsck ignorieren und mit einem
$ git log --raw --all
beginnen und einfach nach dem SHA des fehlenden Objekts (4b9458b) in der gesamten Sache suchen können. Es liegt an Ihnen – Git **hat** viele Informationen, es fehlt nur eine bestimmte Blob-Version.
Der Index
Der Index ist eine Binärdatei (allgemein in .git/index gespeichert), die eine sortierte Liste von Pfadnamen enthält, jeder mit Berechtigungen und dem SHA-1 eines Blob-Objekts; git-ls-files[1] kann Ihnen den Inhalt des Index anzeigen.
$ git ls-files --stage 100644 63c918c667fa005ff12ad89437f2fdc80926e21c 0 .gitignore 100644 5529b198e8d14decbe4ad99db3f7fb632de0439d 0 .mailmap 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0 COPYING 100644 a37b2152bd26be2c2289e1f57a292534a51a93c7 0 Documentation/.gitignore 100644 fbefe9a45b00a54b58d94d06eca48b03d40a50e0 0 Documentation/Makefile ... 100644 2511aef8d89ab52be5ec6a5e46236b4b6bcd07ea 0 xdiff/xtypes.h 100644 2ade97b2574a9f77e7ae4002a4e07a6a38e46d07 0 xdiff/xutils.c 100644 d5de8292e05e7c36c4b68857c1cf9855e3d2f70a 0 xdiff/xutils.h
Beachten Sie, dass Sie in älterer Dokumentation den Index manchmal als "current directory cache" oder einfach nur als "cache" bezeichnet sehen. Er hat drei wichtige Eigenschaften:
-
Der Index enthält alle Informationen, die zur Erzeugung eines einzelnen (eindeutig bestimmten) Baumobjekts erforderlich sind.
Zum Beispiel generiert der Befehl git-commit[1] dieses Baumobjekt aus dem Index, speichert es in der Objektdatenbank und verwendet es als Baumobjekt, das dem neuen Commit zugeordnet ist.
-
Der Index ermöglicht schnelle Vergleiche zwischen dem von ihm definierten Baumobjekt und dem Arbeitsbaum.
Dies geschieht durch die Speicherung einiger zusätzlicher Daten für jeden Eintrag (wie die letzte Änderungszeit). Diese Daten werden oben nicht angezeigt und nicht im erzeugten Baumobjekt gespeichert, können aber verwendet werden, um schnell festzustellen, welche Dateien im Arbeitsverzeichnis von dem im Index gespeicherten abweichen, und so Git davor zu bewahren, alle Daten aus solchen Dateien lesen zu müssen, um nach Änderungen zu suchen.
-
Er kann effizient Informationen über Merge-Konflikte zwischen verschiedenen Baumobjekten darstellen und ermöglicht es, jeden Pfadnamen mit ausreichenden Informationen über die beteiligten Bäume zu verknüpfen, sodass Sie einen Drei-Wege-Merge daraus erstellen können.
Wir haben in Hilfe bei der Konfliktlösung während eines Merges erhalten gesehen, dass der Index während eines Merges mehrere Versionen einer einzelnen Datei speichern kann (sogenannte "Stages"). Die dritte Spalte in der Ausgabe von git-ls-files[1] ist die Stufennummer und nimmt Werte ungleich 0 für Dateien mit Merge-Konflikten an.
Der Index ist somit eine Art temporärer Staging-Bereich, der mit einem Baum gefüllt wird, an dem Sie gerade arbeiten.
Wenn Sie den Index vollständig löschen, haben Sie im Allgemeinen keine Informationen verloren, solange Sie den Namen des Baumes haben, den er beschrieb.
Submodule
Große Projekte bestehen oft aus kleineren, in sich geschlossenen Modulen. Zum Beispiel würde der Quellcode-Baum einer Embedded-Linux-Distribution jede Software in der Distribution mit einigen lokalen Modifikationen enthalten; ein Filmplayer benötigt möglicherweise eine spezifische, nachweislich funktionierende Version einer Dekomprimierungsbibliothek; mehrere unabhängige Programme könnten die gleichen Build-Skripte gemeinsam nutzen.
Bei zentralisierten Revisionskontrollsystemen wird dies oft erreicht, indem jedes Modul in einem einzigen Repository enthalten ist. Entwickler können alle Module oder nur die Module auschecken, mit denen sie arbeiten müssen. Sie können sogar Dateien in mehreren Modulen in einem einzigen Commit ändern, während sie Dinge verschieben oder APIs und Übersetzungen aktualisieren.
Git erlaubt keine Teil-Checkouts, sodass die Duplizierung dieses Ansatzes in Git Entwickler zwingen würde, eine lokale Kopie von Modulen zu behalten, an denen sie nicht interessiert sind. Commits in einem riesigen Checkout wären langsamer als erwartet, da Git jedes Verzeichnis auf Änderungen scannen müsste. Wenn Module eine lange lokale Historie haben, würden Clones ewig dauern.
Auf der positiven Seite können verteilte Revisionskontrollsysteme viel besser mit externen Quellen integriert werden. In einem zentralisierten Modell wird ein einzelner beliebiger Snapshot des externen Projekts aus seiner eigenen Revisionskontrolle exportiert und dann in die lokale Revisionskontrolle auf einem Vendor-Branch importiert. Die gesamte Historie ist verborgen. Mit verteilter Revisionskontrolle können Sie die gesamte externe Historie klonen und die Entwicklung viel einfacher verfolgen und lokale Änderungen erneut mergen.
Die Submodul-Unterstützung von Git ermöglicht es einem Repository, als Unterverzeichnis eine Auscheckung eines externen Projekts zu enthalten. Submodule behalten ihre eigene Identität; die Submodul-Unterstützung speichert nur den Speicherort des Submodul-Repositorys und die Commit-ID, sodass andere Entwickler, die das enthaltende Projekt ("Superprojekt") klonen, alle Submodule in derselben Revision leicht klonen können. Teil-Checkouts des Superprojekts sind möglich: Sie können Git anweisen, keine, einige oder alle Submodule zu klonen.
Der Befehl git-submodule[1] ist seit Git 1.5.3 verfügbar. Benutzer mit Git 1.5.2 können die Submodul-Commits im Repository nachschlagen und manuell auschecken; frühere Versionen erkennen die Submodule überhaupt nicht.
Um zu sehen, wie die Submodul-Unterstützung funktioniert, erstellen Sie vier Beispiel-Repositories, die später als Submodul verwendet werden können.
$ mkdir ~/git $ cd ~/git $ for i in a b c d do mkdir $i cd $i git init echo "module $i" > $i.txt git add $i.txt git commit -m "Initial commit, submodule $i" cd .. done
Erstellen Sie nun das Superprojekt und fügen Sie alle Submodule hinzu.
$ mkdir super $ cd super $ git init $ for i in a b c d do git submodule add ~/git/$i $i done
|
Hinweis
|
Verwenden Sie hier keine lokalen URLs, wenn Sie vorhaben, Ihr Superprojekt zu veröffentlichen! |
Sehen Sie, welche Dateien git submodule erstellt hat.
$ ls -a . .. .git .gitmodules a b c d
Der Befehl git submodule add <repo> <path> tut ein paar Dinge:
-
Er klont das Submodul von <repo> in den angegebenen <path> unter dem aktuellen Verzeichnis und checkt standardmäßig den Master-Branch aus.
-
Er fügt den Klonpfad des Submoduls zur Datei gitmodules[5] hinzu und fügt diese Datei zum Index hinzu, bereit zum Committen.
-
Er fügt die aktuelle Commit-ID des Submoduls zum Index hinzu, bereit zum Committen.
Committen Sie das Superprojekt.
$ git commit -m "Add submodules a, b, c and d."
Klonen Sie nun das Superprojekt.
$ cd .. $ git clone super cloned $ cd cloned
Die Submodul-Verzeichnisse sind vorhanden, aber leer.
$ ls -a a . .. $ git submodule status -d266b9873ad50488163457f025db7cdd9683d88b a -e81d457da15309b4fef4249aba9b50187999670d b -c1536a972b9affea0f16e0680ba87332dc059146 c -d96249ff5d57de5de093e6baff9e0aafa5276a74 d
|
Hinweis
|
Die oben genannten Commit-Objektnamen wären für Sie unterschiedlich, sollten aber mit den HEAD-Commit-Objektnamen Ihrer Repositories übereinstimmen. Sie können dies überprüfen, indem Sie git ls-remote ../a ausführen. |
Das Herunterladen der Submodule ist ein zweistufiger Prozess. Führen Sie zuerst git submodule init aus, um die URLs der Submodul-Repositorys zu .git/config hinzuzufügen.
$ git submodule init
Verwenden Sie nun git submodule update, um die Repositories zu klonen und die im Superprojekt angegebenen Commits auszuchecken.
$ git submodule update $ cd a $ ls -a . .. .git a.txt
Ein wesentlicher Unterschied zwischen git submodule update und git submodule add ist, dass git submodule update einen bestimmten Commit auscheckt, anstatt die Spitze eines Branches. Es ist wie das Auschecken eines Tags: der Head ist abgetrennt, so dass Sie nicht an einem Branch arbeiten.
$ git branch * (detached from d266b98) master
Wenn Sie eine Änderung innerhalb eines Submoduls vornehmen möchten und einen abgetrennten Head haben, sollten Sie einen Branch erstellen oder auschecken, Ihre Änderungen vornehmen, die Änderung innerhalb des Submoduls veröffentlichen und dann das Superprojekt aktualisieren, um auf den neuen Commit zu verweisen.
$ git switch master
oder
$ git switch -c fix-up
dann
$ echo "adding a line again" >> a.txt $ git commit -a -m "Updated the submodule from within the superproject." $ git push $ cd .. $ git diff diff --git a/a b/a index d266b98..261dfac 160000 --- a/a +++ b/a @@ -1 +1 @@ -Subproject commit d266b9873ad50488163457f025db7cdd9683d88b +Subproject commit 261dfac35cb99d380eb966e102c1197139f7fa24 $ git add a $ git commit -m "Updated submodule a." $ git push
Sie müssen git submodule update nach git pull ausführen, wenn Sie auch Submodule aktualisieren möchten.
Fallstricke bei Submodulen
Veröffentlichen Sie immer zuerst die Submodul-Änderung, bevor Sie die Änderung am Superprojekt veröffentlichen, die sie referenziert. Wenn Sie vergessen, die Submodul-Änderung zu veröffentlichen, können andere das Repository nicht klonen.
$ cd ~/git/super/a $ echo i added another line to this file >> a.txt $ git commit -a -m "doing it wrong this time" $ cd .. $ git add a $ git commit -m "Updated submodule a again." $ git push $ cd ~/git/cloned $ git pull $ git submodule update error: pathspec '261dfac35cb99d380eb966e102c1197139f7fa24' did not match any file(s) known to git. Did you forget to 'git add'? Unable to checkout '261dfac35cb99d380eb966e102c1197139f7fa24' in submodule path 'a'
In älteren Git-Versionen konnte man leicht vergessen, neue oder geänderte Dateien in einem Submodul zu committen, was lautlos zu ähnlichen Problemen führt wie das Nicht-Pushen der Submodul-Änderungen. Ab Git 1.7.0 zeigen sowohl git status als auch git diff im Superprojekt Submodule als geändert an, wenn sie neue oder geänderte Dateien enthalten, um versehentliche Commits eines solchen Zustands zu verhindern. git diff fügt auch ein -dirty auf der Arbeitsbaum-Seite hinzu, wenn es Patch-Ausgaben generiert oder mit der Option --submodule verwendet wird.
$ git diff diff --git a/sub b/sub --- a/sub +++ b/sub @@ -1 +1 @@ -Subproject commit 3f356705649b5d566d97ff843cf193359229a453 +Subproject commit 3f356705649b5d566d97ff843cf193359229a453-dirty $ git diff --submodule Submodule sub 3f35670..3f35670-dirty:
Sie sollten auch keine Branches in einem Submodul über Commits zurückspulen, die jemals in einem Superprojekt aufgezeichnet wurden.
Es ist nicht sicher, git submodule update auszuführen, wenn Sie Änderungen in einem Submodul vorgenommen und committet haben, ohne zuerst einen Branch auszuchecken. Diese werden lautlos überschrieben.
$ cat a.txt module a $ echo line added from private2 >> a.txt $ git commit -a -m "line added inside private2" $ cd .. $ git submodule update Submodule path 'a': checked out 'd266b9873ad50488163457f025db7cdd9683d88b' $ cd a $ cat a.txt module a
|
Hinweis
|
Die Änderungen sind immer noch im Reflog des Submoduls sichtbar. |
Wenn Sie uncommitted Änderungen im Arbeitsbaum Ihres Submoduls haben, wird git submodule update diese nicht überschreiben. Stattdessen erhalten Sie die übliche Warnung, dass Sie nicht von einem "dirty" Branch wechseln können.
Low-Level-Git-Operationen
Viele der höherwertigen Befehle wurden ursprünglich als Shell-Skripte implementiert, die auf einem kleineren Kern von Low-Level-Git-Befehlen basieren. Diese können immer noch nützlich sein, wenn Sie ungewöhnliche Dinge mit Git tun oder einfach nur seine inneren Abläufe verstehen möchten.
Objektzugriff und -manipulation
Der Befehl git-cat-file[1] kann den Inhalt jedes Objekts anzeigen, obwohl der höherwertige Befehl git-show[1] normalerweise nützlicher ist.
Der Befehl git-commit-tree[1] ermöglicht das Erstellen von Commits mit beliebigen Eltern und Bäumen.
Ein Baum kann mit git-write-tree[1] erstellt und seine Daten können mit git-ls-tree[1] abgerufen werden. Zwei Bäume können mit git-diff-tree[1] verglichen werden.
Ein Tag wird mit git-mktag[1] erstellt, und die Signatur kann mit git-verify-tag[1] überprüft werden, obwohl es normalerweise einfacher ist, für beides git-tag[1] zu verwenden.
Der Arbeitsablauf
Höherwertige Operationen wie git-commit[1] und git-restore[1] verschieben Daten zwischen dem Arbeitsbaum, dem Index und der Objektdatenbank. Git bietet Low-Level-Operationen, die jeden dieser Schritte einzeln ausführen.
Im Allgemeinen arbeiten alle Git-Operationen mit der Index-Datei. Einige Operationen arbeiten *ausschließlich* mit der Index-Datei (zeigen den aktuellen Zustand des Index an), aber die meisten Operationen verschieben Daten zwischen der Index-Datei und entweder der Datenbank oder dem Arbeitsverzeichnis. Somit gibt es vier Hauptkombinationen:
Arbeitsverzeichnis → Index
Der Befehl git-update-index[1] aktualisiert den Index mit Informationen aus dem Arbeitsverzeichnis. Sie aktualisieren die Indexinformationen normalerweise, indem Sie einfach den Dateinamen angeben, den Sie aktualisieren möchten, z. B.
$ git update-index filename
aber um häufige Fehler bei Dateinamen-Globbing usw. zu vermeiden, fügt der Befehl normalerweise keine völlig neuen Einträge hinzu oder entfernt alte Einträge, d. h. er aktualisiert normalerweise nur vorhandene Cache-Einträge.
Um Git mitzuteilen, dass Sie wirklich erkennen, dass bestimmte Dateien nicht mehr existieren oder dass neue Dateien hinzugefügt werden sollen, sollten Sie die Flags --remove und --add verwenden.
HINWEIS! Ein Flag --remove bedeutet *nicht*, dass nachfolgende Dateinamen unbedingt entfernt werden: Wenn die Dateien noch in Ihrer Verzeichnisstruktur existieren, wird der Index mit ihrem neuen Status aktualisiert, nicht entfernt. Das einzige, was --remove bedeutet, ist, dass update-index eine entfernte Datei als gültige Sache betrachtet und wenn die Datei tatsächlich nicht mehr existiert, wird der Index entsprechend aktualisiert.
Als Sonderfall können Sie auch git update-index --refresh ausführen, wodurch die "Statistik"-Informationen jedes Index aktualisiert werden, um mit den aktuellen Statistik-Informationen übereinzustimmen. Es werden *nicht* die Objektstatus selbst aktualisiert, und es werden nur die Felder aktualisiert, die verwendet werden, um schnell zu testen, ob ein Objekt noch mit seinem alten Backing-Store-Objekt übereinstimmt.
Das zuvor eingeführte git-add[1] ist nur ein Wrapper für git-update-index[1].
Index → Objektdatenbank
Sie schreiben Ihre aktuelle Indexdatei mit dem Programm in ein "Baum"-Objekt
$ git write-tree
das ohne Optionen kommt – es schreibt einfach den aktuellen Index in die Menge der Baumobjekte, die diesen Zustand beschreiben, und gibt den Namen des resultierenden übergeordneten Baumes zurück. Sie können diesen Baum verwenden, um den Index jederzeit neu zu generieren, indem Sie in die andere Richtung gehen.
Objektdatenbank → Index
Sie lesen eine "Baum"-Datei aus der Objektdatenbank und verwenden diese, um Ihren aktuellen Index zu befüllen (und zu überschreiben – tun Sie dies nicht, wenn Ihr Index ungespeicherte Zustände enthält, die Sie später wiederherstellen möchten!). Der normale Vorgang ist einfach
$ git read-tree <SHA-1 of tree>
und Ihr Index wird nun dem Baum entsprechen, den Sie zuvor gespeichert haben. Dies ist jedoch nur Ihr *Index*-Datei: Der Inhalt Ihres Arbeitsverzeichnisses wurde nicht geändert.
Index → Arbeitsverzeichnis
Sie aktualisieren Ihr Arbeitsverzeichnis aus dem Index, indem Sie Dateien "auschecken". Dies ist keine sehr häufige Operation, da Sie normalerweise Ihre Dateien auf dem neuesten Stand halten und anstatt in Ihr Arbeitsverzeichnis zu schreiben, den Index-Dateien die Änderungen in Ihrem Arbeitsverzeichnis mitteilen (d. h. git update-index).
Wenn Sie sich jedoch entscheiden, zu einer neuen Version zu springen, die Version von jemand anderem auszuchecken oder einfach einen früheren Baum wiederherzustellen, füllen Sie Ihre Indexdatei mit `read-tree` und müssen dann das Ergebnis mit
$ git checkout-index filename
oder, wenn Sie den gesamten Index auschecken möchten, verwenden Sie -a.
HINWEIS! git checkout-index weigert sich normalerweise, alte Dateien zu überschreiben. Wenn Sie also eine alte Version des Baumes bereits ausgecheckt haben, müssen Sie das Flag -f verwenden (vor dem Flag -a oder dem Dateinamen), um das Auschecken zu *erzwingen*.
Schließlich gibt es noch ein paar Kleinigkeiten, die nicht rein von einer Darstellung zur anderen übergehen.
Alles zusammenfügen
Um einen mit git write-tree instanziierten Baum zu committen, würden Sie ein "Commit"-Objekt erstellen, das auf diesen Baum und die dahinterliegende Historie verweist – insbesondere die "Eltern"-Commits, die ihm in der Historie vorausgingen.
Normalerweise hat ein "Commit" einen Elternteil: den vorherigen Zustand des Baumes, bevor eine bestimmte Änderung vorgenommen wurde. Manchmal kann er jedoch zwei oder mehr Eltern-Commits haben, in diesem Fall nennen wir ihn einen "Merge", da ein solcher Commit zwei oder mehr vorherige Zustände, die durch andere Commits repräsentiert werden, zusammenbringt ("mergt").
Mit anderen Worten, während ein "Baum" einen bestimmten Verzeichniszustand eines Arbeitsverzeichnisses darstellt, stellt ein "Commit" diesen Zustand im Zeitverlauf dar und erklärt, wie wir dorthin gelangt sind.
Sie erstellen ein Commit-Objekt, indem Sie ihm den Baum geben, der den Zustand zum Zeitpunkt des Commits beschreibt, und eine Liste von Eltern.
$ git commit-tree <tree> -p <parent> [(-p <parent2>)...]
und geben dann den Grund für den Commit über stdin an (entweder durch Umleitung aus einer Pipe oder einer Datei oder durch direkte Eingabe am TTY).
git commit-tree gibt den Namen des Objekts zurück, das diesen Commit repräsentiert, und Sie sollten ihn für die spätere Verwendung speichern. Normalerweise würden Sie einen neuen HEAD-Zustand committen, und obwohl Git es egal ist, wo Sie die Notiz über diesen Zustand speichern, neigen wir in der Praxis dazu, das Ergebnis in die Datei zu schreiben, auf die von .git/HEAD verwiesen wird, damit wir immer sehen können, was der letzte committete Zustand war.
Hier ist eine Grafik, die veranschaulicht, wie die verschiedenen Teile zusammenpassen.
commit-tree
commit obj
+----+
| |
| |
V V
+-----------+
| Object DB |
| Backing |
| Store |
+-----------+
^
write-tree | |
tree obj | |
| | read-tree
| | tree obj
V
+-----------+
| Index |
| "cache" |
+-----------+
update-index ^
blob obj | |
| |
checkout-index -u | | checkout-index
stat | | blob obj
V
+-----------+
| Working |
| Directory |
+-----------+
Untersuchung der Daten
Sie können die in der Objektdatenbank und im Index dargestellten Daten mit verschiedenen Hilfsprogrammen untersuchen. Für jedes Objekt können Sie git-cat-file[1] verwenden, um Details über das Objekt zu untersuchen.
$ git cat-file -t <objectname>
zeigt den Typ des Objekts an, und sobald Sie den Typ kennen (der normalerweise implizit darin ist, wo Sie das Objekt finden), können Sie
$ git cat-file blob|tree|commit|tag <objectname>
verwenden, um seinen Inhalt anzuzeigen. HINWEIS! Bäume haben Binärinhalte, und als Ergebnis gibt es einen speziellen Helfer, um diesen Inhalt anzuzeigen, namens git ls-tree, der den Binärinhalt in ein leichter lesbares Format umwandelt.
Es ist besonders lehrreich, sich "Commit"-Objekte anzusehen, da diese tendenziell klein und ziemlich selbsterklärend sind. Insbesondere, wenn Sie der Konvention folgen, den Namen des obersten Commits in .git/HEAD zu haben, können Sie
$ git cat-file commit HEAD
tun, um zu sehen, was der oberste Commit war.
Zusammenführen mehrerer Bäume
Git kann Ihnen helfen, einen Drei-Wege-Merge durchzuführen, der wiederum für einen Viel-Wege-Merge verwendet werden kann, indem das Merge-Verfahren mehrmals wiederholt wird. Die übliche Situation ist, dass Sie nur einen Drei-Wege-Merge durchführen (zwei Historienlinien abgleichen) und das Ergebnis committen, aber wenn Sie möchten, können Sie mehrere Branches gleichzeitig mergen.
Um einen Drei-Wege-Merge durchzuführen, beginnen Sie mit den beiden Commits, die Sie mergen möchten, ermitteln Sie ihren nächsten gemeinsamen Elternteil (einen dritten Commit) und vergleichen Sie die Bäume, die diesen drei Commits entsprechen.
Um die "Basis" für den Merge zu erhalten, suchen Sie den gemeinsamen Elternteil von zwei Commits.
$ git merge-base <commit1> <commit2>
Dies gibt den Namen eines Commits aus, auf dem beide basieren. Sie sollten nun die Baumobjekte dieser Commits nachschlagen, was Sie leicht mit
$ git cat-file commit <commitname> | head -1
tun können, da die Baumobjektinformationen immer die erste Zeile eines Commit-Objekts sind.
Sobald Sie die drei Bäume kennen, die Sie mergen möchten (der eine "ursprüngliche" Baum, auch gemeinsamer Baum genannt, und die beiden "Ergebnis"-Bäume, auch die zu mergenden Branches genannt), führen Sie einen "Merge"-Read in den Index durch. Dies wird sich beschweren, wenn es Ihre alten Indexinhalte verwerfen muss. Stellen Sie also sicher, dass Sie diese committet haben – tatsächlich würden Sie normalerweise immer gegen Ihren letzten Commit mergen (der somit mit dem übereinstimmen sollte, was Sie ohnehin in Ihrem aktuellen Index haben).
Um den Merge durchzuführen, machen Sie
$ git read-tree -m -u <origtree> <yourtree> <targettree>
was alle trivialen Merge-Operationen direkt in der Indexdatei durchführt, und Sie können das Ergebnis einfach mit git write-tree schreiben.
Zusammenführen mehrerer Bäume, Fortsetzung
Leider sind viele Merges nicht trivial. Wenn Dateien hinzugefügt, verschoben oder gelöscht wurden, oder wenn beide Branches dieselbe Datei geändert haben, hinterlässt dies einen Indexbaum, der "Merge-Einträge" enthält. Ein solcher Indexbaum kann *NICHT* in ein Baumobjekt geschrieben werden, und Sie müssen alle solchen Merge-Konflikte mit anderen Werkzeugen auflösen, bevor Sie das Ergebnis schreiben können.
Sie können diesen Indexstatus mit dem Befehl git ls-files --unmerged untersuchen. Ein Beispiel.
$ git read-tree -m $orig HEAD $target $ git ls-files --unmerged 100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello.c 100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello.c 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello.c
Jede Zeile der Ausgabe von git ls-files --unmerged beginnt mit den Blob-Modus-Bits, dem Blob-SHA-1, der *Stufennummer* und dem Dateinamen. Die *Stufennummer* ist Git's Art zu sagen, aus welchem Baum sie stammt: Stufe 1 entspricht dem Baum $orig, Stufe 2 dem Baum HEAD und Stufe 3 dem Baum $target.
Zuvor sagten wir, dass triviale Merges innerhalb von git read-tree -m durchgeführt werden. Zum Beispiel, wenn sich die Datei von $orig zu HEAD oder $target nicht geändert hat, oder wenn sich die Datei von $orig zu HEAD und von $orig zu $target auf die gleiche Weise geändert hat, ist das Endergebnis offensichtlich das, was in HEAD ist. Was das obige Beispiel zeigt, ist, dass die Datei hello.c von $orig zu HEAD und von $orig zu $target auf unterschiedliche Weise geändert wurde. Sie könnten dies lösen, indem Sie Ihr bevorzugtes 3-Wege-Merge-Programm, z. B. diff3, merge oder Git's eigenes `merge-file`, auf die Blobs dieser drei Stufen selbst anwenden, wie folgt:
$ git cat-file blob 263414f >hello.c~1 $ git cat-file blob 06fa6a2 >hello.c~2 $ git cat-file blob cc44c73 >hello.c~3 $ git merge-file hello.c~2 hello.c~1 hello.c~3
Dies würde das Zusammenführungsergebnis in der Datei hello.c~2 hinterlassen, zusammen mit Konfliktmarkern, falls Konflikte bestehen. Nachdem Sie überprüft haben, ob das Zusammenführungsergebnis sinnvoll ist, können Sie Git mitteilen, wie das endgültige Zusammenführungsergebnis für diese Datei aussieht, indem Sie
$ mv -f hello.c~2 hello.c $ git update-index hello.c
Wenn ein Pfad im Zustand "unmerged" ist, teilt das Ausführen von git update-index für diesen Pfad Git mit, dass der Pfad als aufgelöst markiert werden soll.
Das Obige ist die Beschreibung einer Git-Zusammenführung auf der niedrigsten Ebene, um Ihnen zu helfen zu verstehen, was konzeptionell unter der Haube passiert. In der Praxis ruft niemand, nicht einmal Git selbst, git cat-file dreimal für diesen Zweck auf. Es gibt ein Programm namens git merge-index, das die Stufen in temporäre Dateien extrahiert und ein "merge"-Skript darauf aufruft.
$ git merge-index git-merge-one-file hello.c
und damit wird das höherstufige git merge -s resolve implementiert.
Git hacken
Dieses Kapitel behandelt interne Details der Git-Implementierung, die wahrscheinlich nur Git-Entwickler verstehen müssen.
Objektspeicherformat
Alle Objekte haben einen statisch bestimmten "Typ", der das Format des Objekts identifiziert (d.h. wie es verwendet wird und wie es auf andere Objekte verweisen kann). Derzeit gibt es vier verschiedene Objekttypen: "blob", "tree", "commit" und "tag".
Unabhängig vom Objekttyp haben alle Objekte folgende gemeinsame Merkmale: Sie werden alle mit zlib deflated und haben einen Header, der nicht nur ihren Typ angibt, sondern auch Größeninformationen über die Daten im Objekt liefert. Es ist erwähnenswert, dass der SHA-1-Hash, der zur Benennung des Objekts verwendet wird, der Hash der Originaldaten plus dieses Headers ist, sodass sha1sum file nicht mit dem Objektnamen für file übereinstimmt (die frühesten Versionen von Git haben leicht anders gehasht, aber die Schlussfolgerung bleibt die gleiche).
Das Folgende ist ein kurzes Beispiel, das zeigt, wie diese Hashes manuell generiert werden können.
Nehmen wir eine kleine Textdatei mit einfachem Inhalt an.
$ echo "Hello world" >hello.txt
Wir können nun manuell den Hash generieren, den Git für diese Datei verwenden würde.
-
Das Objekt, für das wir den Hash benötigen, ist vom Typ "blob" und hat eine Größe von 12 Bytes.
-
Präfixen Sie den Objekt-Header mit dem Dateiinhalt und übergeben Sie dies an
sha1sum.
$ { printf "blob 12\0"; cat hello.txt; } | sha1sum
802992c4220de19a90767f3000a79a31b98d0df7 -
Dieser manuell erstellte Hash kann mit git hash-object überprüft werden, was natürlich die Addition des Headers verbirgt.
$ git hash-object hello.txt 802992c4220de19a90767f3000a79a31b98d0df7
Als Ergebnis kann die allgemeine Konsistenz eines Objekts immer unabhängig vom Inhalt oder Typ des Objekts getestet werden: Alle Objekte können validiert werden, indem überprüft wird, dass (a) ihre Hashes mit dem Inhalt der Datei übereinstimmen und (b) das Objekt erfolgreich zu einem Byte-Stream aufgeblasen werden kann, der eine Sequenz von <ascii-type-ohne-leerzeichen> + <leerzeichen> + <ascii-dezimal-größe> + <byte\0> + <binär-objekt-daten> bildet.
Die strukturierten Objekte können ferner ihre Struktur und ihre Verbindungen zu anderen Objekten überprüfen lassen. Dies geschieht im Allgemeinen mit dem Programm git fsck, das einen vollständigen Abhängigkeitsgraphen aller Objekte generiert und deren interne Konsistenz überprüft (zusätzlich zur Überprüfung ihrer oberflächlichen Konsistenz durch den Hash).
Eine Vogelperspektive auf den Quellcode von Git
Für neue Entwickler ist es nicht immer einfach, sich im Quellcode von Git zurechtzufinden. Dieser Abschnitt gibt Ihnen eine kleine Anleitung, wo Sie anfangen können.
Ein guter Startpunkt ist der Inhalt des ersten Commits, mit
$ git switch --detach e83c5163
Die initiale Revision legt den Grundstein für fast alles, was Git heute hat (auch wenn sich Details an einigen Stellen unterscheiden mögen), ist aber klein genug, um in einer Sitzung gelesen zu werden.
Beachten Sie, dass sich die Terminologie seit dieser Revision geändert hat. Zum Beispiel verwendet die README in dieser Revision das Wort "changeset", um zu beschreiben, was wir jetzt einen commit nennen.
Außerdem nennen wir es nicht mehr "cache", sondern "index"; die Datei heißt jedoch immer noch read-cache.h.
Wenn Sie die Ideen in diesem ersten Commit verstanden haben, sollten Sie sich eine neuere Version ansehen und read-cache-ll.h, object.h und commit.h überfliegen.
Früher war Git (in der Tradition von UNIX) eine Ansammlung von Programmen, die extrem einfach waren und die man in Skripten verwendete, wobei die Ausgabe eines in ein anderes leitete. Dies erwies sich als gut für die anfängliche Entwicklung, da es einfacher war, neue Dinge zu testen. Kürzlich sind jedoch viele dieser Teile zu Built-ins geworden, und ein Teil des Kerns wurde "libifiziert", d.h. aus Leistungs-, Portabilitäts- und Vermeiderungsgründen in libgit.a verschoben.
Nun wissen Sie, was der Index ist (und finden die entsprechenden Datenstrukturen in read-cache-ll.h) und dass es nur ein paar Objekttypen gibt (blobs, trees, commits und tags), die ihre gemeinsame Struktur von struct object erben, die ihr erstes Element ist (und somit können Sie z.B. (struct object *)commit casten, um das Gleiche wie &commit->object zu erreichen, d.h. den Objektnamen und die Flags zu erhalten).
Nun ist ein guter Zeitpunkt, um eine Pause zu machen, damit diese Informationen sacken können.
Nächster Schritt: Machen Sie sich mit der Objektnamenbildung vertraut. Lesen Sie Commits benennen. Es gibt viele Möglichkeiten, ein Objekt zu benennen (und nicht nur Revisionen!). All dies wird in sha1_name.c behandelt. Schauen Sie sich einfach die Funktion get_sha1() kurz an. Viele der speziellen Behandlungen werden von Funktionen wie get_sha1_basic() oder ähnlichem durchgeführt.
Dies dient nur dazu, Sie für den am stärksten libifizierten Teil von Git zu begeistern: den Revisionswalker.
Grundsätzlich war die erste Version von git log ein Shell-Skript.
$ git-rev-list --pretty $(git-rev-parse --default HEAD "$@") | \
LESS=-S ${PAGER:-less}
Was bedeutet das?
git rev-list ist die ursprüngliche Version des Revisionswalkers, der immer eine Liste von Revisionen nach stdout ausgab. Er ist immer noch funktionsfähig und muss es sein, da die meisten neuen Git-Befehle als Skripte mit git rev-list beginnen.
git rev-parse ist nicht mehr so wichtig; er wurde nur verwendet, um Optionen herauszufiltern, die für die verschiedenen Plumbing-Befehle relevant waren, die von dem Skript aufgerufen wurden.
Das meiste, was git rev-list tat, ist in revision.c und revision.h enthalten. Es verpackt die Optionen in einer Struktur namens rev_info, die steuert, wie und welche Revisionen durchlaufen werden, und mehr.
Die ursprüngliche Aufgabe von git rev-parse wird jetzt von der Funktion setup_revisions() übernommen, die die Revisionen und die üblichen Befehlszeilenoptionen für den Revisionswalker parst. Diese Informationen werden in der Struktur rev_info für die spätere Verwendung gespeichert. Sie können Ihre eigene Befehlszeilenoptionen nach dem Aufruf von setup_revisions() parsen. Danach müssen Sie prepare_revision_walk() zur Initialisierung aufrufen und können dann die Commits einzeln mit der Funktion get_revision() abrufen.
Wenn Sie an weiteren Details des Revisionswalk-Prozesses interessiert sind, schauen Sie sich einfach die erste Implementierung von cmd_log() an; rufen Sie git show v1.3.0~155^2~4 auf und scrollen Sie zu dieser Funktion (beachten Sie, dass Sie setup_pager() nicht mehr direkt aufrufen müssen).
Heutzutage ist git log ein Builtin, was bedeutet, dass es im Befehl git enthalten ist. Die Quellseite eines Builtins ist
-
eine Funktion namens
cmd_<bla>, die typischerweise in builtin/<bla.c> definiert ist (beachten Sie, dass ältere Versionen von Git sie stattdessen inbuiltin-<bla>.chatten) und inbuiltin.hdeklariert ist. -
ein Eintrag im Array
commands[] ingit.c, und -
ein Eintrag in
BUILTIN_OBJECTSimMakefile.
Manchmal sind mehr als ein Builtin in einer Quelldatei enthalten. Zum Beispiel befinden sich sowohl cmd_show() als auch cmd_log() in builtin/log.c, da sie viel Code gemeinsam nutzen. In diesem Fall müssen die Befehle, die nicht wie die .c-Datei heißen, in der sie leben, in BUILT_INS im Makefile aufgeführt werden.
git log sieht in C komplizierter aus als im ursprünglichen Skript, aber das ermöglicht eine viel größere Flexibilität und Leistung.
Auch hier ist ein guter Zeitpunkt für eine Pause.
Lektion drei: Studieren Sie den Code. Wirklich, es ist der beste Weg, um die Organisation von Git kennenzulernen (nachdem Sie die grundlegenden Konzepte kennen).
Denken Sie also über etwas nach, das Sie interessiert, z. B. "Wie kann ich auf einen Blob zugreifen, wenn ich nur den Objektnamen kenne?". Der erste Schritt ist, einen Git-Befehl zu finden, mit dem Sie das tun können. In diesem Beispiel ist es entweder git show oder git cat-file.
Zur Verdeutlichung bleiben wir bei git cat-file, weil es
-
Plumbing ist, und
-
bereits im ersten Commit vorhanden war (es durchlief buchstäblich nur etwa 20 Revisionen als
cat-file.c, wurde inbuiltin/cat-file.cumbenannt, als es zu einem Builtin gemacht wurde, und sah dann weniger als 10 Versionen).
Schauen Sie sich also builtin/cat-file.c an, suchen Sie nach cmd_cat_file() und sehen Sie, was es tut.
repo_config(the_repository, git_default_config);
if (argc != 3)
usage("git cat-file [-t|-s|-e|-p|<type>] <sha1>");
if (get_sha1(argv[2], sha1))
die("Not a valid object name %s", argv[2]);
Lassen wir die offensichtlichen Details beiseite; der einzig wirklich interessante Teil hier ist der Aufruf von get_sha1(). Er versucht, argv[2] als Objektnamen zu interpretieren, und wenn er sich auf ein Objekt bezieht, das im aktuellen Repository vorhanden ist, schreibt er die resultierende SHA-1 in die Variable sha1.
Zwei Dinge sind hier interessant:
-
get_sha1() gibt 0 bei Erfolg zurück. Dies mag einige neue Git-Hacker überraschen, aber es gibt eine lange Tradition in UNIX, bei verschiedenen Fehlern unterschiedliche negative Zahlen zurückzugeben – und 0 bei Erfolg. -
Die Variable
sha1in der Funktionssignatur vonget_sha1() istunsignedchar*, wird aber tatsächlich als Zeiger aufunsignedchar[20] erwartet. Diese Variable enthält die 160-Bit-SHA-1 des angegebenen Commits. Beachten Sie, dass, wann immer eine SHA-1 alsunsignedchar*übergeben wird, es sich um die binäre Darstellung handelt, im Gegensatz zur ASCII-Darstellung in Hex-Zeichen, die alschar*übergeben wird.
Sie werden beides im Code sehen.
Nun zum Wesentlichen:
case 0:
buf = odb_read_object_peeled(r->objects, sha1, argv[1], &size, NULL);
So lesen Sie einen Blob (eigentlich nicht nur einen Blob, sondern jeden Objekttyp). Um zu erfahren, wie die Funktion odb_read_object_peeled() tatsächlich funktioniert, finden Sie den Quellcode dafür (etwa git grep read_object_with | grep ":[a-z]" im Git-Repository) und lesen Sie den Quellcode.
Um herauszufinden, wie das Ergebnis verwendet werden kann, lesen Sie einfach weiter in cmd_cat_file().
write_or_die(1, buf, size);
Manchmal weiß man nicht, wo man nach einer Funktion suchen soll. In vielen solchen Fällen hilft es, die Ausgabe von git log zu durchsuchen und dann den entsprechenden Commit mit git show anzuzeigen.
Beispiel: Wenn Sie wissen, dass es einen Testfall für git bundle gab, sich aber nicht mehr erinnern, wo er war (ja, Sie könnten git grep bundle t/ verwenden, aber das illustriert nicht den Punkt!)
$ git log --no-merges t/
Im Pager (less), suchen Sie einfach nach "bundle", gehen Sie ein paar Zeilen zurück und sehen Sie, dass es sich in Commit 18449ab0 befindet. Kopieren Sie nun diesen Objektnamen und fügen Sie ihn in die Befehlszeile ein:
$ git show 18449ab0
Voilà.
Ein weiteres Beispiel: Finden Sie heraus, was zu tun ist, um ein Skript zu einem Builtin zu machen.
$ git log --no-merges --diff-filter=A builtin/*.c
Sehen Sie, Git ist tatsächlich das beste Werkzeug, um etwas über die Quelle von Git selbst herauszufinden!
Git-Glossar
Git erklärt
- alternative Objekt-Datenbank
-
Über den Alternativen-Mechanismus kann ein Repository einen Teil seiner Objekt-Datenbank von einer anderen Objekt-Datenbank erben, die als "Alternative" bezeichnet wird.
- Bare-Repository
-
Ein Bare-Repository ist normalerweise ein entsprechend benannter Ordner mit der Endung
.git, der keine lokal ausgecheckte Kopie von Dateien unter Versionskontrolle hat. Das heißt, alle Git-Administrations- und Steuerdateien, die normalerweise im versteckten Unterordner.gitvorhanden wären, befinden sich stattdessen direkt im Ordnerrepository.git, und es sind keine anderen Dateien vorhanden und ausgecheckt. Normalerweise stellen Anbieter von öffentlichen Repositories Bare-Repositories zur Verfügung. - Blob-Objekt
-
Nicht typisiertes Objekt, z. B. der Inhalt einer Datei.
- Branch (Zweig)
-
Ein "Branch" ist eine Entwicklungslinie. Der aktuellste Commit auf einem Branch wird als Spitze dieses Branches bezeichnet. Die Spitze des Branches wird durch einen Branch-Verweis (head) referenziert, der sich weiterbewegt, wenn zusätzliche Entwicklungen auf dem Branch durchgeführt werden. Ein einzelnes Git-Repository kann eine beliebige Anzahl von Branches verfolgen, aber Ihr Arbeitsbaum ist nur mit einem davon (dem "aktuellen" oder "ausgecheckten" Branch) verknüpft, und HEAD zeigt auf diesen Branch.
- Cache
-
Veraltet für: Index.
- Kette
-
Eine Liste von Objekten, wobei jedes Objekt in der Liste einen Verweis auf seinen Nachfolger enthält (z. B. könnte der Nachfolger eines Commits einer seiner Vorgänger sein).
- Changeset (Änderungssatz)
-
BitKeeper/cvsps-Sprache für "Commit". Da Git keine Änderungen, sondern Zustände speichert, ist die Verwendung des Begriffs "changesets" mit Git nicht wirklich sinnvoll.
- Checkout (Auschecken)
-
Die Aktion, den gesamten oder einen Teil des Arbeitsbaums mit einem Tree-Objekt oder Blob aus der Objekt-Datenbank zu aktualisieren und den Index und HEAD zu aktualisieren, wenn der gesamte Arbeitsbaum auf einen neuen Branch gezeigt wurde.
- Cherry-Picking
-
Im SCM-Jargon bedeutet "cherry pick", eine Teilmenge von Änderungen aus einer Reihe von Änderungen (typischerweise Commits) auszuwählen und sie als neue Reihe von Änderungen oberhalb einer anderen Codebasis aufzuzeichnen. In Git wird dies durch den Befehl "git cherry-pick" durchgeführt, um die von einem bestehenden Commit eingeführte Änderung zu extrahieren und sie basierend auf der Spitze des aktuellen Branches als neuen Commit zu erfassen.
- Clean (sauber)
-
Ein Arbeitsbaum ist "clean", wenn er mit der von der aktuellen Spitze referenzierten Revision übereinstimmt. Siehe auch "dirty".
- Commit
-
Als Substantiv: Ein einzelner Punkt in der Git-Historie; die gesamte Historie eines Projekts wird als Menge von miteinander verbundenen Commits dargestellt. Das Wort "Commit" wird von Git oft an den Stellen verwendet, an denen andere Revisionskontrollsysteme die Wörter "Revision" oder "Version" verwenden. Wird auch als Kurzform für Commit-Objekt verwendet.
- Commit-Graph-Konzept, Darstellungen und Nutzung
-
Ein Synonym für die DAG-Struktur, die von den Commits in der Objekt-Datenbank gebildet wird und durch die Spitzen von Branches referenziert wird, die ihre Kette von verknüpften Commits verwenden. Diese Struktur ist der definitive Commit-Graph. Der Graph kann in anderen Formen dargestellt werden, z. B. in der "Commit-Graph"-Datei.
- Commit-Graph-Datei
-
Die "Commit-Graph"-Datei (normalerweise mit Bindestrich) ist eine ergänzende Darstellung des Commit-Graphen, die Spaziergänge im Commit-Graphen beschleunigt. Die "Commit-Graph"-Datei wird entweder im Verzeichnis .git/objects/info oder im Info-Verzeichnis einer alternativen Objekt-Datenbank gespeichert.
- Commit-Objekt
-
Ein Objekt, das Informationen über eine bestimmte Revision enthält, wie z. B. Vorgänger, Committer, Autor, Datum und das Tree-Objekt, das dem obersten Verzeichnis der gespeicherten Revision entspricht.
- Commit-ish (auch Committish)
-
Ein Commit-Objekt oder ein Objekt, das rekursiv zu einem Commit-Objekt dereferenziert werden kann. Folgendes sind alles Commit-ishes: ein Commit-Objekt, ein Tag-Objekt, das auf ein Commit-Objekt verweist, ein Tag-Objekt, das auf ein Tag-Objekt verweist, das auf ein Commit-Objekt verweist, usw.
- Kern-Git
-
Fundamentale Datenstrukturen und Hilfsprogramme von Git. Bietet nur begrenzte Werkzeuge zur Quellcodeverwaltung.
- DAG
-
Directed Acyclic Graph (gerichteter azyklischer Graph). Die Commit-Objekte bilden einen gerichteten azyklischen Graphen, da sie Vorgänger (gerichtet) haben und der Graph der Commit-Objekte azyklisch ist (es gibt keine Kette, die mit demselben Objekt beginnt und endet).
- schwebendes Objekt
-
Ein unerreichbares Objekt, das auch von anderen unerreichbaren Objekten aus nicht erreichbar ist; ein schwebendes Objekt hat keine Verweise von irgendeinem Verweis oder Objekt im Repository.
- Dereferenzieren
-
Bezieht sich auf einen symbolischen Verweis: die Aktion des Zugriffs auf den Verweis, auf den ein symbolischer Verweis zeigt. Rekursives Dereferenzieren beinhaltet die Wiederholung des oben genannten Prozesses auf dem resultierenden Verweis, bis ein nicht-symbolischer Verweis gefunden wird.
Bezieht sich auf ein Tag-Objekt: die Aktion des Zugriffs auf das Objekt, auf das ein Tag zeigt. Tags werden rekursiv dereferenziert, indem die Operation auf dem Ergebnisobjekt wiederholt wird, bis das Ergebnis entweder einen angegebenen Objekttyp (wo zutreffend) oder einen beliebigen Nicht-"Tag"-Objekttyp hat. Ein Synonym für "rekursives Dereferenzieren" im Kontext von Tags ist "abziehen".
Bezieht sich auf ein Commit-Objekt: die Aktion des Zugriffs auf das Tree-Objekt des Commits. Commits können nicht rekursiv dereferenziert werden.
Sofern nicht anders angegeben, ist "Dereferenzieren", wie es im Kontext von Git-Befehlen oder -Protokollen verwendet wird, implizit rekursiv.
- detached HEAD
-
Normalerweise speichert der HEAD den Namen eines Branches, und Befehle, die mit der vom HEAD repräsentierten Historie arbeiten, arbeiten mit der Historie, die zur Spitze des Branches führt, auf den der HEAD zeigt. Git erlaubt Ihnen jedoch auch, einen beliebigen Commit auszuchecken, der nicht unbedingt die Spitze eines bestimmten Branches ist. Der HEAD in einem solchen Zustand wird als "detached" bezeichnet.
Beachten Sie, dass Befehle, die mit der Historie des aktuellen Branches arbeiten (z. B.
gitcommit, um eine neue Historie darauf aufzubauen), auch dann noch funktionieren, wenn der HEAD detached ist. Sie aktualisieren den HEAD, damit er auf die Spitze der aktualisierten Historie zeigt, ohne einen Branch zu beeinflussen. Befehle, die Informationen *über* den aktuellen Branch aktualisieren oder abfragen (z. B.gitbranch--set-upstream-to, der festlegt, mit welchem Remote-Tracking-Branch der aktuelle Branch integriert wird), funktionieren offensichtlich nicht, da in diesem Zustand kein (echter) aktueller Branch abgefragt werden kann. - Verzeichnis
-
Die Liste, die Sie mit "ls" erhalten :-)
- Dirty (unsauber)
-
Ein Arbeitsbaum gilt als "dirty", wenn er Änderungen enthält, die nicht auf den aktuellen Branch committet wurden.
- Evil Merge (böswillige Zusammenführung)
-
Eine böswillige Zusammenführung ist eine Zusammenführung, die Änderungen einführt, die in keinem Vorgänger vorkommen.
- Fast-Forward (Schnellvorlauf)
-
Ein Fast-Forward ist eine spezielle Art von Zusammenführung, bei der Sie eine Revision haben und Änderungen von einem anderen Branch "zusammenführen", die zufällig Nachkommen dessen sind, was Sie haben. In einem solchen Fall erstellen Sie keinen neuen Zusammenführungs-Commit, sondern aktualisieren einfach Ihren Branch, damit er auf dieselbe Revision wie der zusammengeführte Branch zeigt. Dies geschieht häufig bei einem Remote-Tracking-Branch eines Remote-Repositorys.
- Fetch (Abrufen)
-
Das Abrufen eines Branches bedeutet, die Head-Referenz des Branches aus einem Remote-Repository abzurufen, herauszufinden, welche Objekte in der lokalen Objekt-Datenbank fehlen, und diese ebenfalls abzurufen. Siehe auch git-fetch[1].
- Dateisystem
-
Linus Torvalds entwarf Git ursprünglich als Dateisystem im Userspace, d.h. als Infrastruktur zur Speicherung von Dateien und Verzeichnissen. Dies gewährleistete die Effizienz und Geschwindigkeit von Git.
- Git-Archiv
-
Synonym für Repository (für Archiv-Leute).
- Gitfile
-
Eine einfache Datei
.gitam Stammverzeichnis eines Arbeitsbaums, die auf das Verzeichnis verweist, das das eigentliche Repository ist. Für die richtige Verwendung siehe git-worktree[1] oder git-submodule[1]. Für die Syntax siehe gitrepository-layout[5]. - Grafts (Pfropfen)
-
Grafts ermöglichen es, zwei ansonsten unterschiedliche Entwicklungslinien zu verbinden, indem falsche Abstammungsinformationen für Commits aufgezeichnet werden. Auf diese Weise können Sie Git dazu bringen, vorzutäuschen, dass die Menge der Vorgänger eines Commits anders ist als das, was bei der Erstellung des Commits aufgezeichnet wurde. Konfiguriert über die Datei
.git/info/grafts.Beachten Sie, dass der Graft-Mechanismus veraltet ist und zu Problemen beim Übertragen von Objekten zwischen Repositories führen kann; siehe git-replace[1] für ein flexibleres und robusteres System, das dasselbe tut.
- Hash
-
Im Git-Kontext Synonym für Objektname.
- Head (Spitze)
-
Ein benannter Verweis auf den Commit an der Spitze eines Branches. Heads werden in einer Datei im Verzeichnis
$GIT_DIR/refs/heads/gespeichert, außer bei Verwendung von Packed Refs. (Siehe git-pack-refs[1].) - HEAD
-
Der aktuelle Branch. Im Detail: Ihr Arbeitsbaum wird normalerweise aus dem Zustand des Baums abgeleitet, auf den HEAD verweist. HEAD ist ein Verweis auf einen der Heads in Ihrem Repository, außer bei einem detached HEAD, in welchem Fall er direkt auf einen beliebigen Commit verweist.
- Head-Referenz
-
Ein Synonym für Head.
- Hook (Haken)
-
Während der normalen Ausführung mehrerer Git-Befehle werden Aufrufe an optionale Skripte gemacht, die es einem Entwickler ermöglichen, Funktionalität oder Prüfungen hinzuzufügen. Typischerweise erlauben Hooks, dass ein Befehl vorab verifiziert und potenziell abgebrochen wird, und ermöglichen eine Benachrichtigung nach Abschluss der Operation. Die Hook-Skripte befinden sich im Verzeichnis
$GIT_DIR/hooks/und werden einfach durch Entfernen der.sample-Endung aus dem Dateinamen aktiviert. In früheren Versionen von Git mussten sie ausführbar gemacht werden. - Index
-
Eine Sammlung von Dateien mit Stat-Informationen, deren Inhalte als Objekte gespeichert sind. Der Index ist eine gespeicherte Version Ihres Arbeitsbaums. Tatsächlich kann er auch eine zweite und sogar eine dritte Version eines Arbeitsbaums enthalten, die beim Zusammenführen verwendet werden.
- Index-Eintrag
-
Die Informationen zu einer bestimmten Datei, die im Index gespeichert sind. Ein Index-Eintrag kann unmerged sein, wenn eine Zusammenführung gestartet, aber noch nicht abgeschlossen wurde (d. h. wenn der Index mehrere Versionen dieser Datei enthält).
- Master (Haupt)
-
Der Standard-Entwicklungs-Branch. Immer wenn Sie ein Git-Repository erstellen, wird ein Branch namens "master" erstellt und zum aktiven Branch. In den meisten Fällen enthält dieser die lokale Entwicklung, obwohl dies rein konventionell ist und nicht erforderlich.
- Merge (Zusammenführung)
-
Als Verb: Die Inhalte eines anderen Branches (möglicherweise aus einem externen Repository) in den aktuellen Branch einbringen. Wenn der zusammengeführte Branch aus einem anderen Repository stammt, geschieht dies durch zuerstiges Abrufen des Remote-Branches und anschließendes Zusammenführen des Ergebnisses in den aktuellen Branch. Diese Kombination aus Fetch- und Merge-Operationen wird als Pull bezeichnet. Das Zusammenführen wird durch einen automatischen Prozess durchgeführt, der Änderungen identifiziert, die seit der Verzweigung der Branches vorgenommen wurden, und dann alle diese Änderungen zusammen anwendet. In Fällen, in denen sich Änderungen widersprechen, kann manuelles Eingreifen erforderlich sein, um die Zusammenführung abzuschließen.
Als Substantiv: Wenn es sich nicht um einen Fast-Forward handelt, führt eine erfolgreiche Zusammenführung zur Erstellung eines neuen Commits, der das Ergebnis der Zusammenführung repräsentiert und die Spitzen der zusammengeführten Branches als Vorgänger hat. Dieser Commit wird als "Merge-Commit" oder manchmal einfach als "Merge" bezeichnet.
- Objekt
-
Die Speichereinheit in Git. Es wird eindeutig durch die SHA-1 seines Inhalts identifiziert. Folglich kann ein Objekt nicht geändert werden.
- Objektdatenbank
-
Speichert eine Menge von "Objekten", und ein einzelnes Objekt wird durch seinen Objektnamen identifiziert. Die Objekte leben normalerweise in
$GIT_DIR/objects/. - Objektidentifikator (oid)
-
Synonym für Objektname.
- Objektname
-
Der eindeutige Identifikator eines Objekts. Der Objektnamen wird normalerweise durch eine 40 Zeichen lange hexadezimale Zeichenkette dargestellt. Umgangssprachlich auch SHA-1 genannt.
- Objekttyp
-
Einer der Identifikatoren "commit", "tree", "tag" oder "blob", der den Typ eines Objekts beschreibt.
- Octopus
- Orphan
-
Der Vorgang, auf einen Branch zu wechseln, der noch nicht existiert (d. h. ein unborn Branch). Nach einer solchen Operation wird der zuerst erstellte Commit zu einem Commit ohne Eltern, was eine neue Historie startet.
- Origin
-
Das Standard-Upstream-Repository. Die meisten Projekte haben mindestens ein Upstream-Projekt, dem sie folgen. Standardmäßig wird *origin* zu diesem Zweck verwendet. Neue Upstream-Updates werden in Remote-Tracking-Branches namens origin/name-des-upstream-branches geholt, die Sie mit
gitbranch-rsehen können. - Overlay
-
Aktualisiert und fügt nur Dateien zum Arbeitsverzeichnis hinzu, löscht sie aber nicht, ähnlich wie cp -R den Inhalt im Zielverzeichnis aktualisieren würde. Dies ist der Standardmodus bei einem Checkout, wenn Dateien aus dem Index oder einem tree-ish ausgecheckt werden. Im Gegensatz dazu löscht der No-Overlay-Modus auch verfolgte Dateien, die in der Quelle nicht vorhanden sind, ähnlich wie rsync --delete.
- Pack
-
Eine Sammlung von Objekten, die in einer Datei komprimiert wurden (um Platz zu sparen oder sie effizient zu übertragen).
- Pack-Index
-
Die Liste der Identifikatoren und anderer Informationen der Objekte in einem Pack, um den Zugriff auf den Inhalt eines Packs zu erleichtern.
- Pathspec
-
Muster, das verwendet wird, um Pfade in Git-Befehlen zu begrenzen.
Pathspecs werden auf der Kommandozeile von "git ls-files", "git ls-tree", "git add", "git grep", "git diff", "git checkout" und vielen anderen Befehlen verwendet, um den Geltungsbereich von Operationen auf eine Teilmenge des Baumes oder des Arbeitsbaumes zu beschränken. Siehe die Dokumentation jedes Befehls, ob Pfade relativ zum aktuellen Verzeichnis oder zum Wurzelverzeichnis sind. Die Pathspec-Syntax lautet wie folgt:
-
Jeder Pfad stimmt mit sich selbst überein.
-
Der Pathspec bis zum letzten Schrägstrich repräsentiert ein Verzeichnispräfix. Der Geltungsbereich dieses Pathspecs ist auf diesen Unterbaum beschränkt.
-
Der Rest des Pathspecs ist ein Muster für den Rest des Pfadnamens. Pfade, die relativ zum Verzeichnispräfix liegen, werden mit fnmatch(3) gegen dieses Muster abgeglichen; insbesondere können * und ? Verzeichnistrenner abgleichen.
Zum Beispiel passt Documentation/*.jpg auf alle .jpg-Dateien im Documentation-Unterbaum, einschließlich Documentation/chapter_1/figure_1.jpg.
Ein Pathspec, der mit einem Doppelpunkt beginnt
:, hat eine besondere Bedeutung. In der Kurzform folgt dem führenden Doppelpunkt:null oder mehr "Magische Signatur"-Buchstaben (die optional durch einen weiteren Doppelpunkt:beendet werden), und der Rest ist das Muster, das gegen den Pfad abgeglichen wird. Die "Magische Signatur" besteht aus ASCII-Symbolen, die weder alphanumerisch, noch glob-, regex-spezielle Zeichen oder Doppelpunkte sind. Der optionale Doppelpunkt, der die "Magische Signatur" beendet, kann weggelassen werden, wenn das Muster mit einem Zeichen beginnt, das nicht zur Menge der "Magischen Signatur"-Symbole gehört und kein Doppelpunkt ist.In der langen Form folgt dem führenden Doppelpunkt
:eine öffnende Klammer (, eine durch Kommas getrennte Liste von null oder mehr "Magischen Wörtern" und eine schließende Klammer ), und der Rest ist das Muster, das gegen den Pfad abgeglichen wird.Ein Pathspec nur mit einem Doppelpunkt bedeutet "es gibt keinen Pathspec". Diese Form sollte nicht mit anderen Pathspecs kombiniert werden.
- top
-
Das magische Wort
top(magische Signatur:/) bewirkt, dass das Muster vom Stammverzeichnis des Arbeitsbaumes abgeglichen wird, auch wenn der Befehl aus einem Unterverzeichnis heraus ausgeführt wird. - literal
-
Platzhalter im Muster wie
*oder ? werden als literale Zeichen behandelt. - icase
-
Groß-/Kleinschreibung wird nicht beachtet.
- glob
-
Git behandelt das Muster als Shell-Glob, das für die Verwendung mit fnmatch(3) und dem FNM_PATHNAME-Flag geeignet ist: Platzhalter im Muster stimmen nicht mit einem / im Pfadnamen überein. Zum Beispiel stimmt "Documentation/*.html" mit "Documentation/git.html", aber nicht mit "Documentation/ppc/ppc.html" oder "tools/perf/Documentation/perf.html" überein.
Zwei aufeinanderfolgende Sternchen ("
**") in Mustern, die gegen vollständige Pfadnamen abgeglichen werden, können eine besondere Bedeutung haben-
Ein führendes "
**" gefolgt von einem Schrägstrich bedeutet Abgleich in allen Verzeichnissen. Zum Beispiel stimmt "**/foo" mit der Datei oder dem Verzeichnis "foo" überall überein. "**/foo/bar" stimmt mit der Datei oder dem Verzeichnis "bar" überall überein, das sich direkt unter dem Verzeichnis "foo" befindet. -
Ein abschließendes "
/**" stimmt mit allem im Inneren überein. Zum Beispiel stimmt "abc/**" mit allen Dateien im Verzeichnis "abc" überein, relativ zum Speicherort der Datei.gitignore, mit unendlicher Tiefe. -
Ein Schrägstrich gefolgt von zwei aufeinanderfolgenden Sternchen und dann einem Schrägstrich passt zu null oder mehr Verzeichnissen. Zum Beispiel passt "
a/**/b" zu "a/b", "a/x/b", "a/x/y/b" und so weiter. -
Andere aufeinanderfolgende Sternchen gelten als ungültig.
Glob-Magie ist mit Literal-Magie inkompatibel.
-
- attr
-
Nach
attr:folgt eine durch Leerzeichen getrennte Liste von "Attributanforderungen", die alle erfüllt sein müssen, damit der Pfad als Treffer gilt; dies gilt zusätzlich zu den üblichen nicht-magischen Pathspec-Mustervergleichen. Siehe gitattributes[5].Jede der Attributanforderungen für den Pfad hat eine der folgenden Formen:
-
"
ATTR" verlangt, dass das AttributATTRgesetzt ist. -
"
-ATTR" verlangt, dass das AttributATTRnicht gesetzt ist. -
"
ATTR=VALUE" verlangt, dass das AttributATTRauf die ZeichenketteVALUEgesetzt ist. -
"
!ATTR" verlangt, dass das AttributATTRnicht spezifiziert ist.Beachten Sie, dass bei Abgleich mit einem Baumobjekt Attribute immer noch aus dem Arbeitsverzeichnis und nicht aus dem gegebenen Baumobjekt abgerufen werden.
-
- exclude
-
Nachdem ein Pfad mit einem beliebigen Nicht-Exclude-Pathspec übereinstimmt, wird er mit allen Exclude-Pathspecs (magische Signatur:
!oder sein Synonym^) verglichen. Wenn er übereinstimmt, wird der Pfad ignoriert. Wenn kein Nicht-Exclude-Pathspec vorhanden ist, wird die Ausnahme auf das Ergebnis angewendet, als wäre sie ohne Pathspec aufgerufen worden.
-
- Elternteil
-
Ein Commit-Objekt enthält eine (möglicherweise leere) Liste der logischen Vorgänger(n) in der Entwicklungslinie, d. h. seine Eltern.
- Abziehen (Peel)
-
Die Aktion des rekursiven Dereferenzierens eines Tag-Objekts.
- Pickaxe
-
Der Begriff Pickaxe bezieht sich auf eine Option der Diffcore-Routinen, die hilft, Änderungen auszuwählen, die eine gegebene Textzeichenkette hinzufügen oder löschen. Mit der Option
--pickaxe-allkann sie verwendet werden, um die vollständige Änderungssammlung anzuzeigen, die z. B. eine bestimmte Textzeile eingefügt oder entfernt hat. Siehe git-diff[1]. - Plumbing
-
Niedliche Bezeichnung für Core Git.
- Porcelain
-
Niedliche Bezeichnung für Programme und Programmsammlungen, die von Core Git abhängen und einen hohen Zugriff auf Core Git bieten. Porcelains bieten mehr eine SCM-Schnittstelle als die Plumbing.
- pro-Worktree-Ref
-
Refs, die pro-Worktree sind, anstatt global. Dies sind derzeit nur HEAD und alle Refs, die mit
refs/bisect/beginnen, könnten aber später andere ungewöhnliche Refs enthalten. - Pseudoref
-
Eine Ref, die eine andere Semantik als normale Refs hat. Diese Refs können über normale Git-Befehle gelesen werden, aber nicht mit Befehlen wie git-update-ref[1] geschrieben werden.
Die folgenden Pseudorefs sind Git bekannt:
-
FETCH_HEADwird von git-fetch[1] oder git-pull[1] geschrieben. Es kann auf mehrere Objekt-IDs verweisen. Jede Objekt-ID ist mit Metadaten versehen, die angeben, von wo sie geholt wurde und ihr Abrufstatus. -
MERGE_HEADwird von git-merge[1] beim Auflösen von Merge-Konflikten geschrieben. Es enthält alle Commit-IDs, die gemergt werden.
-
- Pull
-
Einen Branch zu ziehen bedeutet, ihn zu fetchen und zu mergen. Siehe auch git-pull[1].
- Push
-
Einen Branch zu pushen bedeutet, die Head-Ref des Branches von einem entfernten Repository zu holen, festzustellen, ob sie ein Vorgänger der lokalen Head-Ref des Branches ist, und in diesem Fall alle Objekte, die von der lokalen Head-Ref erreichbar sind und im entfernten Repository fehlen, in die entfernte Objektdatenbank zu legen und die entfernte Head-Ref zu aktualisieren. Wenn der entfernte Head kein Vorgänger des lokalen Heads ist, schlägt der Push fehl.
- Erreichbar
-
Alle Vorgänger eines gegebenen Commits gelten als "erreichbar" von diesem Commit aus. Allgemeiner ausgedrückt ist ein Objekt von einem anderen erreichbar, wenn wir es von dem anderen durch eine Kette erreichen können, die Tags zu dem verfolgt, was sie taggen, Commits zu ihren Eltern oder Bäumen und Bäume zu den Bäumen oder Blobs, die sie enthalten.
- Erreichbarkeits-Bitmaps
-
Erreichbarkeits-Bitmaps speichern Informationen über die Erreichbarkeit einer ausgewählten Gruppe von Commits in einem Packfile oder einem Multi-Pack-Index (MIDX), um die Objektsuche zu beschleunigen. Die Bitmaps werden in einer ".bitmap"-Datei gespeichert. Ein Repository kann maximal eine Bitmap-Datei in Gebrauch haben. Die Bitmap-Datei kann entweder zu einem Pack oder zum Multi-Pack-Index des Repositories gehören (falls vorhanden).
- Rebase
-
Eine Reihe von Änderungen von einem Branch auf eine andere Basis erneut anwenden und den Head dieses Branches auf das Ergebnis setzen.
- Ref
-
Ein Name, der auf einen Objektnamen oder eine andere Ref verweist (letzteres wird als symbolische Ref bezeichnet). Zur Bequemlichkeit kann eine Ref bei der Verwendung als Argument für einen Git-Befehl manchmal abgekürzt werden; siehe gitrevisions[7] für Details. Refs werden im Repository gespeichert.
Der Ref-Namensraum ist hierarchisch. Ref-Namen müssen entweder mit
refs/beginnen oder sich im Stammverzeichnis der Hierarchie befinden. Für letzteres müssen ihre Namen die folgenden Regeln befolgen:-
Der Name besteht nur aus Großbuchstaben oder Unterstrichen.
-
Der Name endet mit "
_HEAD" oder ist gleich "HEAD".Es gibt einige unregelmäßige Refs im Stammverzeichnis der Hierarchie, die diesen Regeln nicht entsprechen. Die folgende Liste ist erschöpfend und wird in Zukunft nicht erweitert:
-
AUTO_MERGE -
BISECT_EXPECTED_REV -
NOTES_MERGE_PARTIAL -
NOTES_MERGE_REF -
MERGE_AUTOSTASHVerschiedene Unterhierarchien werden für unterschiedliche Zwecke verwendet. Zum Beispiel wird die Hierarchie
refs/heads/zur Darstellung lokaler Branches verwendet, während die Hierarchierefs/tags/zur Darstellung lokaler Tags verwendet wird.
-
- Reflog
-
Ein Reflog zeigt die lokale "Historie" einer Ref. Mit anderen Worten, es kann Ihnen sagen, was die 3. letzte Revision in *diesem* Repository war und was gestern um 21:14 Uhr der aktuelle Zustand in *diesem* Repository war. Siehe git-reflog[1] für Details.
- Refspec
-
Ein "Refspec" wird von fetch und push verwendet, um die Zuordnung zwischen der entfernten Ref und der lokalen Ref zu beschreiben. Siehe git-fetch[1] oder git-push[1] für Details.
- Entferntes Repository
-
Ein Repository, das verwendet wird, um dasselbe Projekt zu verfolgen, aber irgendwo anders liegt. Zur Kommunikation mit entfernten Repositories siehe fetch oder push.
- Remote-Tracking-Branch
-
Eine Ref, die verwendet wird, um Änderungen von einem anderen Repository zu verfolgen. Sie sieht typischerweise wie refs/remotes/foo/bar aus (was darauf hindeutet, dass sie einen Branch namens *bar* in einem Remote namens *foo* verfolgt) und entspricht der rechten Seite eines konfigurierten Fetch-Refspecs. Ein Remote-Tracking-Branch sollte keine direkten Änderungen enthalten oder lokale Commits darauf gemacht haben.
- Repository
-
Eine Sammlung von Refs zusammen mit einer Objektdatenbank, die alle Objekte enthält, die von den Refs erreichbar sind, möglicherweise ergänzt durch Metadaten aus einem oder mehreren Porcelains. Ein Repository kann sich eine Objektdatenbank mit anderen Repositories über den Alternates-Mechanismus teilen.
- Auflösen (Resolve)
-
Die Aktion, manuell zu beheben, was ein fehlgeschlagener automatischer Merge hinterlassen hat.
- Revision
-
Synonym für Commit (das Nomen).
- Zurückspulen (Rewind)
-
Einen Teil der Entwicklung verwerfen, d. h. den Head auf eine frühere Revision setzen.
- SCM
-
Quellcodeverwaltung (Werkzeug).
- SHA-1
-
"Secure Hash Algorithm 1"; eine kryptografische Hashfunktion. Im Kontext von Git als Synonym für Objektname verwendet.
- Shallow Clone
-
Größtenteils ein Synonym für Shallow Repository, aber die Phrase macht deutlicher, dass es durch Ausführen des Befehls
gitclone--depth=...erstellt wurde. - Shallow Repository
-
Ein flaches Repository hat eine unvollständige Historie, von der einige Commits Eltern "versengt" haben (mit anderen Worten, Git wird angewiesen, so zu tun, als hätten diese Commits keine Eltern, obwohl sie im Commit-Objekt aufgezeichnet sind). Dies ist manchmal nützlich, wenn Sie nur an der jüngsten Historie eines Projekts interessiert sind, obwohl die tatsächliche Historie, die im Upstream aufgezeichnet ist, viel größer ist. Ein flaches Repository wird erstellt, indem die Option
--depthfür git-clone[1] angegeben wird, und seine Historie kann später mit git-fetch[1] vertieft werden. - Stash-Eintrag
-
Ein Objekt, das verwendet wird, um den Inhalt eines schmutzigen Arbeitsverzeichnisses und des Index vorübergehend für eine spätere Wiederverwendung zu speichern.
- Submodul
-
Ein Repository, das die Historie eines separaten Projekts innerhalb eines anderen Repositories (letzteres wird als Superprojekt bezeichnet) enthält.
- Superprojekt
-
Ein Repository, das Repositories anderer Projekte in seinem Arbeitsverzeichnis als Submodule referenziert. Das Superprojekt kennt die Namen von (hält aber keine Kopien von) Commit-Objekten der enthaltenen Submodule.
- Symref
-
Symbolische Referenz: Anstatt die SHA-1 ID selbst zu enthalten, hat sie das Format ref: refs/some/thing und wird beim Referenzieren rekursiv zu dieser Referenz dereferenziert. HEAD ist ein Paradebeispiel für eine Symref. Symbolische Referenzen werden mit dem Befehl git-symbolic-ref[1] bearbeitet.
- Tag
-
Eine Ref im Namensraum
refs/tags/, die auf ein Objekt eines beliebigen Typs verweist (typischerweise verweist ein Tag entweder auf einen Tag oder ein Commit-Objekt). Im Gegensatz zu einem Head wird ein Tag nicht durch den Befehlcommitaktualisiert. Ein Git-Tag hat nichts mit einem Lisp-Tag zu tun (was im Git-Kontext als Objekttyp bezeichnet würde). Ein Tag wird am häufigsten verwendet, um einen bestimmten Punkt in der Commit-Abstammungslinie Kette zu markieren. - Tag-Objekt
-
Ein Objekt, das eine Ref enthält, die auf ein anderes Objekt verweist, welches eine Nachricht ähnlich wie ein Commit-Objekt enthalten kann. Es kann auch eine (PGP-)Signatur enthalten, in diesem Fall wird es als "signiertes Tag-Objekt" bezeichnet.
- Themen-Branch
-
Ein regulärer Git-Branch, der von einem Entwickler verwendet wird, um eine konzeptionelle Entwicklungslinie zu identifizieren. Da Branches sehr einfach und kostengünstig sind, ist es oft wünschenswert, mehrere kleine Branches zu haben, die jeweils sehr klar definierte Konzepte oder kleine inkrementelle, aber zusammenhängende Änderungen enthalten.
- Trailer
-
Schlüssel-Wert-Metadaten. Trailer finden sich optional am Ende einer Commit-Nachricht. Können in anderen Gemeinschaften als "Footer" oder "Tags" bezeichnet werden. Siehe git-interpret-trailers[1].
- Baum (Tree)
-
Entweder ein Arbeitsbaum oder ein Baum-Objekt zusammen mit den abhängigen Blob- und Baumobjekten (d. h. eine gespeicherte Darstellung eines Arbeitsbaumes).
- Baum-Objekt
-
Ein Objekt, das eine Liste von Dateinamen und Modi zusammen mit Referenzen auf die zugehörigen Blob- und/oder Baumobjekte enthält. Ein Baum entspricht einem Verzeichnis.
- Tree-ish (auch treeish)
-
Ein Baum-Objekt oder ein Objekt, das rekursiv zu einem Baum-Objekt dereferenziert werden kann. Das Dereferenzieren eines Commit-Objekts ergibt das Baum-Objekt, das dem obersten Verzeichnis der Revision entspricht. Die folgenden sind alle Tree-ishes: ein Commit-ish, ein Baum-Objekt, ein Tag-Objekt, das auf ein Baum-Objekt verweist, ein Tag-Objekt, das auf ein Tag-Objekt verweist, das auf ein Baum-Objekt verweist, usw.
- Unborn
-
Der HEAD kann auf einen Branch zeigen, der noch nicht existiert und auf dem noch kein Commit liegt, und ein solcher Branch wird als unborn Branch bezeichnet. Die häufigste Art, wie Benutzer auf einen unborn Branch stoßen, ist das Erstellen eines neuen Repositories, ohne von woanders zu klonen. Der HEAD würde auf den *main* (oder *master*, je nach Ihrer Konfiguration) Branch zeigen, der noch geboren werden muss. Einige Operationen können Sie auch mit ihrer Orphan-Option auf einen unborn Branch bringen.
- Unmerged Index
-
Ein Index, der nicht zusammengeführte Index-Einträge enthält.
- Unerreichbares Objekt
-
Ein Objekt, das nicht von einem Branch, Tag oder einer anderen Referenz erreichbar ist.
- Upstream-Branch
-
Der Standard-Branch, der in den fraglichen Branch gemergt wird (oder auf den der fragliche Branch rebased wird). Er wird über branch.<name>.remote und branch.<name>.merge konfiguriert. Wenn der Upstream-Branch von *A* *origin/B* ist, sagen wir manchmal "A verfolgt origin/B".
- Arbeitsbaum
-
Der Baum der tatsächlich ausgecheckten Dateien. Der Arbeitsbaum enthält normalerweise den Inhalt des Baumes des HEAD-Commits plus alle lokalen Änderungen, die Sie vorgenommen, aber noch nicht committet haben.
- Worktree
-
Ein Repository kann null (d. h. ein Bare-Repository) oder ein oder mehrere Worktrees haben, die daran angehängt sind. Ein "Worktree" besteht aus einem "Arbeitsbaum" und Repository-Metadaten, von denen die meisten zwischen anderen Worktrees eines einzelnen Repositories geteilt werden, und einige separat pro Worktree gepflegt werden (z. B. der Index, HEAD und Pseudorefs wie MERGE_HEAD, pro-Worktree-Refs und die pro-Worktree-Konfigurationsdatei).
Anhang A: Git-Schnellübersicht
Dies ist eine schnelle Zusammenfassung der wichtigsten Befehle; die vorherigen Kapitel erklären, wie diese im Detail funktionieren.
Erstellen eines neuen Repositories
Aus einem Tarball
$ tar xzf project.tar.gz $ cd project $ git init Initialized empty Git repository in .git/ $ git add . $ git commit
Aus einem entfernten Repository
$ git clone git://example.com/pub/project.git $ cd project
Branches verwalten
$ git branch # list all local branches in this repo $ git switch test # switch working directory to branch "test" $ git branch new # create branch "new" starting at current HEAD $ git branch -d new # delete branch "new"
Anstatt einen neuen Branch auf dem aktuellen HEAD (Standard) zu basieren, verwenden Sie
$ git branch new test # branch named "test" $ git branch new v2.6.15 # tag named v2.6.15 $ git branch new HEAD^ # commit before the most recent $ git branch new HEAD^^ # commit before that $ git branch new test~10 # ten commits before tip of branch "test"
Einen neuen Branch erstellen und gleichzeitig wechseln
$ git switch -c new v2.6.15
Branches aus dem Repository, das Sie geklont haben, aktualisieren und untersuchen
$ git fetch # update $ git branch -r # list origin/master origin/next ... $ git switch -c masterwork origin/master
Einen Branch aus einem anderen Repository fetchen und ihm einen neuen Namen in Ihrem Repository geben
$ git fetch git://example.com/project.git theirbranch:mybranch $ git fetch git://example.com/project.git v2.6.15:mybranch
Eine Liste der Repositories führen, mit denen Sie regelmäßig arbeiten
$ git remote add example git://example.com/project.git
$ git remote # list remote repositories
example
origin
$ git remote show example # get details
* remote example
URL: git://example.com/project.git
Tracked remote branches
master
next
...
$ git fetch example # update branches from example
$ git branch -r # list all remote branches
Historie erkunden
$ gitk # visualize and browse history $ git log # list all commits $ git log src/ # ...modifying src/ $ git log v2.6.15..v2.6.16 # ...in v2.6.16, not in v2.6.15 $ git log master..test # ...in branch test, not in branch master $ git log test..master # ...in branch master, but not in test $ git log test...master # ...in one branch, not in both $ git log -S'foo()' # ...where difference contain "foo()" $ git log --since="2 weeks ago" $ git log -p # show patches as well $ git show # most recent commit $ git diff v2.6.15..v2.6.16 # diff between two tagged versions $ git diff v2.6.15..HEAD # diff with current head $ git grep "foo()" # search working directory for "foo()" $ git grep v2.6.15 "foo()" # search old tree for "foo()" $ git show v2.6.15:a.txt # look at old version of a.txt
Nach Regressionen suchen
$ git bisect start $ git bisect bad # current version is bad $ git bisect good v2.6.13-rc2 # last known good revision Bisecting: 675 revisions left to test after this # test here, then: $ git bisect good # if this revision is good, or $ git bisect bad # if this revision is bad. # repeat until done.
Änderungen vornehmen
Sicherstellen, dass Git weiß, wer die Schuld trägt
$ cat >>~/.gitconfig <<\EOF [user] name = Your Name Comes Here email = you@yourdomain.example.com EOF
Dateiinhalte für den nächsten Commit auswählen, dann den Commit machen
$ git add a.txt # updated file $ git add b.txt # new file $ git rm c.txt # old file $ git commit
Oder, den Commit vorbereiten und in einem Schritt erstellen
$ git commit d.txt # use latest content only of d.txt $ git commit -a # use latest content of all tracked files
Mergen
$ git merge test # merge branch "test" into the current branch $ git pull git://example.com/project.git master # fetch and merge in remote branch $ git pull . test # equivalent to git merge test
Ihre Änderungen teilen
Patches importieren oder exportieren
$ git format-patch origin..HEAD # format a patch for each commit # in HEAD but not in origin $ git am mbox # import patches from the mailbox "mbox"
Einen Branch in einem anderen Git-Repository fetchen, dann in den aktuellen Branch mergen
$ git pull git://example.com/project.git theirbranch
Den geholten Branch vor dem Mergen in den aktuellen Branch in einem lokalen Branch speichern
$ git pull git://example.com/project.git theirbranch:mybranch
Nachdem Commits auf einem lokalen Branch erstellt wurden, den entfernten Branch mit Ihren Commits aktualisieren
$ git push ssh://example.com/project.git mybranch:theirbranch
Wenn der entfernte und der lokale Branch beide "test" heißen
$ git push ssh://example.com/project.git test
Kurzform für ein häufig verwendetes entferntes Repository
$ git remote add example ssh://example.com/project.git $ git push example test
Anhang B: Notizen und To-Do-Liste für dieses Handbuch
To-Do-Liste
Dies ist ein laufendes Projekt.
Die grundlegenden Anforderungen
-
Es muss in Ordnung gelesen werden können, von Anfang bis Ende, von jemandem, der intelligent ist und ein grundlegendes Verständnis der UNIX-Kommandozeile hat, aber ohne spezielles Wissen über Git. Wenn nötig, sollten alle anderen Voraussetzungen spezifisch erwähnt werden, wenn sie auftreten.
-
Wo immer möglich, sollten Abschnittsüberschriften klar die Aufgabe beschreiben, die sie erklären, wie man sie erledigt, in einer Sprache, die nicht mehr Wissen als nötig erfordert: zum Beispiel, "Patches in ein Projekt importieren" anstatt "der
gitamBefehl"
Denken Sie darüber nach, wie Sie einen klaren Abhängigkeitsgraphen von Kapiteln erstellen können, der es den Leuten ermöglicht, wichtige Themen zu erreichen, ohne unbedingt alles dazwischen lesen zu müssen.
Scannen Sie Documentation/ nach anderem ausgelassenem Material; insbesondere
-
Anleitungen
-
Einiges von
technical/? -
Hooks
-
Liste der Befehle in git[1]
Scannen Sie E-Mail-Archive nach anderem ausgelassenem Material
Scannen Sie Manpages, um zu sehen, ob einige mehr Hintergrundwissen voraussetzen, als dieses Handbuch bietet.
Fügen Sie mehr gute Beispiele hinzu. Ganze Abschnitte mit nur Kochbuchbeispielen könnten eine gute Idee sein; vielleicht einen Abschnitt "Fortgeschrittene Beispiele" als Standardabschnitt am Ende jedes Kapitels machen?
Fügen Sie Querverweise zum Glossar hinzu, wo angebracht.
Fügen Sie einen Abschnitt über die Arbeit mit anderen Versionskontrollsystemen hinzu, einschließlich CVS, Subversion und einfach nur Importen von Release-Tarballs.
Schreiben Sie ein Kapitel über die Verwendung von Plumbing und das Schreiben von Skripten.
Alternates, clone -reference, etc.
Mehr zur Wiederherstellung von Repository-Beschädigungen. Siehe: https://lore.kernel.org/git/Pine.LNX.4.64.0702272039540.12485@woody.linux-foundation.org/ https://lore.kernel.org/git/Pine.LNX.4.64.0702141033400.3604@woody.linux-foundation.org/