Einführung
Ein Git-Commit referenziert null, einen oder mehrere Eltern-Commits. Die gesamte Historie ist ein gerichteter azyklischer Graph (DAG) dieser Eltern-Verknüpfungen. Branches und Tags sind einfach Labels auf Knoten dieses Graphen.
Anatomie einer Eltern-Verknüpfung
git cat-file -p HEAD
# tree 9f1a...
# parent b2c3...
# parent d4e5... (nur bei Merge-Commits)
# author Ada ...
# committer Ada ...
Ein Elternteil: lineare Historie. Zwei Eltern: ein Merge. Null Eltern: ein Wurzel-Commit. Drei oder mehr: ein Octopus-Merge.
Den Graph durchlaufen
git log --oneline --graph --all
git log --first-parent # nur ersten Eltern folgen
git log --topo-order
git log --date-order
--first-parent auf main ist großartig, um den "Trunk" der Historie ohne Merge-Side-Rauschen zu sehen.
Vorfahrenabfragen
git merge-base A B # jüngster gemeinsamer Vorfahre
git merge-base --is-ancestor X Y # Exit 0 falls X Vorfahre von Y
git rev-list A..B --count # Commits in B, nicht in A
Bereichssyntax
A..B: Commits von B aus erreichbar, nicht von A.A...B: symmetrische Differenz.^A B: gleichbedeutend mitA..B.B --not A: gleichbedeutend mitA..B.
git log main..feature
git log main...feature --left-right --oneline
Die commit-graph-Datei
Seit Git 2.18 kann Git eine commit-graph-Datei schreiben, die Vorfahren-Daten für riesige Repositories vorberechnet:
git commit-graph write --reachable
git config --global core.commitGraph true
git config --global gc.writeCommitGraph true
Dies beschleunigt git log --graph, git merge-base und Erreichbarkeitsprüfungen dramatisch.
Octopus-Merges
Ein einzelner Merge-Commit kann viele Eltern haben:
git merge feature1 feature2 feature3
Octopus-Merges gelingen nur ohne Konflikte. Sie sind in einigen Workflows für Integrationsbranches nützlich, aber selten nötig.
Den Graph visualisieren
git log --oneline --graph --decorate --all
gitk --all
git log --pretty=format:'%h %p %s'
%p gibt Eltern-SHAs aus.
Generationsnummern
Die commit-graph-Datei speichert eine Generationsnummer pro Commit: 1 für Wurzel-Commits, max(Eltern-Generationen) + 1 sonst. Generationsnummern verwandeln viele Vorfahrenabfragen in O(log n)-Operationen. Modernes Git (2.27+) schreibt korrigierte Commit-Daten und Generation-v2-Nummern, wenn der commit-graph aktiviert ist:
git commit-graph write --reachable --changed-paths
git config gc.writeCommitGraph true
git config core.commitGraph true
git log --oneline --graph -n 5
--changed-paths zeichnet zusätzlich pro Commit einen Bloom-Filter auf, der geänderte Pfade auflistet, was pfadbeschränkte git log-Abfragen dramatisch beschleunigt.
Häufige Fehler
Sich Branches als getrennte Stapel von Commits vorstellen; sie sind Labels auf Knoten, die Vorfahren teilen können. HEAD~3 auf einem Merge-Commit verwenden und überrascht sein: ~ folgt immer dem ersten Elternteil. Verwenden Sie ^2, ^3, um zu anderen Eltern zu springen. Vergessen, dass Rebase die Graph-Topologie umschreibt, indem es neue Commits mit neuen Eltern-Zeigern erstellt; alte Commits leben im Reflog weiter. Schließlich: Glauben, der Commit-Graph speichere Diffs; er speichert nur Trees und Eltern. Diffs werden bei Bedarf durch Vergleich der Trees berechnet.