English ▾ Themen ▾ Neueste Version ▾ gittutorial-2 zuletzt aktualisiert in 2.23.0

NAME

gittutorial-2 - Eine Einführung in Git: Teil Zwei

SYNOPSIS

git *

BESCHREIBUNG

Sie sollten gittutorial[7] durcharbeiten, bevor Sie dieses Tutorial lesen.

Das Ziel dieses Tutorials ist es, zwei grundlegende Bestandteile der Git-Architektur vorzustellen – die Objektdatenbank und die Indexdatei – und dem Leser alles Notwendige an die Hand zu geben, um den Rest der Git-Dokumentation zu verstehen.

Die Git-Objektdatenbank

Lassen Sie uns ein neues Projekt starten und eine kleine Historie erstellen

$ mkdir test-project
$ cd test-project
$ git init
Initialized empty Git repository in .git/
$ echo 'hello world' > file.txt
$ git add .
$ git commit -a -m "initial commit"
[master (root-commit) 54196cc] initial commit
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt
$ echo 'hello world!' >file.txt
$ git commit -a -m "add emphasis"
[master c4d59f3] add emphasis
 1 file changed, 1 insertion(+), 1 deletion(-)

Was sind die 7 Hex-Ziffern, mit denen Git auf den Commit geantwortet hat?

Wir haben in Teil Eins des Tutorials gesehen, dass Commits Namen wie diesen haben. Es stellt sich heraus, dass jedes Objekt in der Git-Historie unter einem 40-stelligen Hex-Namen gespeichert wird. Dieser Name ist der SHA-1-Hash des Inhalts des Objekts; unter anderem stellt dies sicher, dass Git niemals dieselben Daten doppelt speichert (da identische Daten einen identischen SHA-1-Namen erhalten) und dass der Inhalt eines Git-Objekts niemals geändert wird (da dies den Namen des Objekts ebenfalls ändern würde). Die 7 Zeichen langen Hex-Strings hier sind einfach die Abkürzung solcher 40 Zeichen langen Strings. Abkürzungen können überall dort verwendet werden, wo die 40 Zeichen langen Strings verwendet werden können, solange sie eindeutig sind.

Es ist zu erwarten, dass der Inhalt des Commit-Objekts, das Sie beim Befolgen des obigen Beispiels erstellt haben, einen anderen SHA-1-Hash generiert als der oben gezeigte, da das Commit-Objekt den Zeitpunkt seiner Erstellung und den Namen der Person, die den Commit durchführt, aufzeichnet.

Wir können Git mit dem Befehl cat-file nach diesem speziellen Objekt fragen. Kopieren Sie nicht die 40 Hex-Ziffern aus diesem Beispiel, sondern verwenden Sie die aus Ihrer eigenen Version. Beachten Sie, dass Sie sie auf nur wenige Zeichen kürzen können, um sich das Tippen aller 40 Hex-Ziffern zu ersparen

$ git cat-file -t 54196cc2
commit
$ git cat-file commit 54196cc2
tree 92b8b694ffb1675e5975148e1121810081dbdffe
author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500

initial commit

Ein Baum kann sich auf ein oder mehrere "Blob"-Objekte beziehen, die jeweils einer Datei entsprechen. Darüber hinaus kann ein Baum auch auf andere Baum-Objekte verweisen und so eine Verzeichnisstruktur erstellen. Sie können den Inhalt eines jeden Baumes mit ls-tree untersuchen (denken Sie daran, dass ein ausreichend langer Anfangsteil des SHA-1 ebenfalls funktioniert)

$ git ls-tree 92b8b694
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    file.txt

Somit sehen wir, dass dieser Baum eine Datei enthält. Der SHA-1-Hash ist ein Verweis auf die Daten dieser Datei

$ git cat-file -t 3b18e512
blob

Ein "Blob" ist nur Dateidaten, die wir auch mit cat-file untersuchen können

$ git cat-file blob 3b18e512
hello world

Beachten Sie, dass dies die alten Dateidaten sind; das Objekt, das Git in seiner Antwort auf den ersten Baum benannt hat, war also ein Baum mit einem Schnappschuss des Verzeichniszustands, der durch den ersten Commit aufgezeichnet wurde.

Alle diese Objekte werden unter ihren SHA-1-Namen im Git-Verzeichnis gespeichert

$ find .git/objects/
.git/objects/
.git/objects/pack
.git/objects/info
.git/objects/3b
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/92
.git/objects/92/b8b694ffb1675e5975148e1121810081dbdffe
.git/objects/54
.git/objects/54/196cc2703dc165cbd373a65a4dcf22d50ae7f7
.git/objects/a0
.git/objects/a0/423896973644771497bdc03eb99d5281615b51
.git/objects/d0
.git/objects/d0/492b368b66bdabf2ac1fd8c92b39d3db916e59
.git/objects/c4
.git/objects/c4/d59f390b9cfd4318117afde11d601c1085f241

und der Inhalt dieser Dateien ist nur die komprimierten Daten plus ein Header, der ihre Länge und ihren Typ angibt. Der Typ ist entweder ein Blob, ein Baum, ein Commit oder ein Tag.

Der einfachste zu findende Commit ist der HEAD-Commit, den wir über .git/HEAD finden können

$ cat .git/HEAD
ref: refs/heads/master

Wie Sie sehen können, teilt uns dies mit, auf welchem Zweig wir uns gerade befinden, und dies geschieht durch die Benennung einer Datei im .git-Verzeichnis, die selbst einen SHA-1-Namen enthält, der sich auf ein Commit-Objekt bezieht, das wir mit cat-file untersuchen können

$ cat .git/refs/heads/master
c4d59f390b9cfd4318117afde11d601c1085f241
$ git cat-file -t c4d59f39
commit
$ git cat-file commit c4d59f39
tree d0492b368b66bdabf2ac1fd8c92b39d3db916e59
parent 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500

add emphasis

Das "tree"-Objekt hier verweist auf den neuen Zustand des Baumes

$ git ls-tree d0492b36
100644 blob a0423896973644771497bdc03eb99d5281615b51    file.txt
$ git cat-file blob a0423896
hello world!

und das "parent"-Objekt verweist auf den vorherigen Commit

$ git cat-file commit 54196cc2
tree 92b8b694ffb1675e5975148e1121810081dbdffe
author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500

initial commit

Das Baumobjekt ist der Baum, den wir zuerst untersucht haben, und dieser Commit ist ungewöhnlich, da er keine Eltern hat.

Die meisten Commits haben nur einen Elternteil, aber es ist auch üblich, dass ein Commit mehrere Elternteile hat. In diesem Fall repräsentiert der Commit einen Merge, wobei die Elternreferenzen auf die Köpfe der zusammengeführten Branches zeigen.

Neben Blobs, Bäumen und Commits ist der einzige verbleibende Objekttyp ein "Tag", den wir hier nicht besprechen werden; siehe git-tag[1] für Details.

Somit wissen wir nun, wie Git die Objektdatenbank zur Darstellung der Historie eines Projekts verwendet

  • "commit"-Objekte verweisen auf "tree"-Objekte, die den Schnappschuss eines Verzeichnisbaums zu einem bestimmten Zeitpunkt in der Historie darstellen, und verweisen auf "parent"-Commits, um zu zeigen, wie sie in die Projekt-Historie eingebunden sind.

  • "tree"-Objekte stellen den Zustand eines einzelnen Verzeichnisses dar und ordnen Verzeichnisnamen "blob"-Objekten mit Dateidaten und "tree"-Objekten mit Unterverzeichnissen zu.

  • "blob"-Objekte enthalten Dateidaten ohne weitere Struktur.

  • Referenzen auf Commit-Objekte an der Spitze jedes Branches werden in Dateien unter .git/refs/heads/ gespeichert.

  • Der Name des aktuellen Branches wird in .git/HEAD gespeichert.

Beachten Sie übrigens, dass viele Befehle einen Baum als Argument entgegennehmen. Aber wie wir oben sehen können, kann ein Baum auf verschiedene Arten referenziert werden – über den SHA-1-Namen dieses Baumes, über den Namen eines Commits, der auf den Baum verweist, über den Namen eines Branches, dessen Kopf auf diesen Baum verweist, usw. – und die meisten solchen Befehle können jede dieser Namen akzeptieren.

In Befehlssynopsen wird das Wort "tree-ish" manchmal verwendet, um ein solches Argument zu bezeichnen.

Die Indexdatei

Das Hauptwerkzeug, das wir zum Erstellen von Commits verwendet haben, ist git-commit -a, das einen Commit erstellt, der alle Änderungen in Ihrem Arbeitsverzeichnis enthält. Aber was ist, wenn Sie nur Änderungen an bestimmten Dateien committen möchten? Oder nur bestimmte Änderungen an bestimmten Dateien?

Wenn wir uns ansehen, wie Commits im Hintergrund erstellt werden, werden wir sehen, dass es flexiblere Wege gibt, Commits zu erstellen.

Setzen wir unser Testprojekt fort, und modifizieren wir file.txt erneut

$ echo "hello world, again" >>file.txt

aber diesmal, anstatt sofort den Commit zu machen, machen wir einen Zwischenschritt und bitten um Diffs, um den Überblick zu behalten, was passiert

$ git diff
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world!
+hello world, again
$ git add file.txt
$ git diff

Der letzte Diff ist leer, aber es wurden keine neuen Commits gemacht, und der HEAD enthält immer noch nicht die neue Zeile

$ git diff HEAD
diff --git a/file.txt b/file.txt
index a042389..513feba 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world!
+hello world, again

Also vergleicht *git diff* etwas anderes als den HEAD. Das, womit es vergleicht, ist tatsächlich die Indexdatei, die in .git/index im Binärformat gespeichert ist, deren Inhalt wir aber mit ls-files untersuchen können

$ git ls-files --stage
100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
$ git cat-file -t 513feba2
blob
$ git cat-file blob 513feba2
hello world!
hello world, again

Was unser *git add* also getan hat, war, einen neuen Blob zu speichern und dann eine Referenz darauf in die Indexdatei zu legen. Wenn wir die Datei erneut modifizieren, werden wir sehen, dass die neuen Modifikationen in der *git diff*-Ausgabe reflektiert werden

$ echo 'again?' >>file.txt
$ git diff
index 513feba..ba3da7b 100644
--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,3 @@
 hello world!
 hello world, again
+again?

Mit den richtigen Argumenten kann *git diff* uns auch den Unterschied zwischen dem Arbeitsverzeichnis und dem letzten Commit oder zwischen dem Index und dem letzten Commit anzeigen

$ git diff HEAD
diff --git a/file.txt b/file.txt
index a042389..ba3da7b 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,3 @@
 hello world!
+hello world, again
+again?
$ git diff --cached
diff --git a/file.txt b/file.txt
index a042389..513feba 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world!
+hello world, again

Zu jedem Zeitpunkt können wir einen neuen Commit mit *git commit* (ohne die Option "-a") erstellen und verifizieren, dass der committete Zustand nur die im Index gespeicherten Änderungen enthält, nicht die zusätzliche Änderung, die sich noch nur in unserem Arbeitsverzeichnis befindet

$ git commit -m "repeat"
$ git diff HEAD
diff --git a/file.txt b/file.txt
index 513feba..ba3da7b 100644
--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,3 @@
 hello world!
 hello world, again
+again?

Standardmäßig verwendet *git commit* den Index zur Erstellung des Commits, nicht das Arbeitsverzeichnis; die Option "-a" zu commit weist es an, den Index zuerst mit allen Änderungen im Arbeitsverzeichnis zu aktualisieren.

Schließlich lohnt es sich, die Auswirkung von *git add* auf die Indexdatei zu betrachten

$ echo "goodbye, world" >closing.txt
$ git add closing.txt

Die Auswirkung von *git add* war, einen Eintrag zur Indexdatei hinzuzufügen

$ git ls-files --stage
100644 8b9743b20d4b15be3955fc8d5cd2b09cd2336138 0       closing.txt
100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt

Und, wie Sie mit cat-file sehen können, verweist dieser neue Eintrag auf den aktuellen Inhalt der Datei

$ git cat-file blob 8b9743b2
goodbye, world

Der Befehl "status" ist eine nützliche Möglichkeit, eine schnelle Zusammenfassung der Situation zu erhalten

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)

	new file:   closing.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)

	modified:   file.txt

Da der aktuelle Zustand von closing.txt im Index zwischengespeichert ist, wird er als "Changes to be committed" aufgeführt. Da file.txt Änderungen im Arbeitsverzeichnis hat, die sich nicht im Index widerspiegeln, wird es als "changed but not updated" markiert. Zu diesem Zeitpunkt würde die Ausführung von "git commit" einen Commit erstellen, der closing.txt (mit seinem neuen Inhalt) hinzufügt, aber file.txt nicht modifiziert.

Beachten Sie auch, dass ein reines git diff die Änderungen an file.txt anzeigt, aber nicht die Hinzufügung von closing.txt, da die Version von closing.txt in der Indexdatei mit der im Arbeitsverzeichnis identisch ist.

Neben der Tatsache, dass die Indexdatei als Staging-Bereich für neue Commits dient, wird sie auch beim Auschecken eines Branches aus der Objektdatenbank gefüllt und verwendet, um die an einem Merge-Vorgang beteiligten Bäume zu speichern. Siehe gitcore-tutorial[7] und die entsprechenden Manpages für Details.

Was nun?

An diesem Punkt sollten Sie alles wissen, was notwendig ist, um die Manpages für jeden der Git-Befehle zu lesen; ein guter Ausgangspunkt wären die Befehle, die in giteveryday[7] erwähnt werden. Sie sollten in gitglossary[7] unbekannte Fachbegriffe finden können.

Das Git User's Manual bietet eine umfassendere Einführung in Git.

gitcvs-migration[7] erklärt, wie man ein CVS-Repository in Git importiert und zeigt, wie man Git auf CVS-ähnliche Weise verwendet.

Einige interessante Beispiele für die Git-Nutzung finden Sie in den howtos.

Für Git-Entwickler geht gitcore-tutorial[7] detailliert auf die Mechanismen auf niedrigerer Ebene von Git ein, die beispielsweise bei der Erstellung eines neuen Commits eine Rolle spielen.

GIT

Teil der git[1] Suite