Le projet Git a récemment publié sa version Git 2.49.0. Jetons un coup d'œil aux points forts de cette nouvelle version, comprenant des contributions de l'équipe Git de GitLab et de la communauté Git au sens large.
Commande git-backfill(1) associée à la nouvelle API path-walk
Vous avez désormais la possibilité, lorsque vous exécutez la commande git-clone(1)
sur un dépôt Git, de transmettre l'option --filter
afin de créer un clone partiel. Dans ce type de clonage, le serveur n'envoie qu'un sous-ensemble des objets accessibles, en fonction du filtre d'objets spécifié. Par exemple, la création d'un clone avec --filter=blob:none
signifie que Git ne récupérera pas les blobs (ou contenus de fichiers) auprès du serveur et créera ainsi un clone blobless.
Les clones blobless sont des clones très légers, sans fichiers, qui contiennent l'ensemble des commits et des arbres accessibles, mais aucun blob. Lorsque vous effectuez une opération comme git-checkout(1)
, Git télécharge les blobs manquants pour exécuter la commande. Cependant, certaines opérations comme git-blame(1)
peuvent entraîner un téléchargement séquentiel des objets, ce qui ralentit considérablement leur exécution. Cette dégradation des performances se produit parce que la commande git-blame(1)
doit parcourir l'historique des commits pour identifier les blobs spécifiques requis, puis les demander un par un au serveur.
Pour remédier à cela, Git 2.49.0 introduit une nouvelle sous-commande : git-backfill(1)
. Elle permet de télécharger les blobs manquants dans un clone partiel blobless.
En arrière-plan, la commande git-backfill(1)
tire parti de la nouvelle API path-walk, qui est différente de la méthode traditionnelle d'itération de Git sur les commits. Plutôt que d'itérer sur les commits en les parcourant un par un et de consulter de façon récursive les arbres et les blobs associés à chaque commit, l'API path-walk procède par chemin d'accès. Pour chaque chemin de fichier, elle accumule la liste des objets d'arbre associés à une pile, et cette dernière est ensuite traitée en profondeur. Ainsi, plutôt que de traiter chaque objet du commit 1
avant de passer au commit 2
, elle traite toutes les versions du fichier A
à travers tous les commits avant de passer au fichier B
. Cette approche améliore considérablement les performances dans les scénarios où le regroupement par chemin est essentiel.
En guise d'exemple, nous allons créer un clone blobless du dépôt gitlab-org/git
:
$ git clone --filter=blob:none --bare --no-tags [email protected]:gitlab-org/git.git
Cloning into bare repository 'git.git'...
remote: Enumerating objects: 245904, done.
remote: Counting objects: 100% (1736/1736), done.
remote: Compressing objects: 100% (276/276), done.
remote: Total 245904 (delta 1591), reused 1547 (delta 1459), pack-reused 244168 (from 1)
Receiving objects: 100% (245904/245904), 59.35 MiB | 15.96 MiB/s, done.
Resolving deltas: 100% (161482/161482), done.
Dans l'exemple ci-dessus, nous utilisons l'option --bare
pour nous assurer que Git n'a pas besoin de télécharger de blobs pour effectuer
le checkout d'une branche initiale. Nous pouvons ensuite vérifier que ce clone ne contient effectivement aucun blob :
$ git cat-file --batch-all-objects --batch-check='%(objecttype)' | sort | uniq -c
83977 commit
161927 tree
Si vous souhaitez afficher le contenu d'un fichier dans le dépôt, Git doit le télécharger :
$ git cat-file -p HEAD:README.md
remote: Enumerating objects: 1, done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 1 (from 1)
Receiving objects: 100% (1/1), 1.64 KiB | 1.64 MiB/s, done.
[](https://github.com/git/git/actions?query=branch%3Amaster+event%3Apush)
Git - fast, scalable, distributed revision control system
=========================================================
Git is a fast, scalable, distributed revision control system with an
unusually rich command set that provides both high-level operations
and full access to internals.
[snip]
Comme vous pouvez le voir ci-dessus, Git interroge d'abord le dépôt distant pour télécharger le blob avant de pouvoir l'afficher.
Mais si vous souhaitez effectuer une opération git-blame(1)
sur ce fichier, Git devra télécharger de nombreux autres fichiers :
$ git blame HEAD README.md
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (1/1), 1.64 KiB | 1.64 MiB/s, done.
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (1/1), 1.64 KiB | 1.64 MiB/s, done.
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (1/1), 1.64 KiB | 1.64 MiB/s, done.
remote: Enumerating objects: 1, done.
[snip]
df7375d772 README.md (Ævar Arnfjörð Bjarmason 2021-11-23 17:29:09 +0100 1) [](https://github.com/git/git/actions?query=branch%3Amaster+event%3Apush)
5f7864663b README.md (Johannes Schindelin 2019-01-29 06:19:32 -0800 2)
28513c4f56 README.md (Matthieu Moy 2016-02-25 09:37:29 +0100 3) Git - fast, scalable, distributed revision control system
28513c4f56 README.md (Matthieu Moy 2016-02-25 09:37:29 +0100 4) =========================================================
556b6600b2 README (Nicolas Pitre 2007-01-17 13:04:39 -0500 5)
556b6600b2 README (Nicolas Pitre 2007-01-17 13:04:39 -0500 6) Git is a fast, scalable, distributed revision control system with an
556b6600b2 README (Nicolas Pitre 2007-01-17 13:04:39 -0500 7) unusually rich command set that provides both high-level operations
556b6600b2 README (Nicolas Pitre 2007-01-17 13:04:39 -0500 8) and full access to internals.
556b6600b2 README (Nicolas Pitre 2007-01-17 13:04:39 -0500 9)
[snip]
Nous avons tronqué la sortie, mais comme vous pouvez le constater, Git interroge le serveur séparément pour chaque révision de ce fichier. Ce processus est loin d'être optimal. Avec la commande git-backfill(1)
, nous pouvons demander à Git de télécharger tous les blobs en une seule fois :
$ git backfill
remote: Enumerating objects: 50711, done.
remote: Counting objects: 100% (15438/15438), done.
remote: Compressing objects: 100% (708/708), done.
remote: Total 50711 (delta 15154), reused 14730 (delta 14730), pack-reused 35273 (from 1)
Receiving objects: 100% (50711/50711), 11.62 MiB | 12.28 MiB/s, done.
Resolving deltas: 100% (49154/49154), done.
remote: Enumerating objects: 50017, done.
remote: Counting objects: 100% (10826/10826), done.
remote: Compressing objects: 100% (634/634), done.
remote: Total 50017 (delta 10580), reused 10192 (delta 10192), pack-reused 39191 (from 1)
Receiving objects: 100% (50017/50017), 12.17 MiB | 12.33 MiB/s, done.
Resolving deltas: 100% (48301/48301), done.
remote: Enumerating objects: 47303, done.
remote: Counting objects: 100% (7311/7311), done.
remote: Compressing objects: 100% (618/618), done.
remote: Total 47303 (delta 7021), reused 6693 (delta 6693), pack-reused 39992 (from 1)
Receiving objects: 100% (47303/47303), 40.84 MiB | 15.26 MiB/s, done.
Resolving deltas: 100% (43788/43788), done.
Cette commande permet de télécharger l'ensemble des blobs, transformant ainsi le clone blobless en un clone complet :
$ git cat-file --batch-all-objects --batch-check='%(objecttype)' | sort | uniq -c
148031 blob
83977 commit
161927 tree
Ce projet a été mené par Derrick Stolee et a été fusionné via le commit e565f37553.
Intégration de zlib-ng
Tous les objets contenus dans le dossier .git/
sont compressés par Git à l'aide de zlib
, la bibliothèque de référence conforme à la spécification RFC 1950 :
ZLIB Compressed Data
Format. Créée en 1995, zlib
bénéficie d'une longue histoire et d'une portabilité
incroyable. Elle prend même en charge de nombreux systèmes antérieurs à Internet. En raison de sa
compatibilité étendue avec une grande diversité d'architectures et de compilateurs, elle s'accompagne de certaines
limites techniques.
Pour pallier ces contraintes, une bifurcation nommée zlib-ng
a été créée. zlib-ng
est une version optimisée pour les systèmes modernes. Cette bifurcation abandonne la prise en charge des systèmes hérités au profit d'optimisations Intel, de certaines optimisations Cloudflare et de quelques autres correctifs plus ciblés.
La bibliothèque zlib-ng
elle-même inclut une couche de compatibilité avec zlib
, permettant
d'utiliser zlib-ng
en remplacement immédiat de zlib
. Toutefois,
cette couche de compatibilité n'est pas encore disponible sur toutes les distributions Linux. Dans Git 2.49.0 :
- Une couche de compatibilité a été intégrée directement au projet Git.
- Des options de compilation ont été ajoutées à la fois au fichier
Makefile
et au fichier de compilation Meson.
Grâce à ces ajouts, vous tirez plus facilement parti des gains de performances procurés par
zlib-ng
.
Lors de benchmarks en local, nous avons constaté une accélération d'environ 25 % en utilisant zlib-ng
au lieu de zlib
. Nous sommes d'ailleurs en train de déployer progressivement ces améliorations sur
GitLab.com.
Si vous souhaitez bénéficier des améliorations apportées par zlib-ng
, vérifiez d'abord si Git
sur votre machine l'utilise déjà en exécutant la
commande git version --build-options
:
$ git version --build-options
git version 2.47.1
cpu: x86_64
no commit associated with this build
sizeof-long: 8
sizeof-size_t: 8
shell-path: /bin/sh
libcurl: 8.6.0
OpenSSL: OpenSSL 3.2.2 4 Jun 2024
zlib: 1.3.1.zlib-ng
Si la dernière ligne contient zlib-ng
, votre instance Git est déjà créée
à l'aide de la variante optimisée de zlib
. Sinon, vous pouvez :
- Soit demander au chargé de maintenance du paquet Git que vous utilisez d'inclure la prise en charge de
zlib-ng
. - Soit compiler Git vous-même à partir de la source.
Ces améliorations ont été introduites par Patrick Steinhardt.
Poursuite de l'optimisation de Meson
Dans notre article sur la version 2.48.0 de Git, nous avons évoqué l'introduction du système de compilation Meson. Meson est un outil d'automatisation de compilation utilisé par le projet Git qui, à terme, pourrait remplacer Autoconf, CMake et peut-être même Make.
Au cours de ce cycle de sortie de nouvelles versions, nous avons poursuivi notre optimisation de l'utilisation de Meson, en ajoutant diverses fonctionnalités manquantes et des correctifs de stabilisation :
- L'amélioration de la couverture de test dans le cadre des pratiques CI a été fusionnée dans le commit 72f1ddfbc9.
- Des ajustements pour permettre l'utilisation de Meson dans
contrib/
ont été fusionnés dans le commit 2a1530a953. - Des correctifs et améliorations diverses de la procédure de compilation basée sur Meson ont été fusionnées dans le commit ab09eddf60.
- La prise en charge par Meson de la compilation
de
git-subtree(1)
a été fusionnée dans le commit 3ddeb7f337. - L'apprentissage par Meson de la génération des pages de la documentation HTML a été fusionné dans le commit 1b4e9a5f8b.
L'ensemble de ces contributions a été mené par Patrick Steinhardt.
Retrait définitif des sous-répertoires .git/branches/ et .git/remotes/
Vous connaissez probablement l'existence du répertoire .git
et de son
contenu. Mais avez-vous déjà entendu parler des sous-répertoires .git/branches/
et
.git/remotes/
? Comme vous le savez peut-être, les références aux branches sont stockées dans
.git/refs/heads/
, ce n'est donc pas à cela que sert .git/branches/
, et qu'en est-il de
.git/remotes/
?
En 2005, le sous-répertoire .git/branches/
a été introduit afin de stocker le nom abrégé de dépôts distants, et quelques mois plus tard, ces informations ont été
déplacées vers .git/remotes/
.
Puis, en 2006,
à l'aide de la commande git-config(1)
, il a été possible de gérer
les dépôts distants de manière centralisée,
ce qui est devenu la méthode standard de configurer des dépôts distants. En 2011, les
sous-répertoires .git/branches/
et .git/remotes/
ont été
documentés
comme étant « hérités » et obsolètes. Ils ne sont plus utilisés dans les dépôts modernes.
Enfin en 2024, la documentation BreakingChanges a été créée pour répertorier les changements cassants de la prochaine version majeure de Git (v3.0). Bien que cette nouvelle version ne soit pas encore planifiée dans un avenir proche, cette documentation permet de suivre les modifications significatives à venir.
Dans le commit 8ccc75c245, l'utilisation des sous-répertoires .git/branches/
et .git/remotes/
a été ajoutée à cette liste, en les marquant officiellement comme obsolètes et voués à être supprimés dans Git 3.0.
Merci à Patrick Steinhardt d'avoir formalisé ce retrait définitif.
Ajout de liaisons en Rust pour libgit
Lors de la compilation de Git, une bibliothèque interne nommée libgit.a
est créée. Elle contient
certaines des fonctionnalités de base de Git.
Bien que cette bibliothèque (comme la majeure partie de Git) soit écrite en C, la version Git 2.49.0 introduit des liaisons
pour que certaines de ces fonctions deviennent disponibles en langage Rust. Pour ce faire, deux
nouveaux paquets Cargo ont été créés : libgit-sys
et libgit-rs
. Ils
se trouvent dans le sous-répertoire contrib/
de l'arbre source de Git.
C'est une pratique assez
courante
de diviser une bibliothèque en deux packages lorsqu'une interface de fonction
étrangère est utilisée.
Le paquet libgit-sys
fournit l'interface directe et minimaliste vers les fonctions en C et effectue le lien avec la bibliothèque native libgit.a
. Le paquet libgit-rs
fournit une interface de haut niveau vers les fonctions de libgit-sys
, assez similaire à Rust.
Jusqu'à présent, les fonctionnalités de ces packages Rust sont très limitées : elles fournissent uniquement
une interface d'interaction avec la commande git-config(1)
.
Cette initiative a été menée par Josh Steadmon et a été fusionnée via le commit a4af0b6288.
Nouvel algorithme de hachage de noms
La base de données d'objets Git dans .git/
stocke la plupart de ses données sous forme de fichiers d'empaquetage. Ces derniers sont également utilisés pour transférer des objets entre le serveur et le client Git par le biais du réseau.
Pour en savoir plus sur le format de ces fichiers, consultez la documentation gitformat-pack(5)
. D'autre part, les fichiers d'empaquetage
utilisent une technique de compression qui a son importance, appelée la compression delta. Avec ce type de compression, tous les objets ne sont pas stockés dans leur intégralité : certains sont enregistrés en tant que delta d'une autre base. Ainsi, au lieu d'enregistrer le contenu complet des objets, ce sont les modifications par rapport à un autre objet de référence qui sont stockées.
Sans détailler la façon dont ces deltas sont calculés ou stockés, vous vous doutez bien qu'il est essentiel de regrouper les fichiers très similaires pour optimiser la compression. Jusqu'à la
version v2.48.0, Git examinait les 16 derniers caractères du nom du chemin d'accès au fichier pour déterminer si les blobs semblaient similaires, à l'aide d'un algorithme nommé « version 1
».
Dans Git 2.49.0, l'algorithme « version 2
» a été introduit. Il s'agit d'une itération de l'algorithme version 1
, mais modifié, de sorte que l'impact du répertoire parent dans le calcul est réduit. Vous pouvez spécifier la version de l'algorithme de hachage de noms à utiliser à l'aide de l'option --name-hash-version
de la commande git-repack(1)
.
Derrick Stolee, qui a mené ce projet, a effectué une
comparaison de la taille des fichiers d'empaquetage après exécution de git repack -adf --name-hash-version=<n>
:
Dépôt | Taille avec la version 1 | Taille avec la version 2 |
---|---|---|
fluentui | 440 Mo | 161 Mo |
Dépôt B | 6 248 Mo | 856 Mo |
Dépôt C | 37 278 Mo | 6 921 Mo |
Dépôt D | 131 204 Mo | 7 463 Mo |
Pour en savoir plus, consultez l'ensemble de correctifs, qui a été fusionné dans le commit aae91a86fb.
Capacité de promisor remote
Il est de notoriété publique que Git ne sait pas très bien gérer les fichiers volumineux. Des solutions comme Git LFS existent pour résoudre ce problème, mais celles-ci comportent malgré tout certaines lacunes. En voici quelques exemples :
- Avec Git LFS, l'utilisateur doit configurer les fichiers à placer dans LFS. Le serveur n'a aucun contrôle sur ce choix et doit donc servir tous les fichiers.
- Chaque fois qu'un fichier est validé dans le dépôt, il est impossible de le récupérer sans réécrire l'historique. C'est un vrai problème, surtout pour les fichiers volumineux qui sont bloqués pour toujours.
- Les utilisateurs ne peuvent pas changer d'avis une fois le fichier sélectionné et placé dans Git LFS.
- Configurer, apprendre et utiliser de manière optimale un outil comme Git LFS est fastidieux.
Depuis un certain temps, Git a adopté le concept de « promisor remotes ». Cette fonctionnalité pouvant être utilisée pour gérer des fichiers volumineux a été améliorée dans Git 2.49.0.
L'idée de la capacité « promisor remote » est relativement simple : au lieu d'envoyer lui-même tous les objets, un serveur Git peut indiquer au client Git de télécharger ces objets à partir de XYZ. XYZ représente alors un « promisor remote ».
Git 2.49.0 permet désormais au serveur de transmettre les informations du « promisor remote »
au client. Cette amélioration est une extension du protocole
gitprotocol-v2
. Pendant l'échange
de données, le serveur peut ainsi envoyer au client les noms et les URL des « promisor remotes » dont il a connaissance.
Jusqu'à présent, le client n'utilise pas les informations du « promisor remote » qu'il reçoit du serveur lors d'un clonage, de sorte que tous les objets sont toujours transmis depuis le dépôt distant à partir duquel le clonage a été initié. Nous envisageons de poursuivre l'optimisation de cette fonctionnalité pour faire en sorte de permettre au client d'utiliser les informations du « promisor remote » du serveur, ainsi que pour simplifier son utilisation.
Cet ensemble de correctifs a été soumis par Christian Couder et fusionné via le commit 2c6fd30198.
Clone léger utilisant --revision
Une nouvelle option --revision
a été ajoutée à la commande git-clone(1)
. Elle vous permet de créer un clone léger d'un dépôt ne contenant que l'historique associé à une révision donnée. Elle fonctionne de manière similaire à --branch
, mais accepte un nom de référence (comme refs/heads/main
, refs/tags/v1.0
et refs/merge-requests/123
) ou un ID d'objet de commit hexadécimal. La différence avec --branch
est qu'elle ne crée pas de branche de suivi et le pointeur HEAD
est détaché. Cette option n'est donc pas adaptée si vous souhaitez contribuer à cette branche.
Vous pouvez combiner --revision
avec --depth
pour créer un clone minimal, par exemple dans le cadre de tests automatisés. Lorsque vous disposez d'un système CI qui doit effectuer le checkout d'une branche (ou toute autre référence) pour exécuter des tests autonomes sur le code source, un clone minimal suffit largement.
Ce changement a été mené par Toon Claes.