Il y a 3 mois -

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.

Publié par Dmytro Podyachiy

Développeur full stack passionné par le monde open source, Dmytro travaille depuis plusieurs années dans l’écosystème Java sur des missions innovantes. Évangéliste du framework Angular, il l’utilise depuis presque 3 ans. Intéressé par les langages différents, il utilise notamment Scala et Javascript coté serveur.

Commentaire

1 réponses pour " Docteur, j’ai commité 8 Go dans mon Git. C’est grave ? "

  1. Published by , Il y a 3 mois

    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 ;-)

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.