J’ai créé mon propre Git
(tonystr.net)- Pour mieux comprendre la structure interne d’un système de gestion de versions, l’auteur a implémenté lui-même un système similaire à Git
- Il remplace le hachage SHA-1 et la compression zlib de Git par un hachage SHA-256 et la compression zstd, avec un dépôt structuré autour du répertoire
.tvc - Écrit en Rust, le projet implémente étape par étape les fonctions de hachage de fichiers, compression, commit et checkout
- Les objets de commit incluent le hash de l’arbre, le commit parent, l’auteur et le message, et un même fichier n’est pas réenregistré si son hash existe déjà
- L’expérience met en évidence que Git est un stockage de fichiers adressé par le contenu, et souligne l’importance d’un format de données structuré
Méthodes de hachage et de compression
- Git identifie tous les objets par un hachage SHA-1, mais ce projet utilise SHA-256
- SHA-1 est ancien et présente des faiblesses de sécurité, mais ici la sécurité n’était pas essentielle puisqu’il s’agissait simplement d’identifier le contenu des fichiers
- À la place de zlib, le projet adopte zstd de Facebook comme bibliothèque de compression
- zstd a été jugé plus efficace, et la compatibilité avec Git n’était pas un objectif
- Le projet s’appelle « tvc (Tony’s Version Control) », et utilise les fichiers
.tvcet.tvcignorecomme équivalents de la structure Git
Étapes d’implémentation
- La procédure d’implémentation suit l’ordre suivant : lecture des arguments de commande → lecture des règles d’exclusion → affichage de la liste des fichiers → hachage et compression → création des arbres et commits → gestion de HEAD → checkout d’un commit
- Écrit en Rust, la commande
lsapplique les règles de.tvcignore, parcourt récursivement les fichiers non ignorés et affiche le hachage SHA-256 de chaque fichier - Les fonctions de compression et décompression de fichiers ont été implémentées simplement à l’aide de la bibliothèque zstd
Structure des commits
- Un objet de commit contient les informations suivantes
- le type d’objet (« commit »)
- l’état du système de fichiers à cet instant (hash de l’arbre)
- le commit précédent (HEAD)
- l’auteur (
author) - le message de commit
- Contrairement à Git, le projet ne distingue pas auteur et committer, et n’implémente ni fusion ni rebase
- Lors de la création d’un commit, un objet arbre est généré, haché, compressé puis stocké dans
.tvc/objects/, avant mise à jour du fichier HEAD - Si deux fichiers ont le même hash, ils ne sont pas stockés une seconde fois, ce qui permet d’éviter les doublons
Objets arbre et checkout
- La fonction
generate_tree()parcourt les répertoires, hache, compresse et stocke chaque fichier, puis construit une chaîne contenant le nom de fichier et son hash- Les sous-répertoires sont traités récursivement pour former une structure en arbre
- Les objets commit et arbre sont analysés sous forme de structures (
Commit,Tree) afin d’être plus faciles à manipuler en mémoire - La fonction
generate_fs()reconstruit le système de fichiers à partir de la structure d’arbre et effectue le checkout dans le chemin spécifié
Enseignements du projet
- Le projet permet de constater directement que Git est un stockage de fichiers adressé par le contenu (key-value)
- La partie la plus difficile a été l’analyse du format des objets ; l’auteur prévoit d’utiliser la prochaine fois un format plus explicite comme YAML ou JSON
- Le code complet est publié sur GitHub (tonystr/t-version-control)
1 commentaires
Réactions sur Hacker News
Il est intéressant de noter que Git est le seul SCM à prendre en charge la recursive merge strategy
Cette approche se souvient automatiquement des résolutions de conflits passées, ce qui est très utile
Beaucoup préfèrent encore rebase, mais lors de l’implémentation d’un merge, il faut absolument inclure un mécanisme de mémorisation de l’historique des résolutions de conflits
Référence connexe : Merge made by recursive strategy
Référence : Git Tools - Rerere
Lien
git mergen’a pas de stratégie « null »Même quand on a déjà résolu le conflit et qu’on veut simplement conserver une trace du merge, Git essaie quand même d’aider
J’aimerais qu’il existe une option pour enregistrer simplement le fait du merge sans toucher à l’index ni au worktree
C’est par exemple ce que fait Pijul
On ne peut pas voir les différentes tentatives de plusieurs commits, c’est plus difficile à annuler, et il devient compliqué de continuer à travailler sur une branche déjà mergée
Quand plusieurs PR constituent chacune une pièce d’un même puzzle, un merge simple me semble bien meilleur
C’est toujours agréable d’apprendre le fonctionnement interne d’un outil qu’on utilise tous les jours
En particulier, Git from the Bottom Up est un excellent texte qui explique clairement l’architecture interne de Git
En une vingtaine de minutes, on peut comprendre le fonctionnement opaque des commandes Git
cat-file, et c’est assez coolSi vous vous demandez comment les agents de code planifient leur travail, ce genre de textes fait partie de leurs données d’entraînement
Cela dit, si l’auteur a été aidé par un LLM, la situation peut devenir circulaire
Il semble donc qu’il existe des bots qui aspirent les dépôts publics
C’est une sensation étrange de penser que mon code pourrait servir à l’entraînement de LLM
Le texte lui-même ne contient aucune sortie de LLM, mais j’ai utilisé ChatGPT pour des conseils sur les conventions de code Rust ou des comparaisons d’algorithmes
Le tutoriel CodeCrafters “Build your own Git” est vraiment excellent
Je recommande aussi la vidéo live de Jon Gjengset, où il l’implémente lui-même en Rust
J’aimerais moi aussi que le contrôle de version soit davantage utilisé au-delà du logiciel
GotVC est un projet intéressant, avec chiffrement E2E, import parallèle et prise en charge des gros fichiers
Au final, il faut les ouvrir dans le programme d’origine pour les comparer
Ce billet m’a rappelé ugit: DIY Git in Python
C’est l’une des meilleures ressources que j’ai vues, à la fois très approfondie sur les internals de Git et facile à suivre
Sapling VCS, le fork de Mercurial chez Meta, utilise la compression par dictionnaire Zstd
La documentation explicative permet de comparer cela aux packfiles Git compressés en delta
Sur les petits dépôts, la compression delta de Git est plus efficace, mais sur les grands dépôts, la compression par dictionnaire basée sur les chemins est meilleure
Une fonctionnalité similaire appelée « path-walk » a aussi été ajoutée récemment à Git
J’ai tenté quelque chose de similaire, et mon projet s’appelle « shit »
Lien GitHub
J’ai le souvenir d’avoir essayé autrefois de créer un framework SPA et d’avoir été surpris par sa complexité cachée
Je pense que les développeurs React ou Angular ont dû eux aussi tomber dans ce terrier de lapin
Git cache très bien sa complexité, lui aussi
J’ai vu un client Git écrit en PHP, capable de lire les packfiles et reftables, avec prise en charge d’un diff basé sur LCS
gipht-horse
Et je viens d’apprendre qu’on peut utiliser
@à la place de HEAD, ce qui est syntaxiquement assez logique