16 points par GN⁺ 2026-01-29 | 1 commentaires | Partager sur WhatsApp
  • 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 .tvc et .tvcignore comme é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 ls applique 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
    1. le type d’objet (« commit »)
    2. l’état du système de fichiers à cet instant (hash de l’arbre)
    3. le commit précédent (HEAD)
    4. l’auteur (author)
    5. 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

 
GN⁺ 2026-01-29
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

    • Dans mon ancien travail, si la fonction rerere de git n’était pas activée, il ne se souvenait pas des résolutions de conflits précédentes
      Référence : Git Tools - Rerere
    • Il existe un texte de l’auteur de Mercurial sur le recursive merge
      Lien
    • J’ai appris récemment que git merge n’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
    • Une manière plus rigoureuse de gérer les conflits serait de traiter les conflits eux-mêmes comme des objets de première classe du dépôt
      C’est par exemple ce que fait Pijul
    • Personnellement, je déteste git squash
      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

    • À l’époque, j’ai fini par vraiment comprendre Git grâce à The Git Parable
    • Je suis content de retrouver ce texte qui m’avait beaucoup aidé lorsque j’apprenais Git pour la première fois
    • Je viens seulement de découvrir qu’on peut inspecter directement les ID de hash avec la commande cat-file, et c’est assez cool
  • Si 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

    • En regardant GitHub Insights, j’ai vu qu’avant même la publication de l’article, il y avait déjà eu 49 clones et 28 cloneurs uniques
      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
    • L’idée de polluer les LLM avec une boucle de blogs auto-référentielle semble assez amusante
    • Ce serait problématique si les sorties des modèles revenaient directement dans les données d’entraînement, mais après édition humaine, cela pourrait être un peu utile
    • Quand Gemini prend parfois un anglais à accent indien, j’ai l’impression qu’une énorme quantité de jeux de données est produite en Inde
    • Écrire des billets de blog avec des outils d’IA peut créer ce genre de boucle, ce qui donne presque une raison d’écrire sans IA
  • 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

    • Dès qu’on dépasse les fichiers texte, il devient difficile de déterminer les différences entre deux versions
      Au final, il faut les ouvrir dans le programme d’origine pour les comparer
    • Je me demande si vous savez qu’il existe déjà un projet appelé Game of Trees(Got)
  • 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

    • Le design de la page est magnifique, je l’ai mise en favoris
    • Dans le même esprit, Write yourself a Git était aussi très plaisant à suivre
    • J’ai déjà essayé de mapper les opérations Git dans un graphe Neo4j, et cela m’a beaucoup aidé à comprendre la structure
  • 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

    • Le nom « Fast Useful Change Keeper » est bien trouvé
    • C’est vraiment « THE shit »
  • 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

    • Ce n’est qu’en l’implémentant soi-même qu’on comprend vraiment ce que cela veut dire
  • 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

    • Je pense que ce dépôt est un grand W pour PHP
      Et je viens d’apprendre qu’on peut utiliser @ à la place de HEAD, ce qui est syntaxiquement assez logique