Illustration du nettoyage de l'historique d'un repository Git

Comment réduire la taille d’un repository Git ?

Dans le développement logiciel, Git est devenu l’outil incontournable pour gérer efficacement le code source de nos projets. Toutefois, un problème courant auquel de nombreux développeurs sont confrontés est l’augmentation progressive de la taille d’un repository Git. Cela peut nuire aux performances et ralentir les opérations courantes comme les clonages ou les fetchs.

Pour maintenir un dépôt Git léger et performant, il est essentiel de respecter les bonnes pratiques : éviter de versionner les fichiers binaires ou les fichiers qui n’ont pas vocation à faire partie du code source. Cependant, avec le temps, ces règles sont souvent négligées, et les dépôts finissent par devenir trop volumineux.

Alors, comment réduire la taille d’un repository Git en corrigeant son historique et en supprimant les données inutiles ?

Pourquoi la taille d’un repository Git est-elle importante ?

Un repository Git est une base de données qui stocke l’historique complet des modifications apportées à un projet. Au fil des contributions, les changements accumulés alourdissent progressivement le dépôt, ce qui peut compliquer son utilisation au quotidien.

En effet, lorsque nous versionnons du code source, Git enregistre les modifications sous forme de différences : des ajouts ou suppressions de lignes dans un fichier, ou des modifications de l’arborescence du projet. Cependant, les fichiers binaires présentent un réel défi. Contrairement aux fichiers texte, Git ne peut pas analyser efficacement les différences dans un fichier binaire. Ainsi, chaque mise à jour de ces fichiers est enregistrée sous forme de blocs massifs de données, augmentant considérablement la taille du repository.

Les fichiers non essentiels au code source et les fichiers binaires inutiles contribuent également à l’encombrement du dépôt. Cela peut entraîner des problèmes de performance, des temps de clonage excessivement longs, et même des coûts de stockage accrus, en particulier pour les projets hébergés sur des plateformes payantes. En respectant les bonnes pratiques, telles que celles évoquées précédemment, il est possible d’éviter ces problèmes et de maintenir un repository Git performant et léger.

Présentation d’un dépôt Git problématique

Un cas concret rencontré par notre équipe

Pour illustrer la problématique liée à la taille d’un repository Git, examinons un cas concret. Il s’agit d’un projet destiné à configurer une machine dans une zone hors réseau, nécessitant l’utilisation d’un repository de packages local.

Initialement, les développeurs du projet utilisaient le gestionnaire de version SVN pour stocker les livrables et les images ISO directement dans le dépôt. Après la migration du repository vers Git, nous avons constaté que sa taille dépassait 20 Go.

Ce volume pose de réelles difficultés pour continuer à maintenir de vieilles version :

  • Un nouveau développeur doit télécharger l’intégralité du repository pour commencer à travailler, ce qui peut être extrêmement lent.
  • Le PC doit également disposer de suffisamment d’espace pour accueillir ces 20 Go.

Reproduction d’un repository Git similaire

Historique du projet

Pour reproduire ce problème, voici un aperçu d’un repository Git minimal contenant des fichiers problématiques :

$ git log --oneline --all --graph
*   b40b3b6 (HEAD -> develop, origin/develop) Merge branch 'release/v1.1.0' into develop
|\
| * bc9be6b (tag: v1.1.0, origin/release/v1.1.0, release/v1.1.0) Release v1.1.0
* | baf47cb Add gitignore to clean git repo
* | 15b93ed Add doc for repository management
|/
* 29f97e3 Prepare repository for next release
* 9e5db22 (tag: v1.0.0, origin/release/v1.0.0, release/v1.0.0) Release v1.0.0
* 7e75515 Add doc for local repository
* 7b2dc30 (origin/master, master) Add readme file to project

Ce repository contient deux releases ainsi qu’un commit spécifique (baf47cb) introduisant un fichier .gitignore pour limiter le stockage des fichiers problématiques.

Analyse du dépôt Git

Malgré le fichier .gitignore, la taille totale du dépôt reste significative. Voici le détail des fichiers présents dans le repository, atteignant 221 Mo :

$ git --no-pager log --oneline --stat
b40b3b6 (HEAD -> develop, origin/develop) Merge branch 'release/v1.1.0' into develop
baf47cb Add gitignore to clean git repo
 .gitignore | 7 +++++++
 1 file changed, 7 insertions(+)
15b93ed Add doc for repository management
 README.md | 4 ++++
 1 file changed, 4 insertions(+)
bc9be6b (tag: v1.1.0, origin/release/v1.1.0, release/v1.1.0) Release v1.1.0
 VERSION.md  |   2 +-
 project.zip | Bin 0 -> 62924875 bytes
 2 files changed, 1 insertion(+), 1 deletion(-)
29f97e3 Prepare repository for next release
 CHANGELOG.md |   6 ++++++
 project.zip  | Bin 52437333 -> 0 bytes
 repo.iso     | Bin 52428800 -> 62914560 bytes
 3 files changed, 6 insertions(+)
9e5db22 (tag: v1.0.0, origin/release/v1.0.0, release/v1.0.0) Release v1.0.0
 VERSION.md  |   1 +
 project.zip | Bin 0 -> 52437333 bytes
 2 files changed, 1 insertion(+)
7e75515 Add doc for local repository
 README.md |   5 +++++
 repo.iso  | Bin 0 -> 52428800 bytes
 2 files changed, 5 insertions(+)
7b2dc30 (origin/master, master) Add readme file to project
 README.md | 1 +
 1 file changed, 1 insertion(+)

En analysant ce repository, nous identifions deux fichiers particulièrement problématiques :

  • repo.iso : une image ISO contenant les packages nécessaires pour l’OS.
  • project.zip : un fichier ZIP contenant le livrable déployable sur l’OS, qui inclut également l’image ISO et d’autres fichiers.

Ces fichiers binaires, à eux seuls, contribuent fortement à l’alourdissement du dépôt. Ce problème illustre clairement pourquoi la taille d’un repository Git doit être maîtrisée pour garantir des performances optimales.

Préparation du nettoyage du dépôt Git

Avant de procéder au nettoyage de votre repository Git, il est important de noter qu’une fois les gros fichiers supprimés de l’historique, ils ne pourront plus être récupérés. Il convient donc de planifier soigneusement cette opération. Deux approches principales s’offrent à vous :

  • Conserver un repository en lecture seule contenant les fichiers volumineux
  • Stocker les fichiers volumineux sur un autre support avant leur suppression

Conserver un repository en lecture seule contenant les fichiers volumineux

Dans cette option, vous créez un dépôt Git en lecture seule (readonly) qui contiendra les fichiers lourds tels que les packages et les ISO. Ce repository ne sera plus modifié, et les bonnes pratiques seront appliquées uniquement sur le nouveau repository.

Avantages :

  • Temps de transfert amélioré, car le nouveau dépôt sera allégé.
  • Mise en place simple et rapide, nécessitant peu d’efforts.

Inconvénients :

  • Le coût du stockage en ligne reste inchangé, voire légèrement plus important, car le dépôt est dupliqué.
  • Cette solution ne convient pas si votre DSI exige une réduction de l’espace de stockage sur le dépôt distant (remote).

Stocker les fichiers volumineux sur un autre support avant leur suppression

Dans cette seconde option, vous extrayez les fichiers volumineux de l’historique Git pour les stocker sur un support externe ou un serveur dédié. Voici quelques options pour leur stockage :

  • Un serveur de packaging comme Nexus
  • Un serveur FTP/SFTP
  • Un disque dur ou une clé USB

Le choix des fichiers à sauvegarder dépend de vos besoins : souhaitez-vous conserver une trace des livrables ou seulement pouvoir régénérer les fichiers si nécessaire ?

Avantages :

  • Réduction significative du besoin en stockage online.
  • Temps de transfert optimisé, même pour les repositories volumineux.

Inconvénients : Cette méthode peut être longue et fastidieuse, surtout si le repository contient de nombreuses versions à traiter.

Quelle option choisir ?

Le choix entre ces deux approches dépend de vos contraintes techniques, de la taille de votre repository, et des ressources disponibles. L’option 1 est idéale pour une solution rapide et simple à mettre en place, tandis que l’option 2 permet une optimisation plus complète du stockage, mais nécessite un effort plus important.

Nettoyage du repository via git filter-branch

Pourquoi choisir git filter-branch ?

De nombreux outils permettent de nettoyer un repository Git, avec des performances variables. Cependant, pour un nettoyage ponctuel, l’outil officiel de Git, git filter-branch, reste une option fiable et accessible, malgré certains avertissements concernant son usage.

Nettoyage de l’historique Git

Commencez par cloner le repository en local pour travailler dans un environnement isolé :

$ git clone $URL repo # <1>
$ cd repo

Une fois le clone effectué et le dépôt à jour, vous pouvez exécuter la commande suivante pour nettoyer les fichiers volumineux :

$ git filter-branch --force --index-filter \
    'git rm --cached --ignore-unmatch *.iso *.zip' \
    --prune-empty -- --all

Voici quelques explications sur les options utilisées :

  • --index-filter : Exécute la commande git rm sur chaque commit du dépôt sans réécrire tous les blobs (améliore les performances).
  • git rm --cached --ignore-unmatch : Supprime les fichiers correspondant aux patterns (*.iso et *.zip) uniquement de l’index, sans échouer si aucun fichier correspondant n’est trouvé.
  • --prune-empty : Élimine les commits vides générés par la suppression des fichiers.
  • -- --all : Applique la commande sur toutes les branches et références du repository.

Résultats du nettoyage

Une fois la commande exécutée, nous obtenons le résultat suivant :

WARNING: git-filter-branch has a glut of gotchas generating mangled history
	 rewrites.  Hit Ctrl-C before proceeding to abort, then use an
	 alternative filtering tool such as 'git filter-repo'
	 (https://github.com/newren/git-filter-repo/) instead.  See the
	 filter-branch manual page for more details; to squelch this warning,
	 set FILTER_BRANCH_SQUELCH_WARNING=1.
Proceeding with filter-branch...

Rewrite 7b2dc305714847a3dd8bb1b45ec329cf29ff93f6 (1/8) (0 seconds passed, remainRewrite 7e75515bd80e0b0e5d82bb82c6d30da78f645651 (2/8) (0 seconds passed, remaining 0 predicted)    rm 'repo.iso'
Rewrite 9e5db222aa8f9d74aa2213a1f3e04626d13fe100 (3/8) (0 seconds passed, remaining 0 predicted)    rm 'project.zip'
rm 'repo.iso'
Rewrite 29f97e364eb4ef2c6fbc131d231e99410288828f (4/8) (0 seconds passed, remaining 0 predicted)    rm 'repo.iso'
Rewrite 15b93ed850d22ce1b43b5e84419b2d734437307a (5/8) (0 seconds passed, remaining 0 predicted)    rm 'repo.iso'
Rewrite baf47cb907bd8b0bce500bc27053209629afd9c8 (6/8) (0 seconds passed, remaining 0 predicted)    rm 'repo.iso'
Rewrite bc9be6ba241d72348166476ba2d0bc7684c908a2 (7/8) (0 seconds passed, remaining 0 predicted)    rm 'project.zip'
rm 'repo.iso'
Rewrite b40b3b66db7184355818c385d1a1d134b703f5eb (8/8) (0 seconds passed, remaining 0 predicted)    rm 'project.zip'
rm 'repo.iso'

Ref 'refs/heads/develop' was rewritten
WARNING: Ref 'refs/heads/master' is unchanged
Ref 'refs/heads/release/v1.0.0' was rewritten
Ref 'refs/heads/release/v1.1.0' was rewritten
Ref 'refs/remotes/origin/develop' was rewritten
WARNING: Ref 'refs/remotes/origin/master' is unchanged
Ref 'refs/remotes/origin/release/v1.0.0' was rewritten
Ref 'refs/remotes/origin/release/v1.1.0' was rewritten
Ref 'refs/tags/v1.0.0' was rewritten
Ref 'refs/tags/v1.1.0' was rewritten

Vous constaterez que les fichiers problématiques (par exemple repo.iso et project.zip) sont bien supprimés de l’historique.

Pousser les modifications sur un nouveau repository

Bien que les fichiers problématiques aient été supprimés localement, la taille du remote repository ne changera pas si vous forcez le push (git push --force). Cela s’explique par le fait que Git conserve tout l’historique de l’ancien dépôt.

Pour résoudre ce problème, il est nécessaire de créer un nouveau repository vide, puis de pousser toutes vos modifications locales :

$ git remote add newer $URL # <1>
$ git push newer --all # <2>
$ git push newer --tags # <3>
1

Remplacer $URL avec l’URI de votre nouveau dépôt Git.

2

Pousser toutes les branches.

3

Pousser tous les tags.

Après ces étapes, vous observerez une diminution significative de la taille du dépôt :

  • Taille de l’ancien repository (origin) : 221 Mo
  • Taille du nouveau repository (newer) : 320 Ko

Conclusion

En suivant ces étapes, nous avons allégé notre repository Git tout en préservant l’historique utile de vos modifications. Toutefois, il ne suffit pas de nettoyer le repository une fois. Il est essentiel d’adopter les bonnes pratiques pour éviter que le problème ne se reproduise :

  • Mettre en place un .gitignore pertinent : Exclure dès le départ les fichiers volumineux (binaires, ISO, logs, etc.) du versionnage Git.
  • Sensibiliser votre équipe : Ajoutez un fichier CONTRIBUTING.md pour documenter les bonnes pratiques à suivre.

Ce nettoyage ne se limite pas à améliorer les performances techniques. La réduction de la taille des repositories Git contribue également à diminuer l’empreinte environnementale associée au stockage en ligne :

  • Impact énergétique des serveurs : le stockage des données consomme de l’énergie à laquelle s’ajoutent les émissions de gaz à effet de serre.
  • Optimisation du réseau : les repositories plus légers réduisent la consommation énergétique liée aux transferts réseau.

Adopter des pratiques de versionnage responsables s’inscrit dans une démarche globale de sobriété numérique, qui vise à limiter l’impact environnemental de nos activités numériques. En allégeant vos repositories, vous améliorez non seulement la productivité de votre équipe, mais vous contribuez également à réduire les coûts énergétiques et environnementaux !

Laisser un commentaire