# bion-git — mini-git from scratch, le bion du principe « fork = liberté » ## Quoi Le noyau conceptuel de git, réimplémenté depuis les principes en **Rust std-only, zéro dépendance, zéro unsafe** — même le SHA-1 est maison (`src/sha1.rs`, ~90 lignes, vérifié contre les vecteurs officiels du NIST) : - **magasin d'objets content-addressed** : `blob` / `tree` / `commit`, nommés par le SHA-1 de leur contenu, stockés dans `.bgit/objects/ab/cdef…` (2 caractères de répertoire / 38 de fichier, comme git) ; - **instantanés** : `write_tree` photographie un répertoire, `checkout_tree` le rematérialise n'importe où ; - **histoire** : commits chaînés (0, 1 ou N parents — le merge est représentable), `log` en remontant le premier parent ; - **branches** : 41 octets dans `refs/heads/` — forker est gratuit. Les ids de **blobs et trees sont bit-à-bit ceux du vrai git** (testé contre `git hash-object` et `git write-tree`, tri des entrées compris). ## Pourquoi C'est le principe RS-7 fait structure de données : *« dès qu'on build un truc, plus personne n'a à le rebuild — des blocs, la seule chose à faire est l'optimisation »*. Le content-addressing garantit exactement ça : un contenu déjà construit porte un nom absolu, universel, immuable — le rebuild est impossible par construction, on ne peut que référencer ou optimiser. **Le lien avec le xerboxion** : une branche est un **chemin** dans l'espace des états ; un fork une **bifurcation visible**. Deux branches divergentes partagent tout leur passé commun sans copier un octet — la divergence n'écrase rien, ne cache rien : elle *existe* dans le graphe, on peut la montrer, la comparer, la refermer par un merge commit tracé. C'est la sémantique gelée par RS-7 (« divergence = branche, jamais d'écrasement silencieux » — voir `bion-vc` pour son pendant causal/CRDT) et le socle du nexus/RepoVerse : contribution = repo, reprise = fork, réplication = pull des ids manquants. ## Exemple ```rust use bion_git::Repo; let repo = Repo::init(dir)?; // .bgit/ let tree = repo.write_tree(dir)?; // photographier let racine = repo.commit(tree, &[], "premier instant", "rs-1")?; repo.branch("main", racine)?; repo.branch("fork", racine)?; // forker = 41 octets // diverger… let cm = repo.commit(tree2, &[racine], "chemin main", "rs-1")?; let cf = repo.commit(tree3, &[racine], "bifurcation", "rs-7")?; repo.branch("main", cm)?; repo.branch("fork", cf)?; for c in repo.log(repo.branch_target("fork")?)? { // l'histoire println!("{} {} — {}", c.id, c.author, c.message); } repo.checkout_tree(&tree, dest)?; // rematérialiser ``` API complète : `Repo::{init, open, hash_object, cat_object, write_tree, read_tree, checkout_tree, commit, commit_at, read_commit, log, branch, branch_target, branches}` + `Id`, `Kind`, `TreeEntry`, `Commit`, `sha1::{sha1, to_hex}`. Cette API est **stable pour toujours** (dogme des bions) : on peut lui ajouter, jamais lui retirer. ## Écarts assumés avec le vrai git | Écart | Pourquoi | |---|---| | Objets **non compressés** (pas de zlib) | std-only et lisible ; git compresse en deflate *après* le hash, donc les ids restent identiques — seul l'octet sur disque diffère. C'est LE point d'optimisation invité (voir plus bas). | | Commits : auteur = chaîne libre, fuseau figé `+0000`, `committer` = copie d'`author` | même format textuel que git, mais sans imposer `Nom ` ; les ids de commits ne coïncident avec git que si on reproduit son format exact | | Pas d'index/staging | `write_tree` photographie le répertoire directement | | Pas de packfiles, refs non packées | un objet = un fichier, un ref = un fichier | | Modes `100644` et `40000` seulement | ni exécutables ni symlinks (refus explicite) | | `log` = premier parent | le graphe complet est dans `Commit::parents`, à l'appelant d'explorer | ## build-your-own-x Correspond au chapitre **« Build your own Git »** de [codecrafters-io/build-your-own-x](https://github.com/codecrafters-io/build-your-own-x#build-your-own-git) : - *Write yourself a Git!* (thblt) — la référence dont ce bion suit le périmètre ; - *Git Internals — Plumbing and Porcelain* (Pro Git, ch. 10) — le format d'objets ; - CodeCrafters « Build your own Git » — mêmes étapes (init, hash-object, cat-file, write-tree, commit-tree). Et pour le SHA-1 : FIPS 180-1 (NIST, 1995) — l'implémentation suit la spec pas à pas, commentée en français. ## Comment l'optimiser (l'invitation au fork) Le contrat est l'API + le format d'objets ; tout le reste est forkable : 1. **zlib/deflate** : compresser les objets à l'écriture (ids inchangés !) — c'est l'écart n°1 avec git, et un « build your own zlib » pédagogique en soi ; 2. **packfiles + delta-encoding** : regrouper N objets en un fichier, encoder les versions successives en deltas — le vrai gain d'espace de git ; 3. **SHA-256** : brancher un second hash (git fait sa propre migration) ; 4. **index/staging** : un cache `(chemin, mtime, id)` pour ne pas re-hasher les fichiers inchangés à chaque `write_tree` ; 5. **transport** : `pull(remote)` = énumérer les ids manquants et les copier — la réplication pull/apply du xerboxion tombe naturellement du content-addressing ; 6. **merge de trees** : diff3 entre deux trees et leur ancêtre commun, avec conflits *visibles* (jamais d'arbitrage silencieux — brancher `bion-vc`). ## Tests `cargo test -p bion-git` — 15 tests unitaires + 2 doc-tests : vecteurs SHA-1 officiels (dont le million de « a »), ids identiques au vrai git (blobs, tree vide, tree imbriqué vérifié contre `git write-tree`), roundtrips blob/tree/commit, log d'une chaîne, **fork de deux branches divergentes depuis un parent commun**, merge à deux parents, détection de corruption (le magasin est auto-vérifiant), refus des traversées de chemin et de l'injection dans le format commit.