Il y a 2 ans -
Temps de lecture 7 minutes
Docteur, j’ai commité 8 Go dans mon Git. C’est grave ?
Dans cet article, nous allons voir les conséquences de commiter de trop gros fichiers sur un dépôt Git et surtout une solution pour y remédier.
Histoire
Derrière ce titre délibérément provocateur se cache une situation réelle que nous avons vécue sur un projet de développement d’une Plateforme Data Science pour une grande entreprise pharmaceutique française. Une des fonctionnalités phare de cette plateforme est la possibilité de lancer en self-service des Jupyter Notebooks pour explorer des données du Data Lake.
La semaine dernière un de nos utilisateurs nous a sollicité pour un problème au lancement de son notebook qui échouait avec le message « Request Timeout ». Une analyse rapide a montré que la commande qui « tombait » en timeout était : git clone <repository>
.
Nous avons tout de suite tenté de cloner ce dépôt en local. Mais hélas, l’opération est resté coincé à 78% sur la récupération d’un objet particulièrement volumineux. Finalement, après un peu plus de 50 minutes (!) le clone s’est terminé avec succès.
~/data-scientist-workbench$ du -sh . 8.5G .
Aie, 8.5Go ! La première idée était « Mais quelqu’un a commité des binaires là ?! ». Pas de problème, qu’est-ce qui est plus facile que d’afficher la liste des fichiers d’un répertoire ordonné par leur taille ?
~/data-scientist-workbench$ du -a . | sort -n -r | head -n 20 17900496 . 17900480 ./.git 17900232 ./.git/objects 3835800 ./.git/objects/18/65557fcce7b1e7e1f68b066d10db1a2322be80 3835800 ./.git/objects/18 3770328 ./.git/objects/76/134c092389441d2691c4b070b6e67c7fd82305 3770328 ./.git/objects/76 3277272 ./.git/objects/82/1132620aecc7b52d5e90e0e9bc1adb2db8cb3a 3277272 ./.git/objects/82 2950072 ./.git/objects/a6/2e5802a252c868b7d3c8f10520afdfa08d0c2b 2950072 ./.git/objects/a6 1737024 ./.git/objects/9e/7ec901132588e2daa5f9dbe03d7498df0b0327 1737024 ./.git/objects/9e 1541800 ./.git/objects/df/0bee1328ff7000e4e50f9d89b120a6773f165a 1541800 ./.git/objects/df 787704 ./.git/objects/57/11cedfdeca43661a087a9f2162eb62cdffcc46 787704 ./.git/objects/57 96 ./.git/hooks ...
Eh non, apparemment le fichier que nous cherchons a été supprimé mais il reste dans l’historique de Git. La commande git clone
avec l’option --depth 1
qui passe en quelques secondes sur le même dépôt confirme notre supposition.
À ce moment, notre utilisateur commence à paniquer sérieusement. Ce dépôt est utilisé par une trentaine de Data Scientists à travers le monde. Face à sa détresse, nous décidons de continuer l’analyse. Mais comment trouver le ou les fichiers responsables ?
Git Filter Repo
Après quelques recherches, nous nous orientons vers l’outil git-filter-repo. C’est un outil puissant qui permet, entre autres, de rechercher de gros fichiers dans l’historique des commits Git avec la commande --analyze
. Mais aussi de pouvoir les supprimer en réécrivant l’historique des commits concernés avec le commande --invert-path
.
L’installation
L’installation s’est avérée très facile. Il suffit de récupérer un fichier python et de le placer dans le PATH
. Au passage, n’oublions pas de le rendre exécutable.
~$ wget https://raw.githubusercontent.com/newren/git-filter-repo/main/git-filter-repo ... Saving to: ‘git-filter-repo’ git-filter-repo 100%[===============================================>] 159,40K --.-KB/s in 0,01s 2021-04-03 15:38:37 (15,4 MB/s) - ‘git-filter-repo’ saved [163227/163227] ~$ chmod +x git-filter-repo ~$ export PATH=~:$PATH
À noter qu’il est possible de l’installer de différentes façons et notamment via votre gestionnaire de paquets préféré.
L’analyse
Voyons de quoi est capable cet outil :
~/data-scientist-workbench$ git filter-repo --analyze Processed 36 blob sizes Processed 9 commits Writing reports to .git/filter-repo/analysis...done.
Visiblement git-filter-repo nous a généré différents rapports dans .git/filter-repo/analysis
.
Voyons voir :
~/data-scientist-workbench$ ls -l .git/filter-repo/analysis total 64 -rw-r--r-- 1 jovyan staff 3379 Apr 3 17:13 README -rw-r--r-- 1 jovyan staff 920 Apr 3 17:13 blob-shas-and-paths.txt -rw-r--r-- 1 jovyan staff 274 Apr 3 17:13 directories-all-sizes.txt -rw-r--r-- 1 jovyan staff 109 Apr 3 17:13 directories-deleted-sizes.txt -rw-r--r-- 1 jovyan staff 503 Apr 3 17:13 extensions-all-sizes.txt -rw-r--r-- 1 jovyan staff 428 Apr 3 17:13 extensions-deleted-sizes.txt -rw-r--r-- 1 jovyan staff 678 Apr 3 17:13 path-all-sizes.txt -rw-r--r-- 1 jovyan staff 496 Apr 3 17:13 path-deleted-sizes.txt -rw-r--r-- 1 jovyan staff 0 Apr 3 17:13 renames.txt ~/data-scientist-workbench$ cat .git/filter-repo/analysis/path-all-sizes.txt === All paths by reverse accumulated size === Format: unpacked size, packed size, date deleted, path name 1960946160 1961542362 2021-04-03 IMA/cache.case-324 1928263724 1928849986 2021-04-03 IMA/cache.case-9812 1660946160 1661451152 2021-04-03 IMA/cache.case-5571 1494851544 1495306041 2021-04-03 IMA/cache.case-751 885837952 886107293 2021-04-03 IMA/cache.case-8864 774268208 774503629 2021-04-03 IMA/cache.case-512 400000000 400121631 2021-04-03 IMA/cache.case-62431 6 36 IMA/f1.txt 0 15 README.md 0 15 FLAK/f1.txt 0 15 DS-TESTS/f1.txt
À ce moment, il y a un « Ah » prolongé de notre utilisateur : « Mais oui, je me rappelle de ces fichiers, c’est mon application qui les a générées. Je les ai supprimés il y a 2 jours ! Ils étaient dans ma branche. »
Il a de la chance finalement ! Nous n’allons pas devoir réécrire l’historique du master avec toutes les conséquences que cet acte barbare peut provoquer ! Cette opération pourra être faite sur sa branche, ce qui est bien moins grave car lui seul y travaille.
Dry run d’abord
Avant de réécrire l’historique pour supprimer les fichiers en question, il est prudent d’essayer les commandes avec l’option --dry-run
:
~/data-scientist-workbench$ git checkout Prod_DS_Shiny ~/data-scientist-workbench$ git filter-repo --invert-path --path-regex IMA/cache.case-.* --dry-run Parsed 9 commits New history written in 0.12 seconds; now repacking/cleaning... NOTE: Not running fast-import or cleaning up; --dry-run passed. Requested filtering can be seen by comparing: .git/filter-repo/fast-export.original .git/filter-repo/fast-export.filtered
Nous pouvons alors vérifier :
~/data-scientist-workbench$ cat .git/filter-repo/fast-export.original | grep cache.case M 100644 5711cedfdeca43661a087a9f2162eb62cdffcc46 IMA/cache.case-62431 M 100644 df0bee1328ff7000e4e50f9d89b120a6773f165a IMA/cache.case-512 M 100644 9e7ec901132588e2daa5f9dbe03d7498df0b0327 IMA/cache.case-8864 ... ~/data-scientist-workbench$ cat .git/filter-repo/fast-export.filtered | grep cache.case ~/data-scientist-workbench$
Ça a l’air bien : le fast-export.filtered
ne contient aucune référence aux fichiers problématiques.
La correction
Le moment de vérité – exécutons la même commande sans --dry-run
:
~/data-scientist-workbench$ git filter-repo --invert-path --path-regex IMA/cache.case-.* Parsed 9 commits New history written in 0.15 seconds; now repacking/cleaning... Repacking your repo and cleaning out old unneeded objects HEAD is now at 9415b6b Remove cases Enumerating objects: 8, done. Counting objects: 100% (8/8), done. Delta compression using up to 12 threads Compressing objects: 100% (4/4), done. Writing objects: 100% (8/8), done. Total 8 (delta 1), reused 0 (delta 0), pack-reused 0 Completely finished after 0.32 seconds.
Ouh là ! 0.32 secondes – c’était rapide !
Un petit check :
~/data-scientist-workbench$ du -sh . 2M .
Oui ! Victoire !
Après la promesse de plusieurs bières par notre utilisateur dans le monde post-Covid, nous pouvons tranquillement pousser la branche vers origin
pour terminer la correction :
~/data-scientist-workbench$ git push origin Prod_DS_Shiny --force
Conclusion
Qu’avons nous appris de cette histoire ?
Nous savons depuis longtemps que commiter de gros fichiers dans Git n’est pas une bonne idée, et là nous avons eu une bonne illustration des conséquences que cela peut avoir. En effet, cela peut avoir un sérieux impact négatif sur la production — et imaginez ce qui se serait passé si la branche avait été mergée surmaster
et récupérée par d’autres développeurs.
Preuve qu’il est important de systématiquement vérifier ce qu’on commit et d’utiliser le .gitignore
.
Finalement, git-filter-repo
est un outil versatile pour réécrire l’historique Git qui est capable de beaucoup de choses. Il peut vous sauver dans les situations délicates, mais doit être utilisé avec beaucoup de précautions. A noter qu’il serait possible de faire la même chose en faisant un rebase interactif avec git rebase -i
, et en utilisant le modificateur edit
pour un commit donné, mais cela serait plus fastidieux, surtout s’il faut éditer plusieurs commits.
Commentaire
1 réponses pour " Docteur, j’ai commité 8 Go dans mon Git. C’est grave ? "
Published by Jacques Pyrat , Il y a 2 ans
Merci pour cet article très instructif.
Attention au terme « versatile ».
En anglais, il signifie, polyvalent.
En français, il signifie « tellement changeant qu’il n’est pas fiable de s’y fier ».
Oui, j’ai fait l’erreur durant des années ;-)