Skip to article frontmatterSkip to article content

ce notebook est complètement optionnel

on s’intéresse à la façon dont les commits sont stockés dans le repository
on n’a pas du tout besoin de savoir ça pour utiliser git pleinement, mais cela pourra peut-être satisfaire la curiosité de certains

pour plus de détails, je vous renvoie à cette page https://git-scm.com/book/en/v2/Git-Internals-Git-Objects

plusieurs types d’objets

en fait pour décrire un commit on a besoin de 3 types d’objets

et pour faire court, on voit ça en détail tout de suite, mais

sachant que pour gagner de la place tout ceci est compressé

le type commit

un object commit est implémenté comme un simple fichier texte compressé
et pour le voir il suffit de faire

git cat-file <le-commit>

voyons ce que ça nous donne avec le commit courant:

# voyons un peu où nous en sommes dans ce repo
git log -3 --oneline
429abbf (grafted, HEAD -> main, origin/main) adopt latest version of style_jb2.css

# je me livre à une petite gymnastique en bash 
# pour calculer le hash du commit courant

# on le range dans une variable
commit_hash=$(git log -1 --pretty='%h')

# et on l'affiche
# vous pouvez vérifier dans le git log au dessus
echo le hash du commit courant est $commit_hash
le hash du commit courant est 429abbf

# et voici le contenu typique d'un objet de type 'commit'

git cat-file -p $commit_hash
tree bf3e4c3c76e3f6b3df33b4e47079568b2dd400a3
parent 02a01d3016afffce725ba8a01dd13f9e250b232e
author Thierry Parmentelat <thierry.parmentelat@inria.fr> 1757326942 +0200
committer Thierry Parmentelat <thierry.parmentelat@inria.fr> 1757326942 +0200

adopt latest version of style_jb2.css

comme on le voit, c’est un fichier texte tout simple, avec pour commencer 4 informations (tree, parent, author, committer) et ensuite le message du commit

ce sont les deux premiers qui vont nous intéresser maintenant, car ils méritent une explication:

et du coup le hash correspondant au tree, puisque ça donne le détail du dossier à la racine du repo, n’est pas un objet de type commit, mais de type tree

regardons ça de plus près

le type tree

c’est le même principe, on obtient le contenu de cet objet à partir de son hash, en utilisant comme plus haut la commande cat-file

à nouveau regardons ce que ça donne avec le commit courant

# pareil, on fait un peu de contorsions en bash pour calculer 
# le hash du `tree` du commit 
# un peu de magie noire, ce n'est pas utile 
# de comprendre le détail de cette commande ;)

tree_hash=$(git cat-file -p $commit_hash | grep '^tree ' | cut -d' ' -f2)

# vérifiez que c'est bien ce qui apparaissait dans la première ligne du commit
echo le hash du tree du commit courant est $tree_hash
le hash du tree du commit courant est bf3e4c3c76e3f6b3df33b4e47079568b2dd400a3

et voici donc le contenu typique d’un objet de type tree

git cat-file -p $tree_hash
040000 tree 17b29ed64c1e995cffea7698989c2eaedb8a2212	.github
100644 blob df5206a24656f56cd93133cb9e93fffaceba4c5a	.gitignore
100644 blob 2332bbbbdcc42f02c7ace29b8f80f251b21fb0a6	.readthedocs.yaml
100644 blob edd15baff204eac4b179b23522ff545a9b9bc9ab	README.md
100644 blob 645cd4a9edf02483c13bee085c4d74b64162e4ff	jupytext.toml
040000 tree 937d36fe953d715abbcb95bc0952d7dc860ed09a	notebooks
100644 blob 3ab1ab7768f4d9776152cf152486a97ee7964aa2	requirements.txt

cette fois, on voit une simple collection d’objets, un mélange de tree et de blob
et vous l’avez deviné, cette liste donne le contenu du dossier principal du commit:

ainsi pour être concret avec un listing comme celui-ci

100644 blob ffdd283698ab76f28f262894cbf9b4cc005f953f	.gitignore
040000 tree 4d747763edc535af280224922965319fb5e5ec1a	.nbhosting
100644 blob edd15baff204eac4b179b23522ff545a9b9bc9ab	README.md
040000 tree 0dced09e30493575a6f77d51d7b9ab6687d869cb	notebooks
100644 blob 9aff54ee714f94ca4ff615a678d558a62bdefd8b	requirements.txt

on sait que le commit contient à sa racine:

pour retrouver le contenu des sous-dossiers, eh bien on vient de voir comment ça marche

le type blob

il ne reste plus qu’à comprendre comment on retrouve le contenu des fichiers usuels ça ne peut pas être plus simple, l’objet est tout simplement sauvé comme le fichier compressé

voyons ça, on va utiliser .. cat-file de nouveau, bien sûr
et avec le premier blob qui est mentionné ça nous donnerait

# calculons le hash du premier blob
# passons sur les détails

blob_hash=$(git cat-file -p $tree_hash | grep ' blob ' | head -1 | awk '{print $3;}')

#
echo le premier blob a pour hash $blob_hash
le premier blob a pour hash df5206a24656f56cd93133cb9e93fffaceba4c5a

# et voici maintenant le contenu du fichier correspondant à ce blob
# qui correspond bien au contenu du .gitignore à la racine du repo
# (et vous pouvez vérifier dans votre repo)

git cat-file -p $blob_hash
**/.ipynb_checkpoints/
**/_build/

# MyST build outputs
_build
node_modules

mais c’est rangé où ?

chacun de ces objets est rangé dans un fichier sous .git/objects; par exemple

c’est-à-dire que les deux premiers caractères du hash servent pour le nom du dossier, et le reste pour le nom du fichier
il s’agit d’une simple optimisation pour éviter de se retrouver avec un trop grand nombre de fichiers dans un seul dossier

et sous quel format ?

le fichier sous .git/objects contient le texte (celui qu’on a vu avec cat-file) simplement compressé avec un outil qui s’appelle zlib

on trouve zlib-flate dans le package qpdf
(merci à https://unix.stackexchange.com/questions/22834/how-to-uncompress-zlib-data-in-unix pour les détails)

# ici il faut bien sûr utiliser le hash complet, pas le raccourci
full_commit_hash=$(git log -1 --pretty='%H')

echo le hash complet du commit est $full_commit_hash
le hash complet du commit est 429abbf09f50cfe3de844e3c8731b58cb9fb7736

# on extrait les deux morceaux:
# les deux premiers caractères
dir_part=${full_commit_hash:0:2}
# le reste
file_part=${full_commit_hash:2:38}

path=".git/objects/$dir_part/$file_part"

echo le fichier qui stocke le commit doit se trouver dans $path
le fichier qui stocke le commit doit se trouver dans .git/objects/42/9abbf09f50cfe3de844e3c8731b58cb9fb7736

# voyons si le fichier existe bien
# je remonte d'un cran car nous sommes dans le dossier notebooks/
ls -l ../$path
ls: cannot access '../.git/objects/42/9abbf09f50cfe3de844e3c8731b58cb9fb7736': No such file or directory

et donc maintenant qu’on a localisé le fichier on peut le décompresser

cat ../$path | zlib-flate -uncompress
cat: ../.git/objects/42/9abbf09f50cfe3de844e3c8731b58cb9fb7736: No such file or directory
Command 'zlib-flate' not found, but can be installed with:
apt install qpdf
Please ask your administrator.

et on se convainc comme ça que cat-file ne fait pas grand-chose de plus que de décompresser le hash qu’on lui passe

quelques références pour aller plus loin

ces deux liens sont des micro-implémentations - en Python et JavaScript respectivement:

avec énormément d’explications qui peuvent éclairer certains points, dont notamment pour le premier lien, l’implémentation de l’index.