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://
plusieurs types d’objets¶
en fait pour décrire un commit on a besoin de 3 types d’objets
commit, pour représenter, eh bien .. un committree, pour le contenu d’un dossierblob, pour le contenu d’un fichier usuel
et pour faire court, on voit ça en détail tout de suite, mais
un
commitcontient des infos administratives (auteur/dat/message) + 1tree(et un ou des parent.s)un
treecontient une collection detreeet/oublobun
blobcontient tout simplement le fichier tel-quel
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 --oneline6f30908 (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_hashle hash du commit courant est 6f30908
# et voici le contenu typique d'un objet de type 'commit'
git cat-file -p $commit_hashtree f1dda2546e8f2ae263b4e479c31969e74ee28143
parent 85fc2972e20021079e28d89bd201cb17d41a6070
author Thierry Parmentelat <thierry.parmentelat@inria.fr> 1760611345 +0200
committer Thierry Parmentelat <thierry.parmentelat@inria.fr> 1760611345 +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:
le
parent, c’est tout simplement le hash du commit parent (il peut y en avoir plusieurs si on regarde un commit de fusion)le
tree, c’est un objet qui va nous dire le contenu de ce commit
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_hashle hash du tree du commit courant est f1dda2546e8f2ae263b4e479c31969e74ee28143
et voici donc le contenu typique d’un objet de type tree
git cat-file -p $tree_hash040000 tree bcea43e88f4976440b590941e8cca53c92344365 .github
100644 blob df5206a24656f56cd93133cb9e93fffaceba4c5a .gitignore
100644 blob 2332bbbbdcc42f02c7ace29b8f80f251b21fb0a6 .readthedocs.yaml
100644 blob 2005959e1af7a5fde85a6bb4febb59ec574fa70b README.md
100644 blob 75d85338ee1dac8058f1bcf6a377466a23784691 jupytext.toml
040000 tree 824f918e174d70306a242d8454731fd79424c6ac 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:
une entrée de type
treesignale la présence d’un sous-dossierune entrée de tyle
blobcorrespond à un fichier usuel
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.txton sait que le commit contient à sa racine:
2 sous-dossiers qui s’appellent
.nbhostingetnotebookset 3 fichiers
.gitignore,README.mdetrequirements.txt
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_hashle 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
l’objet dont le hash est
ffdd283698ab76f28f262894cbf9b4cc005f953fest rangé dans le fichier
.git/objects/ff/dd283698ab76f28f262894cbf9b4cc005f953f
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://
# 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_hashle hash complet du commit est 6f30908c12e5c96856f152ca7e8ed8d8979eb82e
# 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 $pathle fichier qui stocke le commit doit se trouver dans .git/objects/6f/30908c12e5c96856f152ca7e8ed8d8979eb82e
# voyons si le fichier existe bien
# je remonte d'un cran car nous sommes dans le dossier notebooks/
ls -l ../$pathls: cannot access '../.git/objects/6f/30908c12e5c96856f152ca7e8ed8d8979eb82e': No such file or directory
et donc maintenant qu’on a localisé le fichier on peut le décompresser
cat ../$path | zlib-flate -uncompresscat: ../.git/objects/6f/30908c12e5c96856f152ca7e8ed8d8979eb82e: 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
avec gzip
on peut aussi décompresser directement en bash avec gzip, même si c’est encore plus cryptique que la magie noire de tout à l’heure :)
(merci à https://
(printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" ; cat ../$path) | gzip -dcà la fin gzip se plaint qu’il manque le footer de checksum, mais c’est suffisant pour ce qu’on cherche à faire...
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.