Ich habe mein eigenes Git gebaut
(tonystr.net)- Um die interne Struktur eines Versionsverwaltungssystems zu verstehen, wurde direkt ein Git-ähnliches System implementiert
- SHA-256-Hashing und zstd-Komprimierung ersetzen Git's SHA-1 und zlib; das Repository ist in einer
.tvc-Verzeichnisstruktur organisiert - Geschrieben in Rust, mit schrittweiser Implementierung von Datei-Hashing, Komprimierung, Commit und Checkout
- Commit-Objekte enthalten Tree-Hash, Parent-Commit, Autor und Nachricht; identische Dateien werden dank Hash-Deduplizierung nicht erneut gespeichert
- Dabei wird unmittelbar erfahrbar, dass Git ein inhaltsadressierter Dateispeicher ist, und die Bedeutung strukturierter Datenformate wird hervorgehoben
Hashing und Komprimierung
- Git identifiziert alle Objekte über SHA-1-Hashes, in diesem Projekt wird jedoch SHA-256 verwendet
- SHA-1 ist alt und sicherheitstechnisch anfällig, aber in diesem Projekt dient es nur zur Identifikation von Dateiinhalten, daher ist Sicherheit nicht entscheidend
- Anstelle von Gits zlib kommt die zstd-Komprimierungsbibliothek von Facebook zum Einsatz
- zstd wurde als effizienter eingeschätzt, Git-Kompatibilität war kein Ziel
- Der Projektname lautet „tvc (Tony’s Version Control)”;
.tvcund.tvcignoreübernehmen die entsprechenden Rollen von Git
Implementierungsschritte
- Die Umsetzung erfolgte in der Reihenfolge Kommandozeilenargumente lesen → Ignore-Regeln lesen → Dateiliste ausgeben → Hashing und Komprimierung → Tree- und Commit-Erzeugung → HEAD-Verwaltung → Commit-Checkout
- Das Projekt ist in Rust geschrieben; der
ls-Befehl durchsucht rekursiv alle nicht ignorierten Dateien unter Anwendung der.tvcignore-Regeln und gibt für jede Datei den SHA-256-Hash aus - Mit der zstd-Bibliothek wurden Funktionen zum Komprimieren und Dekomprimieren von Dateien einfach umgesetzt
Commit-Struktur
- Ein Commit-Objekt enthält folgende Informationen
- Objekttyp („commit”)
- Den Zustand des Dateisystems zu diesem Zeitpunkt (Tree-Hash)
- Den vorherigen Commit (HEAD)
- Den Autor (
author) - Die Commit-Nachricht
- Anders als bei Git wird nicht zwischen Autor und Committer unterschieden, Merge- oder Rebase-Funktionen wurden nicht implementiert
- Beim Erzeugen eines Commits werden Tree-Objekte erstellt, gehasht, komprimiert und in
.tvc/objects/gespeichert; anschließend wird die HEAD-Datei aktualisiert - Identische Dateien werden bei gleichem Hash nicht erneut gespeichert, wodurch Doppelspeicherung vermieden wird
Tree-Objekte und Checkout
- Die Funktion
generate_tree()durchläuft Verzeichnisse, hasht, komprimiert und speichert jede Datei und setzt Dateinamen und Hashes zu Strings zusammen- Unterverzeichnisse werden rekursiv verarbeitet und so eine Tree-Struktur aufgebaut
- Commit- und Tree-Objekte werden in Strukturen (
Commit,Tree) geparst, damit sie im Speicher leichter verarbeitet werden können - Die Funktion
generate_fs()rekonstruiert auf Basis der Tree-Struktur das Dateisystem und führt den Checkout in den angegebenen Pfad aus
Erkenntnisse aus dem Projekt
- Git lässt sich direkt als inhaltsadressierter (Key-Value-)Dateispeicher erfahren
- Der schwierigste Teil war das Parsen des Objektformats; beim nächsten Mal sollen klarere Formate wie YAML oder JSON verwendet werden
- Der vollständige Code ist im GitHub-Repository (tonystr/t-version-control) öffentlich verfügbar
1 Kommentare
Hacker-News-Kommentare
Interessant ist, dass Git das einzige SCM zu sein scheint, das die recursive merge strategy unterstützt.
Diese Methode merkt sich frühere Konfliktauflösungen automatisch und ist deshalb sehr nützlich.
Viele bevorzugen noch immer rebase, aber bei der Implementierung von Merges sollte man unbedingt einen Mechanismus zum Speichern der Konfliktauflösungshistorie einbauen.
Siehe auch: Merge made by recursive strategy
Siehe: Git Tools - Rerere
Link
git mergekeine „null“-Strategie hat.Selbst wenn man den Konflikt bereits gelöst hat und nur noch den Merge dokumentieren will, versucht Git unbedingt, hilfreich einzugreifen.
Ich wünschte, es gäbe eine Option, die einfach nur den Merge vermerkt, ohne Index oder Working Tree anzufassen.
Pijul macht das zum Beispiel so.
Man kann die einzelnen Versuche über mehrere Commits hinweg nicht mehr sehen, Reverts werden schwieriger, und es ist umständlich, auf einem bereits gemergten Branch weiterzuarbeiten.
Wenn mehrere PRs Teile eines größeren Puzzles sind, halte ich einen einfachen Merge für deutlich besser.
Es macht immer Spaß, das Innenleben von Werkzeugen zu verstehen, die man täglich benutzt.
Besonders Git from the Bottom Up ist ein hervorragender Text, der Gits interne Struktur klar erklärt.
In etwa 20 Minuten kann man die ansonsten schwer durchschaubare Funktionsweise der Git-Befehle verstehen.
cat-fileHash-IDs direkt ansehen kann, habe ich erst jetzt gelernt, und das ist ziemlich cool.Wer sich fragt, wie Coding-Agenten ihre Pläne machen, für den sind solche Beiträge ihr Trainingsmaterial.
Wenn der Autor sich allerdings von einem LLM helfen ließ, wird es vielleicht rekursiv.
Offenbar gibt es Bots, die öffentliche Repositories absaugen.
Der Gedanke, dass mein Code fürs LLM-Training verwendet wird, ist seltsam.
Der Beitrag selbst enthält keine LLM-Ausgaben, aber für Rust-Code-Konventionen oder Ratschläge zum Vergleich von Algorithmen habe ich ChatGPT verwendet.
Das Tutorial CodeCrafters’ „Build your own Git” ist wirklich hervorragend.
Empfehlenswert ist auch Jon Gjengsets Live-Video, in dem er es in Rust selbst implementiert.
Ich fände es auch gut, wenn Versionsverwaltung außerhalb der Softwareentwicklung stärker genutzt würde.
GotVC ist ein interessantes Projekt mit E2E-Verschlüsselung, parallelem Import und einer Struktur zur Unterstützung großer Dateien.
Am Ende muss man sie doch im ursprünglichen Programm öffnen und dort vergleichen.
Dieser Beitrag hat mich an ugit: DIY Git in Python erinnert.
Das ist eine der besten Ressourcen überhaupt, weil sie tief in Gits Interna eintaucht und trotzdem leicht nachvollziehbar bleibt.
Sapling VCS, Metas Mercurial-Fork, verwendet eine Zstd-dictionary-Komprimierung.
In der Dokumentation kann man das mit Gits delta-komprimierten Packfiles vergleichen.
In kleinen Repositories ist Gits Delta-Komprimierung effizienter, aber in großen Repositories ist eine pfadbasierte Dictionary-Komprimierung besser.
Kürzlich wurde Git auch eine ähnliche „path-walk“-Funktion hinzugefügt.
Ich habe auch einmal etwas Ähnliches versucht, und mein Projekt hieß „shit“.
GitHub-Link
Früher wollte ich einmal ein SPA-Framework bauen und war überrascht von der verborgenen Komplexität.
Ich kann mir vorstellen, dass React- oder Angular-Entwickler auf ähnliche Kaninchenlöcher stoßen.
Auch Git versteckt seine Komplexität sehr gut.
Ich habe einen in PHP geschriebenen Git-Client gesehen, der Packfiles und Reftables lesen kann und auch LCS-basiertes Diff unterstützt.
gipht-horse
Und ich habe zum ersten Mal gelernt, dass man
@anstelle von HEAD verwenden kann, was syntaktisch ziemlich sinnvoll ist.