Le projet Git a récemment publié la version 2.45.0 de Git. Jetons un coup d'œil aux points forts de cette version, comprenant des contributions de l'équipe Git de GitLab et de la communauté Git au sens large.
Reftables : un nouveau backend pour le stockage des références
Chaque dépôt Git doit suivre deux structures de données de base :
- Le graphique qui stocke les données de vos fichiers, la structure de répertoires, les messages de commit et les tags.
- Les références qui sont des pointeurs dans ce graphique pour associer des objets spécifiques à un nom plus accessible. Par exemple, une branche est une référence dont le nom commence par un préfixe
refs/heads/
.
Le format sur disque de stockage des références dans un dépôt est resté largement inchangé depuis la création de Git et est appelé le format « fichiers ». Chaque fois que vous créez une référence, Git crée une « référence libre » qui correspond à un fichier ordinaire dans votre dépôt Git dont le chemin d'accès correspond au nom de la référence.
Par exemple :
$ git init .
Initialized empty Git repository in /tmp/repo/.git/
# Updating a reference will cause Git to create a "loose ref". This loose ref is
# a simple file which contains the object ID of the commit.
$ git commit --allow-empty --message "Initial commit"
[main (root-commit) c70f266] Initial commit
$ cat .git/refs/heads/main
c70f26689975782739ef9666af079535b12b5946
# Creating a second reference will end up with a second loose ref.
$ git branch feature
$ cat .git/refs/heads/feature
c70f26689975782739ef9666af079535b12b5946
$ tree .git/refs
.git/refs/
├── heads
│ ├── feature
│ └── main
└── tags
3 directories, 2 files
Il arrive que Git empaquette ces références dans un format de fichier « empaqueté » pour que la recherche de références soit plus efficace. Par exemple :
# Packing references will create "packed" references, which are a sorted list of
# references. The loose reference does not exist anymore.
$ git pack-refs --all
$ cat .git/refs/heads/main
cat: .git/refs/heads/main: No such file or directory
$ cat .git/packed-refs
# pack-refs with: peeled fully-peeled sorted
c70f26689975782739ef9666af079535b12b5946 refs/heads/feature
c70f26689975782739ef9666af079535b12b5946 refs/heads/main
Bien qu'il soit relativement simple, ce format comporte certaines limites :
- Dans les mono repos contenant de nombreuses références, nous avons commencé à nous heurter à des problèmes d'évolutivité. La suppression des références est particulièrement inefficace car l'ensemble du fichier « packed-refs » doit être réécrit pour supprimer la référence supprimée. Dans nos plus grands dépôts, cela peut entraîner la réécriture de plusieurs gigaoctets de données à chaque suppression de référence.
- Il est impossible d'effectuer une lecture atomique des références sans bloquer les rédacteurs simultanés, car il est nécessaire de lire plusieurs fichiers pour comprendre toutes les références.
- Il est impossible d'effectuer une écriture atomique, car elle nécessite la création ou la mise à jour de plusieurs fichiers, ce qui est impossible en une seule étape.
- La maintenance des références ne s'adapte pas bien, parce que vous devez réécrire le fichier « packed-refs » entièrement.
- Étant donné que les références libres utilisent le chemin d'accès du système de fichiers comme nom, elles sont soumises à un comportement spécifique au système de fichiers. Par exemple, les systèmes de fichiers insensibles à la casse ne peuvent pas stocker des références pour lesquelles seule la casse diffère.
Pour résoudre ces problèmes, Git v2.45.0 introduit un nouveau backend « reftable », qui utilise un nouveau format binaire pour stocker les références. Ce nouveau backend est en développement depuis très longtemps. Il a été initialement proposé par Shawn Pearce en juillet 2017 et a été mis en place pour la première fois dans JGit. Il est largement utilisé par le projet Gerrit. En 2021, Han-Wen Nienhuys a intégré la bibliothèque dans Git, ce qui lui permet de lire et d'écrire le format reftable.
Le nouveau backend « reftable » que nous avons introduit dans Git v2.45.0 rassemble enfin la bibliothèque reftable et Git de sorte qu'il est possible d'utiliser le nouveau format comme backend de stockage dans vos dépôts Git.
En supposant que vous exécutez au moins la version 2.45.0 de Git, vous pouvez créer de nouveaux dépôts au format « reftable » en basculant --ref-format=reftable
sur git-init(1)
ou git-clone(1)
.
Par exemple :
$ git init --ref-format=reftable .
Initialized empty Git repository in /tmp/repo/.git/
$ git rev-parse --show-ref-format
reftable
$ find -type f .git/reftable/
.git/reftable/0x000000000001-0x000000000001-01b5e47d.ref
.git/reftable/tables.list
$ git commit --allow-empty --message "Initial commit"
$ find -type f .git/reftable/
.git/reftable/0x000000000001-0x000000000001-01b5e47d.ref
.git/reftable/0x000000000002-0x000000000002-87006b81.ref
.git/reftable/tables.list
Comme vous pouvez le constater, les références sont maintenant stockées dans .git/reftable
au lieu du répertoire .git/refs.
Les références et les journaux de référence (ou « reflogs ») sont stockés dans des « tables », qui sont les fichiers se terminant par .ref
, tandis que le fichier tables.list
contient la liste de toutes les tables actuellement actives.
Le backend « reftable » est censé remplacer le backend « fichiers ». Par conséquent, du point de vue de l'utilisateur, tout devrait fonctionner de la même manière.
Ce projet a été mené par Patrick Steinhardt. Le mérite revient également à Shawn Pearce en tant qu'inventeur original du format et à Han-Wen Nienhuys en tant qu'auteur de la bibliothèque reftable.
De meilleurs outils pour les références
Alors que le format « reftable » résout de nombreux problèmes existants, il en introduit également de nouveaux. L'un des problèmes les plus importants étant l'accessibilité des données qu'il contient.
Avec le backend « fichiers », vous pouvez, dans le pire des cas, utiliser vos outils Unix habituels pour inspecter l'état des références. Les références « empaquetées » et « libres » contiennent toutes deux des données lisibles par l'homme et faciles à interpréter. Ce qui est différent avec le format « reftable », qui est un format binaire. Par conséquent, Git doit fournir tous les outils nécessaires pour extraire les données du nouveau format « reftable ».
Répertorier toutes les références
Le premier problème que nous avons rencontré est qu'il est fondamentalement impossible de connaître toutes les références connues d'un dépôt. Cela peut-être quelque peu déroutant au début : comment est-il possible de créer et modifier des références via Git, qui ne peut pas énumérer de manière exhaustive toutes les références dont il a connaissance ?
En effet, le backend « fichiers » en est incapable. Alors qu'il peut répertorier de manière évidente toutes les références « habituelles » qui commencent par le préfixe refs/
, Git utilise également des pseudo refs. Ces fichiers se trouvent directement à la racine du répertoire Git et seraient des fichiers comme, par exemple, .git/MERGE_HEAD
. Le problème ici est que ces pseudo refs se trouvent à côté d'autres fichiers que Git stocke comme, par exemple, .git/config
.
Alors que certaines pseudo refs sont bien connues et donc faciles à identifier, il n'y a en théorie aucune limite aux références que Git peut écrire. Rien ne vous empêche de créer une référence appelée « foobar ».
Par exemple :
$ git update-ref foobar HEAD
$ cat .git/foobar
f32633d4d7da32ccc3827e90ecdc10570927c77d
Maintenant, le problème du backend « fichiers » est qu'il ne peut énumérer les références qu'en parcourant les répertoires. Donc, pour déterminer si .git/foobar
est effectivement une référence, Git devrait ouvrir le fichier et vérifier s'il est formaté ou non comme une référence.
D'autre part, le backend « reftable » connaît de manière évidente toutes les références qu'il contient : elles sont encodées dans ses structures de données, il lui suffit donc de décoder ces références et de les renvoyer. Mais en raison des restrictions du backend « fichiers », il n'existe aucun outil qui vous permettrait de connaître toutes les références existantes.
Pour résoudre le problème, nous avons intégré un nouvel élément à git-for-each-ref(1)
appelé --include-root-refs
, ce qui lui permettra de répertorier également toutes les références qui existent à la racine de la hiérarchie de nommage des références.
Par exemple :
$ git for-each-ref --include-root-refs
f32633d4d7da32ccc3827e90ecdc10570927c77d commit HEAD
f32633d4d7da32ccc3827e90ecdc10570927c77d commit MERGE_HEAD
f32633d4d7da32ccc3827e90ecdc10570927c77d commit refs/heads/main
Pour le backend « fichiers », ce nouvel élément est géré au mieux et nous y incluons toutes les références qui correspondent à un nom de pseudo ref connu. Pour le backend « reftable », nous pouvons simplement lister toutes les références qui lui sont connues.
Ce projet a été mené par Karthik Nayak.
Répertorier tous les reflogs
Chaque fois que vous mettez à jour des branches, Git, par défaut, suit ces mises à jour de branche dans un reflog. Ce reflog vous permet de rétablir les modifications apportées à cette branche au cas où vous auriez effectué une modification involontaire.
Avec le backend « fichiers », ces journaux sont stockés dans votre répertoire .git/logs
:
$ find -type f .git/logs/
.git/logs/HEAD
.git/logs/refs/heads/main
Lister les fichiers dans ce répertoire est le seul moyen pour vous de savoir quelles références ont un reflog en premier lieu. C'est un problème pour le backend « reftable », qui stocke ces journaux avec les références. Par conséquent, il n'existe plus aucun moyen pour vous de savoir quels reflogs existent dans le dépôt lorsque vous utilisez le format « reftable ».
Il ne s'agit pas d'une faille imputable au format « reftable », mais d'une omission dans les outils fournis par Git. Pour y remédier, nous avons introduit une nouvelle sous-commande list
à git-reflog(1)
permettant de répertorier tous les reflogs existants :
$ git reflog list
HEAD
refs/heads/main
Ce projet a été mené par Patrick Steinhardt.
Un empaquetage plus efficace des références
Pour rester efficaces, les dépôts Git nécessitent une maintenance régulière. Habituellement, cette maintenance est déclenchée par diverses commandes Git qui écrivent des données dans les dépôts Git en exécutant git maintenance run --auto
. Cette commande optimise uniquement les structures de données qui doivent être optimisées afin que Git ne gaspille pas de ressources de calcul.
Une structure de données optimisée par la maintenance de Git constitue la base de données de référence, obtenue en exécutant git pack-refs --all
. Pour le backend « fichiers », cela signifie que toutes les références sont à nouveau regroupées dans le fichier « packed-refs » et que les références libres sont supprimées, tandis que pour le backend « reftable », toutes les tables sont fusionnées en une seule table.
Pour le backend « fichiers », nous ne pouvons pas faire beaucoup mieux. Étant donné que nous devons de toute façon réécrire l'ensemble du fichier « packed-refs », il est logique que nous souhaitions regrouper toutes les références libres.
Mais pour le backend « reftable », cette démarche est sous-optimale, car le backend « reftable » s'optimise automatiquement. Chaque fois que Git ajoute une nouvelle table au backend « reftable », il effectue un compactage automatique et fusionne les tables si nécessaire. Par conséquent, la base de données de référence doit toujours être dans un état optimisé et la fusion de toutes les tables s'avère donc un effort inutile.
Dans la version 2.45.0 de Git, nous avons donc introduit un nouveau mode git pack-refs --auto
, qui demande au backend de référence de s'optimiser si nécessaire. Alors que le backend « fichiers » continue de fonctionner de la même manière même lorsque l'indicateur --auto
est défini, le backend « reftable » utilise les mêmes heuristiques que celles qu'il utilise pour son auto-compactage. Dans la pratique, cela ne devrait pas poser de problème dans la plupart des cas.
De plus, git maintenance run --auto
a été adapté pour transmettre l'indicateur -tauto
à git-pack-refs(1)
pour utiliser ce nouveau mode par défaut.
Ce projet a été mené par Patrick Steinhardt.
En conclusion
Cet article de blog met l'accent sur le nouveau backend « reftable », qui nous permet de mieux évoluer dans les dépôts volumineux comprenant de multiples références, ainsi que sur les outils connexes que nous avons introduits en parallèle pour le faire fonctionner correctement. Cette version de Git par la communauté Git, au sens large, a bien entendu introduit diverses améliorations de performances, des corrections de bugs et des fonctionnalités plus petites. Vous pouvez en apprendre davantage à ce sujet dans l'annonce officielle du projet Git.