Kapitel 4. Verpflichtet

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Ein Commit ist ein Snapshot, der den aktuellen Zustand eines Repositorys zu einem bestimmten Zeitpunkt festhält. Commit-Snapshots werden miteinander verknüpft, wobei jeder neue Snapshot auf seinen Vorgänger verweist. Im Laufe der Zeit wird eine Folge von Änderungen als eine Reihe von Commits dargestellt.

Git verwendet einen Commit, um Änderungen an einem Repository festzuhalten. Auf den ersten Blick ist ein Git-Commit vergleichbar mit einem Check-in oder Commit in anderen Versionskontrollsystemen. Die Ähnlichkeiten sind jedoch nur an der Oberfläche zu sehen. Unter der Haube ist die Art und Weise, wie Git Commits erstellt und verwaltet, völlig einzigartig.

Wenn du einen Commit machst, erstellt Git einen Snapshot des aktuellen Zustands des Indexverzeichnisses und speichert ihn im Objektspeicher, wie in Kapitel 2 kurz beschrieben. Der Snapshot enthält keine Kopie aller Dateien und Verzeichnisse im Index. Stattdessen vergleicht Git den aktuellen Zustand des Index mit dem vorherigen Commit-Snapshot und erstellt daraus eine Liste der betroffenen Dateien und Verzeichnisse, wenn du einen neuen Commit erstellst. Anhand dieser Liste erstellt Git neue Blob-Objekte für jede geänderte Datei und neue Tree-Objekte für jedes geänderte Verzeichnis und verwendet alle Blob- oder Tree-Objekte, die sich nicht geändert haben, wieder.

Wie du das Indexverzeichnis für einen Commit vorbereitest, besprechen wir in Kapitel 5. In diesem Kapitel konzentrieren wir uns darauf, was passiert, wenn du einen Commit machst. Zuerst werden wir uns ansehen, wie Commits eingeführt werden und die Bedeutung von atomaren Changesets verstehen. Dann schauen wir uns an, wie man Commits identifiziert und wie man Commit-Historien anschaut.

Git eignet sich gut für häufige Commits und bietet eine Vielzahl von Befehlen, um sie zu bearbeiten. Wir zeigen dir, wie mehrere Commits mit jeweils kleinen, klar definierten Änderungen auch zu einer besseren Organisation der Änderungen und einer einfacheren Handhabung von Patch-Sets führen können.

Commits: Aufgezeichnete Einheiten der Veränderung

Ein Commit ist die einzige Methode, um Änderungen in einem Repository vorzunehmen. Dieses Mandat sorgt für Nachvollziehbarkeit und Verantwortlichkeit. Unter keinen Umständen dürfen Daten im Projektarchiv geändert werden, ohne dass die Änderung durch einen Commit festgehalten wird! Stell dir das Chaos vor, wenn sich Inhalte im Projektarchiv ändern und es keine Aufzeichnungen darüber gibt, wie das passiert ist, wer das gemacht hat und warum.

Commits werden explizit von Entwicklern eingeführt; das ist das typischste Szenario. Es gibt aber auch Fälle, in denen Git selbst einen Commit einführt. Ein Merge-Vorgang zum Beispiel erzeugt einen neuen Commit im Repository, zusätzlich zu den Commits, die die Entwickler vor dem Merge gemacht haben. Mehr dazu erfährst du in Kapitel 6.

Die Häufigkeit, mit der du Commits erstellst, ist so ziemlich dir überlassen. Logischerweise solltest du einen Commit zu genau definierten Zeitpunkten einführen, wenn sich deine Entwicklung in einem fortgeschrittenen Stadium befindet, z. B. wenn alle Testsuiten bestanden sind.

Man könnte meinen, es wäre zeitaufwändig den gesamten Index mit einem früheren Zustand zu vergleichen, doch der gesamte Prozess ist bemerkenswert schnell. Das liegt daran, dass, wie du dich vielleicht aus Kapitel 2 erinnerst, jedes Git-Objekt einen SHA1-Hash hat, und wenn zwei Objekte, sogar zwei Teilbäume, denselben SHA1-Hash haben, sind die zu vergleichenden Objekte identisch. So kann Git viele rekursive Vergleiche vermeiden, indem es Teilbäume mit gleichem Inhalt ausschneidet.

Das sollte aber nicht bedeuten, dass du zögern solltest, regelmäßig Commits einzuführen.

Atomare Changesets

Jeder Git-Commit ist ein einzelner atomarer Änderungssatz gegenüber dem vorherigen Zustand. Unabhängig von der Anzahl der Verzeichnisse, Dateien, Zeilen oder Bytes, die sich bei einem Commit ändern,1 gelten entweder alle Änderungen oder keine.

Aus der Perspektive des zugrunde liegenden Git-Objektmodells wird der Grund für die Atomisierung klarer. Ein Commit-Snapshot repräsentiert den Zustand aller geänderten Dateien und Verzeichnisse, d.h. er repräsentiert auch einen bestimmten Baumzustand. Ein Changeset zwischen zwei Snapshots stellt also eine vollständige Umwandlung von einem Baumzustand in einen anderen dar. Auch hier gilt, dass du nur von einem Zustand in den anderen wechseln kannst; inkrementelle Wechsel sind nicht möglich. Wie du die Unterschiede zwischen Commits ableiten kannst, wird in Kapitel 7 erläutert.

Als Entwickler ist dies ein wichtiges Prinzip, das du nicht untergraben solltest. Betrachte den folgenden Arbeitsablauf beim Verschieben einer Funktion von einer Datei in eine andere. Wenn du die Funktion mit einem Commit aus der ersten Datei entfernst und sie dann mit einem weiteren Commit zur zweiten Datei hinzufügst, bleibt eine kleine "semantische Lücke" in der Geschichte deines Projektarchivs, in der die Funktion verschwunden ist. Zwei Commits, die in umgekehrter Reihenfolge durchgeführt werden, sind ebenfalls problematisch. In jedem Fall ist dein Code vor dem ersten und nach dem zweiten Commit semantisch konsistent, aber nach dem ersten Commit ist der Code fehlerhaft.

Bei einem atomaren Commit, der die Funktion gleichzeitig aus der ersten Datei löscht und der zweiten Datei hinzufügt, entsteht jedoch keine solche semantische Lücke in der Historie. Wenn du dieses Konzept verstehst, kannst du deine Commits sinnvoller strukturieren.

Git ist es egal , warum Dateien geändert werden. Das heißt, der Inhalt der Änderungen spielt keine Rolle. Du könntest eine Funktion von einer Datei in eine andere verschieben und erwarten, dass dies als eine einheitliche Verschiebung behandelt wird. Du kannst aber auch erst das Entfernen und später das Hinzufügen übertragen. Git kümmert das nicht. Es hat nichts mit der Semantik der Dateien zu tun.

Identifizierung von Commits

Jeder Commit in Git kann explizit oder implizit referenziert werden. Die Möglichkeit, einzelne Commits zu identifizieren, ist eine wichtige Aufgabe für deine alltäglichen Entwicklungsanforderungen. Um einen Branch zu erstellen, musst du zum Beispiel einen Commit auswählen, von dem abgewichen werden soll; um Codevariationen zu vergleichen, musst du zwei Commits angeben; und um die Commit-Historie zu bearbeiten, musst du eine Sammlung von Commits angeben.

Wenn du einen Commit explizit referenzierst, referenzierst du ihn mit seinen absoluten Commit-Namen, und wenn du einen Commit implizit referenzierst, tust du das mit seinen Refs, Symrefs oder relativen Commit-Namen.

Du hast bereits Beispiele für explizite Commit-Referenzen und implizite Commit-Referenzen in den Codeschnipseln in den Kapiteln 1 bis 3 gesehen. Die eindeutige, 40-stellige hexadezimale SHA1-Commit-ID ist eine explizite Referenz, während HEAD, die immer auf den neuesten Commit in einem Zweig verweist, eine implizite Referenz ist; siehe Tabelle 4-1.

Tabelle 4-1. Unterschied zwischen expliziten und impliziten Commits
Explizit Implizit

Identifiziert über

Absoluter Commit-Name

Refs, Symrefs, relative Commit-Namen

Beispiel

34043c95636aee319d606a7a380697cae4f1bfcc

HEAD, HEAD^2, etc.

Wenn du einen bestimmten Commit mit einem Kollegen besprichst, der an denselben Daten arbeitet, aber in einer verteilten Umgebung, ist es manchmal am besten, einen Commit-Namen zu verwenden, der garantiert in beiden Repositories gleich ist. Wenn du hingegen in deinem eigenen Repository arbeitest und dich auf den Zustand eines Zweigs beziehen musst, der ein paar Commits zurückliegt, funktioniert ein einfacher relativer Name perfekt.

Glücklicherweise bietet Git viele verschiedene Mechanismen, um einen Commit zu benennen. Jeder hat seine Vorteile und manche sind je nach Kontext nützlicher als andere.

Absolute Commit-Namen

Der strengste Name für einen Commit ist seine Objekt-ID, die SHA1-Hash-Kennung. Die SHA1-Hash-ID ist ein absoluter Name, das heißt, sie kann sich nur auf genau eine Übertragung beziehen. Es spielt keine Rolle, wo sich der Commit in der Historie des Repositorys befindet; die SHA1-Hash-ID verweist immer auf denselben Commit und identifiziert ihn.

Jede Commit-ID ist global eindeutig, nicht nur für ein Repository, sondern für alle Repositories. Wenn du einen Verweis auf eine bestimmte Commit-ID in deinem Repository mit dem Repository eines anderen Entwicklers vergleichst und dieselbe Commit-ID gefunden wird, kannst du sicher sein, dass ihr beide denselben Commit und Inhalt habt.

Da die Daten, die zu einer Commit-ID beitragen, den Zustand des gesamten Repository-Baums sowie den vorherigen Commit-Zustand enthalten, kannst du außerdem sicher sein, dass ihr beide auf dieselbe komplette Entwicklungslinie verweist, die zu dem Commit geführt hat.

Da eine 40-stellige hexadezimale SHA1-Nummer eine mühsame und fehleranfällige Eingabe ist, ermöglicht Git die Verkürzung dieser Nummer auf ein eindeutiges Präfix innerhalb der Objektdatenbank eines Repositorys. Werfen wir einen Blick auf ein Beispiel aus dem Git-Repository:

   $ git log -1 --pretty=oneline HEAD
   30cc8d0f147546d4dd77bf497f4dec51e7265bd8 ... A regression fix for 2.37

   $ git log -1 --pretty=oneline 30c
   fatal: ambiguous argument '30c': unknown revision or path not in the working tree.
   Use '--' to separate paths from revisions, like this:
   'git <command> [<revision>...] -- [<file>...]'

   $ git log -1 --pretty=oneline 30cc8d
   a5828ae6b52137b913b978e16cd2334482eb4c1f ... A regression fix for 2.37
   
Hinweis

Ein Tag-Name ist zwar kein global eindeutiger Name, aber er ist absolut, da er auf einen eindeutigen Commit verweist und sich im Laufe der Zeit nicht ändert (es sei denn, du änderst ihn explizit, natürlich).

Referenzen und Symrefs

Ein ref verweist auf eine SHA1-Hash-ID im Git-Objektspeicher. Technisch gesehen kann eine einfache Referenz auf ein beliebiges Git-Objekt verweisen, aber im Allgemeinen bezieht sie sich auf ein Commit-Objekt. Ein symbolischer Verweis, oder symref, ist ein Name, der indirekt auf ein Git-Objekt verweist. Sie ist sozusagen eine Abkürzung, die auf das eigentliche Git-Objekt verweist. Es ist trotzdem nur eine Referenz.

Jede symbolische Referenz hat einen eindeutigen, vollständigen Namen, der mit refs/ beginnt, und jede wird hierarchisch im Repository im Verzeichnis .git/refs/ gespeichert. Grundsätzlich gibt es drei verschiedene Namensräume, die in refs/ vertreten sind:

  • refs/heads/ref für deine Filialen vor Ort

  • refs/remotes/ref für deine Remote-Tracking-Zweige

  • refs/tags/ref für deine Tags

Lokale Zweignamen, Remote-Tracking-Zweignamen und Tag-Namen sind einige Beispiele für Referenzen. Ein Beispiel: Ein lokaler Feature-Zweig mit dem Namen dev ist in Wirklichkeit eine Kurzform von refs/heads/dev. Während Remote-Tracking-Zweige im Namensraum refs/remotes/ liegen, ist origin/main eine Kurzform von refs/remotes/origin/main. Schließlich ist ein Tag wie v1.8.17 eine Kurzform von refs/tags/v1.8.17.

Wenn du eine Referenz suchst oder referenzierst, kannst du den voll qualifizierten Referenznamen (refs/heads/main) oder seine Abkürzung (main) verwenden. Für den Fall, dass du nach einem Branch suchst und ein Tag mit demselben Namen existiert, wendet Git eine Disambiguierungsheuristik an und verwendet die erste Übereinstimmung gemäß dieser Liste aus der Manpage git rev-parse:

   .git/ref
   .git/refs/ref
   .git/refs/tags/ref
   .git/refs/heads/ref
   .git/refs/remotes/ref
   .git/refs/remotes/ref/HEAD
   

Die erste passende Regel (.git/ref) wird normalerweise von Git intern verwendet. Sie lauten , , , und . HEAD ORIG_HEAD FETCH_HEAD CHERRY_PICK_HEAD MERGE_HEAD

Hinweis

Technisch gesehen, kann der Name des Git-Verzeichnisses, .git, geändert werden. Daher wird in der internen Dokumentation von Git die Variable $GIT_DIR anstelle der wörtlichen Bezeichnung .git.

Git verwaltet intern die folgenden Symrefs aus bestimmten Gründen automatisch:

HEAD

HEAD bezieht sich immer auf den neuesten Commit des aktuellen Zweigs. Wenn du den Zweig wechselst, aktualisiert Git automatisch HEAD und verweist auf den letzten Commit des neuen Zweigs.

ORIG_HEAD

Bestimmte Vorgänge, wie z.B. das Zusammenführen und Zurücksetzen, zeichnen die vorherige Version von HEAD in ORIG_HEAD auf, bevor sie auf einen neuen Wert angepasst wird. Du kannst ORIG_HEAD verwenden, um den vorherigen Zustand wiederherzustellen oder um einen Vergleich anzustellen.

FETCH_HEAD

Wenn Remote-Repositories verwendet werden, speichert git fetch die Köpfe aller Zweige, die in die Datei .git/FETCH_HEAD geholt wurden, unter. FETCH_HEAD ist eine Abkürzung für den Kopf des zuletzt geholten Zweigs und gilt nur unmittelbar nach einem Fetch-Vorgang. Mit dieser Symref kannst du die HEAD der Commits von git fetch auch dann finden, wenn ein anonymer Fetch verwendet wird, der nicht ausdrücklich einen Zweig nennt. Die Operation fetch wird in Kapitel 11 behandelt.

MERGE_HEAD

Wenn eine Zusammenführung im Gange ist, wird der Hinweis auf den anderen Zweig vorübergehend in der Symref MERGE_HEAD festgehalten. Mit anderen Worten: MERGE_HEAD ist der Commit, der mit HEAD zusammengeführt wird.

CHERRY_PICK_HEAD

Wenn du das Cherry-Picking über den git cherry-pick Befehl verwendest, werden in der CHERRY_PICK_HEAD symref die Commits aufgezeichnet, die du für die geplante Operation ausgewählt hast. Der Befehl git cherry-pick wird in Kapitel 8 behandelt.

Alle diese symbolischen Verweise werden mit dem Low-Level-Splinting-Befehl git symbolic-ref verwaltet.

Warnung

Obwohl es möglich ist, einen eigenen Zweig mit einem dieser speziellen symbolischen Namen zu erstellen, ist das keine gute Idee. Außerdem gibt es in neueren Versionen von Git Schutzmechanismen, die verhindern, dass du bestimmte symbolische Namen verwendest (z. B. HEAD).

Es gibt eine ganze Reihe von Sonderzeichenvarianten für ref-Namen. Die beiden häufigsten, das Caret (^) und die Tilde (~), werden im nächsten Abschnitt beschrieben. Außerdem können Doppelpunkte verwendet werden, um auf alternative Versionen einer gemeinsamen Datei zu verweisen, die an einem Zusammenführungskonflikt beteiligt sind. Dieses Verfahren wird in Kapitel 6 beschrieben.

Relative Commit-Namen

Git verwendet nicht nur absolute Commit-Namen, und Refs sowie Symrefs, sondern bietet auch Mechanismen, um einen Commit relativ zu einer anderen Referenz zu identifizieren, in der Regel die Spitze eines Zweigs. Das ist besonders praktisch, wenn du an deinem lokalen Repository arbeitest und schnell auf Änderungen in früheren Commits verweisen musst.

Wiederum hast du einige dieser Namen schon gesehen, wie main und main^`, wobei main^ sich immer auf den vorletzten Commit im main Zweig bezieht. Es gibt aber auch noch andere: main^^, main~2 und sogar einen komplexen Namen wie main~10^2~2^2.

Mit Ausnahme des ersten oder Root-Commits,2 ist jeder Commit von mindestens einem früheren Commit abgeleitet, möglicherweise sogar von mehreren, wobei die direkten Vorfahren als Eltern-Commits bezeichnet werden. Damit ein Commit mehrere Parent Commits haben kann, muss er das Ergebnis einer Merge-Operation sein. Folglich gibt es für jeden Zweig, der zu einem Merge-Commit beiträgt, einen Parent-Commit.

Innerhalb einer Generation wird das Caret verwendet, um ein anderes Elternteil auszuwählen. Bei einem Commit C ist C^1 der erste Parent, C^2 der zweite Parent und C^n der n^th^ Parent, wie in Abbildung 4-1 dargestellt.

vcg3 0401
Abbildung 4-1. Mehrere übergeordnete Namen

Die Tilde wird verwendet, um vor zurückzugehen und eine vorangehende Generation auszuwählen. Bei C ist C~1 der erste Elternteil, C~2 ist der erste Großelternteil und C~3 ist der erste Urgroßelternteil. Wenn es mehrere Elternteile in einerGeneration gibt, wird das erste Elternteil des ersten Elternteils gewählt. Du wirst feststellen, dass sich sowohl C^1 als auch C~1 auf den ersten Elternteil beziehen; beide Namen sind korrekt, wie in Abbildung 4-2 gezeigt.

vcg3 0402
Abbildung 4-2. Mehrere übergeordnete Namen mit Vorfahren

Git unterstützt auch andere Abkürzungen und Kombinationen. Die abgekürzten Formen C^ und C~ sind dasselbe wie C^1 bzw. C~1. Außerdem ist C^^; dasselbe wie C^1^1, und weil das "der erste Elternteil des ersten Elternteils von Commit C" bedeutet, bezieht es sich auf denselben Commit wie C~2.

Beachte, dass ein abgekürzter Ausdruck wie C^ oder C^^ bei einer Zusammenführungsoperation möglicherweise nicht das erwartete Ergebnis liefert, wie es der Fall wäre, wenn du an einem Zweig mit einer linearen Commit-Historie arbeitest. Abbildung 4-3 veranschaulicht, dass C^^ nicht dasselbe ist wie C^2.

vcg3 0403
Abbildung 4-3. C^^ versus C^2

Durch die Kombination eines ref und Instanzen von Carets und Tildes können beliebige Commits aus dem angestammten Commit-Graphen von ref. Erinnere dich aber daran, dass diese Namen relativ zum aktuellen Wert von ref. Wenn ein neuer Commit auf der Basis von refwird der Commit-Graph um eine neue Generation ergänzt, und jeder "Eltern"-Name verschiebt sich in der Historie und im Graph weiter nach hinten.

Hier ist ein Beispiel aus der Git-Historie, als der Zweig main bei Commit a5828ae6b52137b913b978e16cd2334482eb4c1f war. Benutze den Befehl:

    git show-branch --more=25
    

und die Ausgabe auf die letzten 25 Zeilen beschränken, kannst du den Verlauf des Graphen einsehen und eine komplexe Struktur der Verzweigungszusammenführung untersuchen:

   $ git reset --hard a5828ae6b52137b913b978e16cd2334482eb4c1f
   HEAD is now at a5828ae6b5 Git 2.31

   $ git show-branch --more=25 
   [main] Git 2.31
   [main^] Merge branch 'jn/mergetool-hideresolved-is-optional'
   [main^^2] doc: describe mergetool configuration in git-mergetool(1)
   [main^^2^] mergetool: do not enable hideResolved by default
   [main^^2~2] mergetool: add per-tool support and overrides for the hideResolved flag
   [main~2] Merge branch 'tb/pack-revindex-on-disk'
   [main~2^2] pack-revindex.c: don't close unopened file descriptors
   [main~3] Merge tag 'l10n-2.31.0-rnd2' of git://github.com/git-l10n/git-po
   [main~3^2] l10n: zh_CN: for git v2.31.0 l10n round 1 and 2
   [main~3^2^] Merge branch 'master' of github.com:vnwildman/git
   [main~3^2^^2] l10n: vi.po(5104t): for git v2.31.0 l10n round 2
   [main~3^2~2] Merge branch 'l10n/zh_TW/210301' of github.com:l10n-tw/git-po
   [main~3^2~2^2] l10n: zh_TW.po: v2.31.0 round 2 (15 untranslated)
   [main~3^2~3] Merge branch 'po-id' of github.com:bagasme/git-po
   [main~3^2~3^2] l10n: Add translation team info
   [main~3^2~4] Merge branch 'master' of github.com:Softcatala/git-po
   [main~3^2~4^2] l10n: Update Catalan translation
   [main~3^2~5] Merge branch 'russian-l10n' of github.com:DJm00n/git-po-ru
   [main~3^2~5^2] l10n: ru.po: update Russian translation
   [main~3^2~6] Merge branch 'pt-PT' of github.com:git-l10n-pt-PT/git-po
   [main~3^2~6^2] l10n: pt_PT: add Portuguese translations part 1
   [main~3^2~7] l10n: de.po: Update German translation for Git v2.31.0
   [main~4] Git 2.31-rc2
   [main~5] Sync with Git 2.30.2 for CVE-2021-21300
   [main~5^2] Git 2.30.2
   [main~6] Merge branch 'jt/transfer-fsck-across-packs-fix'


   $ git rev-parse main~3^2~2^
   8278f870221711c2116d3da2a0165ab00368f756
   

Zwischen main~3 und main~4 fand ein Merge statt, der ein paar andere Merges sowie einen einfachen Commit namens main~3^2~2^2 mit sich brachte. Das ist zufällig der Commit 8278f870221711c2116d3da2a0165ab00368f756.

Der Befehl git rev-parse ist die letzte Instanz für die Übersetzung jeder Form von Commit-Namen - Tag, relativ, verkürzt oder absolut - in eine tatsächliche, absolute Commit-Hash-ID innerhalb der Objektdatenbank.

Geschichte verpflichten

Der wichtigste Befehl, um die Historie von Commits anzuzeigen, ist git log. Er hat mehr Optionen, Parameter, Schnickschnack, Colorizer, Selektoren, Formatierer und Spielereien als das sagenumwobene ls. Aber keine Sorge. Genau wie bei ls musst du nicht gleich alle Details lernen. Als Nächstes werden wir in die Ecken und Winkel der Commit-Historie eines Repositorys eintauchen.

Alte Commits ansehen

Wenn du den Befehl git log ausführst, wird die Ausgabe jede zugehörige Übergabe und die dazugehörigen Logmeldungen in deinem Übergabeprotokoll enthalten, die vom angegebenen Startpunkt aus erreichbar sind.

Wenn du zum Beispiel den Befehl git log ohne zusätzliche Optionen ausführst, ist das dasselbe wie die Ausführung des Befehls git log HEAD. Das Ergebnis ist eine Ausgabe aller Commits ab dem Commit HEAD und aller erreichbaren Commits weit zurück im Commit-Graphen (Commit-Graphen werden im folgenden Abschnitt behandelt). Die Ergebnisse werden standardmäßig in umgekehrter chronologischer Reihenfolge angezeigt. Beachte jedoch, dass sich Git bei der Durchsicht deiner Commit-Historie in umgekehrter Reihenfolge an den Commit-Graphen hält und nicht an den Zeitpunkt, zu dem der Snapshot erstellt wurde.

Die Angabe des Startpunkts für die Protokollausgabe mit dem Befehl git log commit kann nützlich sein, um den Verlauf eines Zweigs zu sehen. Verwenden wir den Befehl, um eine Beispielausgabe aus dem Git-Repository selbst zu sehen:

   $ git log main -2
   commit 30cc8d0f147546d4dd77bf497f4dec51e7265bd8 (HEAD -> main, ...)
   Author: Junio C Hamano <gitster@pobox.com>
   Date:   Sat Jul 2 17:01:34 2022 -0700

       A regression fix for 2.37

       Signed-off-by: Junio C Hamano <gitster@pobox.com>

   commit 0f0bc2124b25476504e7215dc2af92d5748ad327
   Merge: e4a4b31577 4788e8b256
   Author: Junio C Hamano <gitster@pobox.com>
   Date:   Sat Jul 2 21:56:08 2022 -0700

       Merge branch 'js/add-i-delete'

       Rewrite of "git add -i" in C that appeared in Git 2.25 didn't
       correctly record a removed file to the index, which was fixed.

       * js/add-i-delete:
         add --interactive: allow `update` to stage deleted files
   

Log-Informationen sind eine verlässliche Quelle der Wahrheit. Ein Rollback durch die gesamte Commit-Historie eines großen Repositorys ist jedoch wahrscheinlich nicht sehr praktisch oder sinnvoll. In der Regel ist ein begrenzter Bereich der Historie informativer und einfacher zu bearbeiten.

Eine Möglichkeit, den Verlauf einzuschränken, ist die Angabe eines Übergabebereichs, eine Technik, die wir später in diesem Kapitel behandeln. Du kannst den Bereich der Historie einschränken, indem du die Form since..until. Wenn du einen Bereich angibst, zeigt git log alle Commits an, die auf since folgen und durchlaufen until. Du kannst auch eine Anzahl als natürlichen Startpunkt angeben, zum Beispiel git log -3.

Hier ist ein Beispiel:

   $ git log --pretty=short --abbrev-commit main~9..main~7

   commit be7935ed8b
   Author: Junio C Hamano <gitster@pobox.com>

       Merged the open-eintr workaround for macOS

   commit 58d581c344
   Author: Elijah Newren <newren@gmail.com>

       Documentation/RelNotes: improve release note for rename detection work
   

Hier zeigt git log die Commits zwischen main~9 und main~7, also den siebten und achten vorherigen Commit auf dem Hauptzweig. Mehr über Ranges erfährst du in "Commit Ranges".

In unserem Beispiel haben wir zwei Formatierungsoptionen eingeführt, --pretty=short und --abbrev-commit. Die erste Option passt die Menge der Informationen über jede Übergabe an und hat mehrere Varianten, darunter oneline, short, medium und full, um nur einige zu nennen. Die zweite Option verlangt lediglich, dass die SHA1-Hash-IDs abgekürzt werden.

Du kannst auch mit der Option format:string kannst du auch festlegen, wie die Protokollinformationen angepasst und angezeigt werden sollen.

Hier ist ein Beispiel:

   $ git log --pretty=format:"%an was the author of commit %h, %ar with%nthe commit titled: [%s]%n" \
   > --abbrev-commit main~9..main~7

   Junio C Hamano was the author of commit be7935ed8b, 12 days ago with
   the commit titled: [Merged the open-eintr workaround for macOS]

   Elijah Newren was the author of commit 58d581c344, 12 days ago with
   the commit titled: [Documentation/RelNotes: improve release note for rename detection work]
   

Du kannst die Option -n zusammen mit dem Befehl git log angeben, um die Ausgabe auf maximal n Commits. Dadurch wird die Ausgabe auf die angegebene Anzahl beschränkt. Wenn du die Option -p kombinierst, werden außerdem der Patch oder die Änderungen, die durch den Commit eingeführt wurden, ausgegeben, während die Ergebnismengen eingeschränkt werden. Auf diese Weise erhältst du mehr Details zu einem Commit, da du mehr Kontext erhältst.

Hier ist ein Beispiel:

   $ git log -1 -p 4fe86488
   commit 4fe86488e1a550aa058c081c7e67644dd0f7c98e
   Author: Jon Loeliger <jdl@freescale.com>
   Date:   Wed Apr 23 16:14:30 2008 -0500

       Add otherwise missing --strict option to unpack-objects summary.

       Signed-off-by: Jon Loeliger <jdl@freescale.com>
       Signed-off-by: Junio C Hamano <gitster@pobox.com>

   diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt
   index 3697896..50947c5 100644
   --- a/Documentation/git-unpack-objects.txt
   +++ b/Documentation/git-unpack-objects.txt
   @@ -8,7 +8,7 @@ git-unpack-objects - Unpack objects from a packed archive

    SYNOPSIS
    --------
   -'git-unpack-objects' [-n] [-q] [-r] <pack-file
   +'git-unpack-objects' [-n] [-q] [-r] [--strict] <pack-file
   

Wenn du wissen möchtest, welche Dateien sich bei einem Commit geändert haben und wie viele Zeilen in jeder Datei geändert wurden, ist die Option --stat die beste Wahl für dich.

Hier ist ein Beispiel:

   $ git log --pretty=short --stat main~9..main~7

   commit be7935ed8bff19f481b033d0d242c5d5f239ed50
   Author: Junio C Hamano <gitster@pobox.com>

       Merged the open-eintr workaround for macOS

   Documentation/RelNotes/2.31.0.txt | 5 +++++
   1 file changed, 5 insertions(+)

   commit 58d581c3446cb616b216307d6b47539bccd494cf
   Author: Elijah Newren <newren@gmail.com>

       Documentation/RelNotes: improve release note for rename detection work

   Documentation/RelNotes/2.31.0.txt | 2 +-
   1 file changed, 1 insertion(+), 1 deletion(-)
   
Tipp

Ein weiterer Befehl zum Anzeigen von Objekten aus dem Git-Objektspeicher ist git show. Du kannst ihn auch verwenden, um einen Commit zu überprüfen:

    $ git show HEAD~2
    

Die Ausgabe zeigt die Commit-Log-Meldung und den textuellen Diff für die Dateien, die in den Commits enthalten sind.

Commit-Diagramme

Bislang haben wir den Befehl git log mit Optionen verwendet, die die Ergebnisse in einem linearen Format anzeigen. Obwohl es nützlich ist, die Commit-Historie zu verstehen, die zu einem bestimmten Punkt in der Zeitachse eines Projekts führt, ist es in dieser flachen Ansicht nicht immer klar, dass zwei aufeinanderfolgende Commits nicht zu einem einzigen Branch gehören müssen.

Die Option --graph in Kombination mit dem Befehl git log gibt eine textuelle Darstellung der Commit-Historie des Repositorys aus. In dieser Ansicht kannst du die Forks eines Commits und den Punkt, an dem Zweige in der Zeitleiste des Repositorys zusammengeführt werden, visualisieren.

Im Folgenden findest du eine vereinfachte Commit-Historie für den Git-Quellcode aus der Anfangszeit:


   $ git log 89d21f4b649..0a02ce72d9 --oneline --graph
   * 0a02ce72d9 Clean up the Makefile a bit.
   * 839a7a06f3 Add the simple scripts I used to do a merge with content conflicts.
   *   b51ad43140 Merge the new object model thing from Daniel Barkalow
   |\
   | * b5039db6d2 [PATCH] Switch implementations of merge-base, port to parsing
   | * ff5ebe39b0 [PATCH] Port fsck-cache to use parsing functions
   | * 5873b67eef [PATCH] Port rev-tree to parsing functions
   | * 175785e5ff [PATCH] Implementations of parsing functions
   | * 6eb8ae00d4 [PATCH] Header files for object parsing
   * | a4b7dbef4e [PATCH] fix bug in read-cache.c which loses files when merging...
   * | 1bc992acac [PATCH] Fix confusing behaviour of update-cache --refresh on...
   * | 6ad6d3d36c Update README to reflect the hierarchical tree objects...
   * | 64982f7510 [PATCH] (resend) show-diff.c off-by-one fix
   * | 75118b13bc Pass a "merge-cache" helper program to execute a merge on...
   * | 74b2428f55 [PATCH] fork optional branch point normazilation
   * | d9f98eebcd Ignore any unmerged entries for "checkout-cache -a".
   * | 5e5128ed1c Remove extraneous ',' ';' and '.' characters from...
   * | 08ca0b04ba Make the revision tracking track the object types too.
   * | d0d7cbe730 Make "commit-tree" check the input objects more carefully.
   * | 7d60ad7cc9 Make "parse_commit" return the "struct revision" for the commit.
   |/
   * 6683463ed6 Do a very simple "merge-base" that finds the most recent...
   * 15000d7899 Make "rev-tree.c" use the new-and-improved "mark_reachable()"
   * 01796b0e91 Make "revision.h" slightly better to use.
   
Hinweis

Die Option --oneline ist eine Abkürzung für --pretty=oneline--abbrev-commit zusammen verwendet.

Die Commit-Historie des Repositorys wird normalerweise als Diagramm dargestellt3 dargestellt, um bei der Diskussion bestimmter Git-Befehle als Referenz zu dienen. Meistens geht es dabei um Vorgänge, die die Commit-Historie des Repositorys verändern können. Das Umschreiben von Commit-Historien wird in Kapitel 8 behandelt.

Wenn du dich erinnerst, haben wir im Abschnitt "Visualisierung des Git-Objektspeichers" eine Abbildung eingefügt, die dir hilft, das Layout und die Beziehungen zwischen den Objekten im Git-Objektspeicher zu visualisieren. Diese Abbildung wird hier als Abbildung 4-4 wiedergegeben.

vcg3 0404
Abbildung 4-4. Git-Objekte

Wenn wir die Commit-Historie eines Repositorys mit Hilfe von Abbildung 4-4 abbilden würden, wäre die Abbildung selbst für ein kleines Repository mit nur einer Handvoll Commits, Merges und Patches zu unhandlich, um sie in dieser Detailtiefe darzustellen. Abbildung 4-5 zeigt ein vollständigeres, aber immer noch etwas vereinfachtes Commit-Diagramm im gleichen Format. Stell dir vor, wie es aussehen würde, wenn alle Commits und alle Datenstrukturen dargestellt würden.

Abbildung 4-6 zeigt das gleiche Commit-Diagramm wie Abbildung 4-5, jedoch ohne die Baum- und Blob-Objekte. Die Zweignamen werden auch in den Commit-Diagrammen als Referenz angezeigt.

Wir können Abbildung 4-5 noch weiter vereinfachen, indem wir eine wichtige Beobachtung über Commits machen: Jeder Commit führt ein Baumobjekt ein, das auf ein oder mehrere Blob-Objekte verweist und den gesamten Zustand des Repositorys repräsentiert, als ein Commit-Snapshot gemacht wurde. Ein Commit kann also einfach als Name dargestellt werden, was die Darstellung der Commit-Historie des Repositorys enorm vereinfacht.

Die Abbildungen 4-5 und 4-6 sind Beispiele für einen gerichteten azyklischen Graphen (DAG). Ein DAG hat die folgenden wichtigen Eigenschaften:

  • Die Kanten innerhalb des Graphen sind alle von einem Knoten zum anderen gerichtet.

  • Ausgehend von einem beliebigen Knoten im Graphen gibt es keinen Pfad entlang der gerichteten Kanten, der zum Startknoten zurückführt.

vcg3 0405
Abbildung 4-5. Vollständiges Commit-Diagramm
vcg3 0406
Abbildung 4-6. Vereinfachter Commit-Graph

Git implementiert die Historie der Commits innerhalb eines Repositorys als DAG. Im Commit-Graphen steht jeder Knoten für einen einzelnen Commit, und alle Kanten sind von einem Nachfolgeknoten zu einem anderen Elternknoten gerichtet und bilden eine Ahnenbeziehung. Die einzelnen Commit-Knoten werden oft wie in Abbildung 4-7 dargestellt beschriftet und dienen dazu, die Geschichte der Commits und die Beziehung zwischen ihnen zu beschreiben.

vcg3 0407
Abbildung 4-7. Beschrifteter Commit-Graph

Ein wichtiger Aspekt einer DAG ist, dass Git sich nicht um die Zeit oder den Zeitpunkt (absolut oder relativ) von Commits kümmert. Der tatsächliche Zeitstempel eines Commits kann irreführend sein, weil die Uhr eines Computers falsch oder uneinheitlich eingestellt sein kann. In einer verteilten Entwicklungsumgebung wird das Problem noch verschärft. Sicher ist jedoch, dass, wenn der Commit Y auf den Parent X verweist, X den Repository-Status vor dem Repository-Status des Commits Y erfasst, unabhängig davon, welche Zeitstempel die Commits haben mögen.

Aufbauend auf diesem Konzept zeigt Abbildung 4-7 das Folgende:

  • Die Zeit ist ungefähr von links nach rechts.

  • A ist der Root-Commit, weil er keinen Parent hat und B nach A entstanden ist.

  • Sowohl E als auch C traten nach B auf, aber es kann keine Aussage über den relativen Zeitpunkt zwischen C und E gemacht werden; beide könnten vor den anderen stattgefunden haben.

  • Die Commits E und C haben einen gemeinsamen Parent, B. Daher ist B der Ursprung eines Zweigs.

  • Der Zweig main beginnt mit den Commits A, B, C und D.

  • In der Zwischenzeit bildet die Abfolge der Commits A, B, E, F und G den Zweig namens pr-17. Der Zweig pr-17 verweist auf den Commit G, wie in Kapitel 3 beschrieben.

  • Commit H ist ein Merge-Commit, bei dem der Zweig pr-17 mit dem Zweig main zusammengeführt wurde. Die Merge-Operation wird in Kapitel 6 ausführlicher behandelt.

  • Da es sich um eine Zusammenführung handelt, hat H mehr als einen übergeordneten Commit - in diesem Fall D und G.

  • Nachdem dieser Commit durchgeführt wurde, wird main aktualisiert, um auf den neuen Commit H zu verweisen, aber pr-17 wird weiterhin auf G verweisen.

Mit der Zeit, wenn du viele DAG-Diagramme von Commits lernst und referenzierst, wirst du bald ein wiederkehrendes Muster erkennen:

  • Normale Commits haben genau einen Parent, nämlich den vorherigen Commit in der Historie. Wenn du eine Änderung vornimmst, ist deine Änderung die Differenz zwischen deinem neuen Commit und seinem Parent.

  • Normalerweise gibt es nur einen Commit mit null Eltern: den Root-Commit.

  • Ein Merge-Commit hat mehr als einen Eltern-Commit.

  • Ein Commit mit mehr als einem Kind ist der Ort, an dem die Geschichte begann, auseinanderzugehen und einen neuen Zweig zu bilden.

In der Praxis werden die Feinheiten der dazwischen liegenden Commits als unwichtig erachtet. Außerdem wird das Implementierungsdetail eines Commits, der auf seinen Elternteil zurückverweist, oft weggelassen, wie in Abbildung 4-8 gezeigt.

vcg3 0408
Abbildung 4-8. Commit-Graph ohne Pfeile

Die Zeit ist immer noch vage von links nach rechts, zwei Zweige werden angezeigt und es gibt eine identifizierte Zusammenführungsübergabe (H), aber die tatsächlichen gerichteten Kanten sind vereinfacht, weil sie implizit verstanden werden.

Die Commit-Graphen sind eine ziemlich abstrakte Darstellung der tatsächlichen Commit-Historie, im Gegensatz zu Tools, die konkrete Darstellungen von Commit-Historiengraphen liefern. Bei diesen Tools wird die Zeit in der Regel von unten nach oben dargestellt, vom ältesten bis zum neuesten Commit. Vom Konzept her handelt es sich um dieselbe Information. Im nächsten Abschnitt werden wir kurz auf das frei verfügbare Tool gitk eingehen.4 Neben gitk gibt es eine Reihe weiterer Tools, die entweder als kostenpflichtige oder kostenlose Version erhältlich sind.

Gitk verwenden, um den Commit-Graph anzuzeigen

Ein Commit-Graph kann dir helfen, eine komplizierte Struktur und Beziehung zu visualisieren. Der Befehl gitk kann ein Bild eines Repository-DAGs zeichnen, das die Commit-Historie des Repositorys darstellt.

Schauen wir uns ein Beispiel für ein einfaches Repository mit zwei Zweigen und einfachen Commits zum Hinzufügen von Dateien an:

   $ mkdir commit-graph-repo
   # Operations to add new files, create new branch and merge branch
   ...
   ...
   $ gitk
   

Das Programm gitk kann eine Menge Dinge tun, aber konzentrieren wir uns erst einmal auf die DAG. Die Ausgabe des Graphen sieht ungefähr so aus wie in Abbildung 4-9.

vcg3 0409
Abbildung 4-9. Zusammenführen gesehen mit gitk
Tipp

Es gibt keine permanenten Aufzeichnungen über die Startpunkte der Verzweigungen, aber Git kann sie algorithmisch über denBefehl git merge-baseermitteln.

Commit-Bereiche

Der Befehl git log bearbeitet eine Reihe von Commits und ist einer von vielen Befehlen, mit denen du die Commit-Historie deines Repositorys durchlaufen kannst. Wenn du den Commit Y als Startpunkt für git log angibst, forderst du Git auf, das Protokoll für alle Commits anzuzeigen, die ab Commit Y erreichbar sind.

In einem Git-Commit-Graphen ist die Menge der erreichbaren Commits die Menge der Commits, die du von einem bestimmten Commit aus erreichen kannst, indem du die gerichteten Elternverbindungen durchläufst. In Bezug auf den Datenfluss ist die Menge der erreichbaren Commits die Menge der Vorgänger-Commits, die in einen bestimmten Ausgangs-Commit einfließen und zu diesem beitragen.

Hinweis

In der Graphentheorie wird ein Knoten X als von einem anderen Knoten A aus erreichbar bezeichnet, wenn du von A aus die Bögen des Graphen gemäß den Regeln durchlaufen und zu X gelangen kannst.

In Git gibt es mehrere Optionen, mit denen du Commits innerhalb eines bestimmten Bereichs ein- und ausschließen kannst. Diese Optionen sind nicht auf den Befehl git log beschränkt, sondern gelten auch für andere unterstützte Git-Unterbefehle. Du kannst zum Beispiel einen bestimmten Commit X und alle Commits, die von X aus erreichbar sind, mit dem Ausdruck ^X ausschließen. Normalerweise wird ein Bereich verwendet, um einen Branch oder einen Teil eines Branches zu untersuchen.

Ein Bereich wird mit einem Begriff mit doppeltem Punkt (..) bezeichnet, wie in start..end, wobei start und end wie in "Identifizierung von Commits" beschrieben angegeben werden können .

Ein Commit-Bereich, start..endist definiert als die Menge der Commits inklusive end und ausschließlich von start. In der Regel wird dies vereinfacht durch den Satz "in end aber nicht start." Abbildung 4-11 enthält eine illustrierte Erklärung.

In "Alte Commits ansehen" hast du gesehen, wie du mit git log einen Commit-Bereich verwenden kannst. In dem Beispiel wurde der Bereich main~9..main~7 verwendet, um den achten und siebten früheren Commit auf dem Hauptzweig anzugeben. Um den Bereich zu visualisieren, betrachte das Commit-Diagramm in Abbildung 4-10. Der Zweig M wird über einen Teil seiner linearen Commit-Historie dargestellt.

vcg3 0410
Abbildung 4-10. Lineare Commit-Historie

Erinnere dich daran, dass die Zeit von links nach rechts fließt. M~11 ist also der älteste gezeigte Commit, M~6 ist der jüngste gezeigte Commit und A ist der achte vorherige Commit.

Der Bereich M~9..M~7 repräsentiert zwei Commits, den achten und siebten ältesten Commit, die mit A und B bezeichnet sind. Der Bereich umfasst nicht M~9 (erinnere dich an die Formulierung "in M~7, aber nicht M~9").

Um auf die Commit-Serie aus dem vorherigen Beispiel zurückzukommen: Als Set-Operation betrachtet, legt M~9..M~7 nur zwei Commits fest, A und B:

  1. Beginne mit allem, was zu M~7 führt, wie in der ersten Zeile von Abbildung 4-11 gezeigt.

    1. Finde alles, was bis einschließlich M~9 führt, wie in der zweiten Zeile der Abbildung gezeigt.

      1. Ziehe M~9 von M~7 ab, um die Commits zu erhalten, die in der dritten Zeile der Abbildung angezeigt werden.

vcg3 0411
Abbildung 4-11. Interpretation von Bereichen als Subtraktionsmenge

Wenn deine Repository-Historie eine einfache lineare Reihe von Commits ist, ist es ziemlich einfach zu verstehen, wie ein Bereich funktioniert. Wenn aber Zweige oder Zusammenführungen in den Graphen einbezogen werden, kann es etwas knifflig werden, deshalb ist es wichtig, die strenge Definition zu verstehen.

Schauen wir uns ein paar weitere Beispiele an. Beachte, dass diese Beispiele nur abstrakte Darstellungen sind und einfach und leicht verständlich sein sollen. Die Operation merge wird zur Unterstützung des Konzepts verwendet, und ihre technischen Aspekte werden in Kapitel 6 ausführlich beschrieben.

Wir beginnen mit dem Fall eines main Zweigs mit einer linearen Geschichte, wie in Abbildung 4-12 gezeigt. Die Mengen B..E, ^B E und C, D und E sind gleichwertig.

vcg3 0412
Abbildung 4-12. Einfache lineare Geschichte

In Abbildung 4-13 wurde der Zweig main mit der Übergabe V mit dem Zweig feature unter B zusammengeführt.

vcg3 0413
Abbildung 4-13. Der Zweigmain wurde mit dem Zweig feature zusammengeführt.

Der Bereich feature..main steht für die Commits in main, aber nicht in feature. Da jeder Commit auf dem Zweig main vor und einschließlich V (d.h. die Menge {..., T, U, V}) zu feature beiträgt, werden diese Commits ausgeschlossen, sodass W, X, Y und Z übrig bleiben.

Die Umkehrung des vorherigen Beispiels wird in Abbildung 4-14 gezeigt. Hier wurde feature mit main zusammengeführt.

vcg3 0414
Abbildung 4-14. Der Zweigfeature wurde mit dem Zweig main zusammengeführt.

In diesem Beispiel ist der Bereich feature..main, der wiederum die Commits in main, aber nicht in feature repräsentiert, die Menge der Commits auf dem Zweig main, die zu V, W, X, Y und Z führen und diese einschließen.

Wir müssen jedoch ein wenig vorsichtig sein und die gesamte Geschichte des feature Zweigs betrachten. Betrachten wir den Fall, dass er ursprünglich als Zweig von main begann und dann wieder zusammengeführt wurde, wie in Abbildung 4-15 gezeigt.

vcg3 0415
Abbildung 4-15. Ein Zweig und eine Zusammenführung

In diesem Fall enthält feature..main nur die Commits W, X, Y, und Z. Erinnere dich daran, dass der Bereich alle Commits ausschließt, die von feature aus erreichbar sind (d.h. die Commits D, C, B, A und früher), sowie V, U und früher vom anderen Elternteil von B. Das Ergebnis ist nur W bis Z.

Und schließlich, genau wie start..end als eine Subtraktionsoperation für eine Menge angesehen werden kann, die Notation A…​B (mit drei Punkten) steht für die symmetrische Differenz zwischen A und Boder die Menge der Commits, die entweder von A oder B aber nicht von beiden. Aufgrund der Symmetrie der Funktion kann keiner der beiden Commits wirklich als Anfang oder Ende betrachtet werden. In diesem Sinne, A und B gleich.

Genauer gesagt, die Menge der Revisionen in der symmetrischen Differenz zwischen A und B, A…​B ist wie folgt gegeben:

    $ git rev-list A B --not $(git merge-base --all A B)
    

Schauen wir uns das Beispiel in Abbildung 4-16 an.

vcg3 0416
Abbildung 4-16. Beispiel für eine symmetrische Differenz

Die Commits, die zu main beitragen, sind (I, H, . . . , B, A, W, V, U). Die Commits, die zu dev beitragen, sind (Z, Y, . . . , U, C, B, A).

Die Vereinigung dieser beiden Mengen ist (A, . . . , I, U, . . . , Z). Die Merge-Basis zwischen main und dev ist W. In komplexeren Fällen kann es mehrere Merge-Basen geben, aber hier haben wir nur eine. Die Commits, die zu W beitragen, sind (W, V, U, C, B und A); dies sind auch die Commits, die main und dev gemeinsam haben, also müssen sie entfernt werden, um die symmetrische Differenz zu bilden: (I, H, Z, Y, X, G, F, E, D).

Es kann hilfreich sein, sich den symmetrischen Unterschied zwischen zwei Zweigen, A und B, so vorzustellen: "Zeige alles im Zweig A oder im Zweig B, aber nur bis zu dem Punkt zurück, an dem sich die beiden Zweige getrennt haben."

Wir können jeden Teil der Definition der symmetrischen Differenz berechnen:

   A...B = (A OR B) AND NOT (merge-base --all A B)

Nachdem wir nun beschrieben haben, was Commit-Bereiche sind, wie man sie schreibt und wie sie funktionieren, ist es wichtig zu erwähnen, dass Git eigentlich keinen echten Bereichsoperator unterstützt. Es handelt sich um eine reine Notationshilfe, die A..B die zugrunde liegende ^A B Form darstellt. Git erlaubt in der Kommandozeile eine viel leistungsfähigere Manipulation von Commit-Sets. Befehle, die einen Bereich akzeptieren, akzeptieren eigentlich eine beliebige Folge von eingeschlossenen und ausgeschlossenen Commits. Du könntest zum Beispiel verwenden:

    $ git log ^dev ^feature ^bugfix main
    

um die Commits in main auszuwählen, aber nicht in den Zweigen dev, feature oder bugfix.

Wie gesagt, all diese Beispiele mögen etwas abstrakt sein, aber die Macht der Bereichsdarstellung wird erst richtig deutlich, wenn du bedenkst, dass jeder Zweigname als Teil des Bereichs verwendet werden kann. Wie in "Verfolgen von Zweigen" beschrieben , kannst du, wenn einer deiner Zweige die Commits eines anderen Repositorys repräsentiert, schnell die Menge der Commits herausfinden, die in deinem Repository und nicht in einem anderen Repository sind!

Zusammenfassung

Zu Beginn dieses Kapitels haben wir Commits als aufgezeichnete Änderungseinheit vorgestellt und erklärt, wie wichtig es ist, dass Commits als atomarer Änderungssatz aufgezeichnet werden. Als Nächstes haben wir deine Kenntnisse über Commits schrittweise erweitert, indem wir dir zunächst die verschiedenen Möglichkeiten vorgestellt haben, wie ein Commit identifiziert werden kann. Wir haben betont, wie wichtig es ist, zu lernen, wie man Commits identifiziert, denn das ist der Grundstein für viele fortgeschrittene Git-Befehle und -Konzepte, die dir dabei helfen werden, komplexe Anforderungen zu bewältigen, die bei zukünftigen Projekten im Umgang mit Repositories auf dich zukommen. Wir haben auch besprochen, wie du die Commit-Historie eines Repositorys einsehen kannst und wie du einen Bereich oder eine Menge der Historie effektiv einschränken kannst, um besser zu verstehen, wie ein Repository zu seinem aktuellen Zustand gekommen ist. Das Verständnis von Commit-Bereichen kann anfangs schwierig sein, aber du wirst die Idee verstehen, wenn du die Commit-Historie des Repositorys anhand von konkreten Anwendungsfällen analysierst, insbesondere wenn du die Commit-Historie bis zu einem bestimmten Zustand des Repositorys durchlaufen musst.

1 Git zeichnet auch ein Modus-Flag auf, das die Ausführbarkeit jeder Datei angibt. Änderungen an diesem Flag sind ebenfalls Teil eines Changesets.

2 Ja, du kannst tatsächlich mehrere Root-Commits in ein einziges Repository einbringen. Das passiert zum Beispiel, wenn zwei verschiedene Projekte und ihre gesamten Repositories zusammengeführt und zu einem zusammengefasst werden.

3 Ein Graph ist eine Sammlung von Knoten und einer Reihe von Kanten zwischen den Knoten. Üblicherweise wird das Diagramm des gerichteten azyklischen Graphen (DAG) verwendet, um den Verlauf von Git-Commits zu erklären.

4 Der Befehl gitk ist kein git Unterbefehl; er ist ein eigenständiger Befehl und ein installierbares Paket.

Get Versionskontrolle mit Git, 3. Auflage now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.