Aller au contenu
Développement medium

Nettoyer l'historique et les fichiers Git

12 min de lecture

Le fichier est dans .gitignore mais Git le suit toujours ? Vous avez commité un secret ? Votre repo fait des centaines de Mo à cause d'un binaire ? Ce guide couvre les trois problèmes de nettoyage les plus fréquents : arrêter de suivre un fichier, purger des données sensibles de tout l'historique, et réduire la taille d'un dépôt.

Prérequis : Enregistrer des modifications et Réécrire l'historique.

  • Arrêter de suivre un fichier sans le supprimer du disque
  • Supprimer un fichier sensible de tout l'historique avec git filter-repo
  • Appliquer le bon workflow selon que le secret est local ou déjà poussé
  • Déboguer .gitignore quand un fichier continue d'être tracké malgré la règle

Les deux premières lignes se traitent en local, en quelques secondes, sans conséquence pour personne. Les deux dernières impliquent de réécrire l'historique et donc de coordonner toute l'équipe. Identifiez votre symptôme ici avant d'aller plus loin : appliquer la solution des lignes 3 et 4 à un problème des lignes 1 et 2 force vos collègues à recloner pour rien.

SymptômeCauseSection
Fichier dans .gitignore mais suivi par GitAjouté avant le .gitignore« Arrêter de suivre un fichier »
.gitignore ne semble pas fonctionnerErreur de syntaxe ou de priorité« Débugger un .gitignore »
Secret/mot de passe commité par erreurVisibles dans l'historique« Supprimer un secret de l'historique »
Dépôt volumineux (> 100 Mo)Fichier binaire dans l'historique« Réduire la taille du dépôt »

Vous avez ajouté config/local.json au repo avant de mettre la règle dans .gitignore. Le fichier est ignoré pour les futures modifications, mais Git le suit toujours car il est dans l'index.

Fenêtre de terminal
git rm --cached config/local.json

--cached retire le fichier de l'index (Git arrête de le suivre) sans le supprimer du disque. Le fichier reste dans votre répertoire de travail.

Commitez le changement :

Fenêtre de terminal
git commit -m "chore: arrêter de suivre config/local.json"

Vérification :

Fenêtre de terminal
git status
# config/local.json ne doit plus apparaître

Pour un dossier entier :

Fenêtre de terminal
git rm -r --cached node_modules/
git commit -m "chore: arrêter de suivre node_modules"

Le .gitignore ne fonctionne pas comme attendu ? Utilisez git check-ignore pour comprendre pourquoi. L'écriture des motifs et les trois sources de règles sont traitées dans ignorer des fichiers avec .gitignore ; cette section se concentre sur le diagnostic dans un dépôt déjà pollué.

Plutôt que de relire le .gitignore à l'oeil, demandez à Git lui-même quelle règle s'applique. L'option -v est indispensable : sans elle, la commande se contente d'afficher le chemin, alors qu'avec elle vous obtenez le fichier source, le numéro de ligne et le motif responsables. Git consulte plusieurs fichiers d'exclusion (.gitignore de chaque répertoire, .git/info/exclude, configuration globale), et c'est souvent celui auquel vous ne pensez pas qui agit.

Fenêtre de terminal
git check-ignore -v config/local.json

Sortie si le fichier est ignoré :

.gitignore:3:config/*.json config/local.json

Cela indique quelle règle (fichier, ligne) cause l'ignorance. Si aucune sortie, le fichier n'est pas ignoré.

Quand plusieurs règles s'appliquent, la dernière règle gagne. L'ordre des lignes est donc porteur de sens : une exception écrite avant la règle qu'elle doit contredire n'a aucun effet. Le tableau qui suit détaille les cinq motifs qui couvrent la quasi-totalité des cas, en particulier la différence entre logs/*.log et logs/**/*.log, source classique de fichiers oubliés dans les sous-dossiers.

# Ignorer tous les logs
*.log
# MAIS garder error.log
!error.log
RègleSignification
*.logIgnore tous les fichiers .log
!error.logNégation, force le suivi de error.log
logs/Ignore le dossier logs et tout son contenu
logs/*.logIgnore les .log dans logs/ (pas les sous-dossiers)
logs/**/*.logIgnore les .log dans logs/ et tous ses sous-dossiers

Deux situations produisent le même ressenti, « mon .gitignore est ignoré », alors que les causes n'ont rien à voir. La première tient au fait que .gitignore ne s'applique qu'aux fichiers non encore suivis : un fichier déjà dans l'index continue d'être versionné quoi que vous écriviez.

# Ne fonctionne pas : le fichier est déjà suivi par Git
secret.env
# Solution : git rm --cached secret.env, puis conserver cette règle

La seconde tient à une optimisation de Git : quand un répertoire entier est exclu, Git n'en parcourt pas le contenu, donc aucune exception à l'intérieur ne peut être évaluée. Il faut exclure le contenu et non le répertoire lui-même.

# Ne fonctionne pas : la négation est inatteignable
build/
!build/important.js
# Solution : exclure le contenu, pas le répertoire
build/*
!build/important.js

Avant de conclure qu'une règle ne marche pas, regardez ce que Git exclut réellement. --ignored ajoute à la sortie de git status une section listant les fichiers écartés, ce qui permet de repérer une règle trop large qui masque un fichier que vous vouliez versionner.

Fenêtre de terminal
git status --ignored

Un mot de passe, une clé API ou un token a été commité. Même si vous le supprimez dans un nouveau commit, il reste visible dans l'historique. Voici le workflow complet.

  1. Installez git-filter-repo

    git-filter-repo remplace l'ancien filter-branch (plus sûr, plus rapide). Préférez le paquet de votre distribution : sur les systèmes récents, pip install dans l'environnement Python global échoue avec externally-managed-environment.

    Fenêtre de terminal
    # Debian / Ubuntu
    sudo apt install git-filter-repo
    # macOS
    brew install git-filter-repo
    # Sinon, dans un environnement Python isolé
    pipx install git-filter-repo

    Vérification :

    Fenêtre de terminal
    git filter-repo --version
  2. Sauvegardez votre repo (par sécurité)

    Fenêtre de terminal
    cp -r mon-projet mon-projet-backup
  3. Supprimez le fichier de tout l'historique

    Fenêtre de terminal
    git filter-repo --path .env --invert-paths

    --invert-paths signifie : « supprime tout ce qui correspond au path donné ». Le fichier .env disparaît de tous les commits.

  4. Vérifiez que le fichier n'apparaît plus

    Fenêtre de terminal
    git log --all --full-history -- .env
    # Aucun résultat = le fichier est purgé
  5. Ajoutez le fichier au .gitignore

    Fenêtre de terminal
    echo ".env" >> .gitignore
    git add .gitignore
    git commit -m "chore: ajouter .env au gitignore"
  6. Force push vers le remote

    Fenêtre de terminal
    git push --force --all
    git push --force --tags
  7. Prévenez votre équipe

    Tous les collègues doivent re-cloner ou exécuter :

    Fenêtre de terminal
    git fetch --all
    git reset --hard origin/main

Supprimer un texte spécifique (pas un fichier entier)

Section intitulée « Supprimer un texte spécifique (pas un fichier entier) »

Si le secret est une ligne à l'intérieur d'un fichier (pas le fichier entier) :

Fenêtre de terminal
git filter-repo --replace-text <(echo 'MON_SECRET_123==>***REDACTED***')

Cela remplace toutes les occurrences de MON_SECRET_123 par ***REDACTED*** dans tous les fichiers de tout l'historique.

Un fichier binaire volumineux (dump SQL, vidéo, archive) a été commité et gonfle le repo :

Le poids d'un dépôt vient rarement de son état actuel : il vient des blobs conservés dans l'historique, y compris ceux de fichiers supprimés depuis longtemps. La commande ci-dessous parcourt tous les objets de toutes les références, ne garde que les blobs, et les trie par taille décroissante. La première colonne est la taille en octets, la seconde le chemin. Un binaire de plusieurs dizaines de mégaoctets en tête de liste explique à lui seul la lenteur des clones.

Fenêtre de terminal
git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '/^blob/ {print $3, $4}' \
| sort -rn \
| head -20

Sortie :

52428800 data/dump.sql
10485760 assets/video-demo.mp4

La commande est la même que pour un secret, seul le chemin change. Attention toutefois : la taille du dossier .git ne diminue pas immédiatement. Les anciens objets restent référencés par le reflog jusqu'à ce que le ramasse-miettes passe, ce que la section dépannage plus bas détaille.

Fenêtre de terminal
git filter-repo --path data/dump.sql --invert-paths

Pour éviter que cela ne se reproduise, utilisez Git LFS (Large File Storage) pour les fichiers volumineux :

Fenêtre de terminal
git lfs install
git lfs track "*.sql"
git lfs track "*.mp4"
git add .gitattributes
git commit -m "chore: configurer Git LFS pour SQL et vidéos"

Ces cinq entrées couvrent l'essentiel des blocages rencontrés après un nettoyage. La troisième mérite une explication : filter-repo a bien retiré les objets de l'historique, mais Git les conserve tant que le reflog y fait référence. Tant que vous n'avez pas expiré le reflog et lancé git gc, la taille du dossier .git ne bouge pas, ce qui donne l'impression que l'opération a échoué.

SymptômeCause probableSolution
.gitignore ne fonctionne pasFichier déjà suivi (dans l'index)git rm --cached fichier d'abord
filter-repo refuse de s'exécuterRepo pas « fresh clone »Ajoutez --force ou reclonez
Le repo est toujours gros après filter-repoObjets non collectésgit reflog expire --expire=now --all && git gc --prune=now --aggressive
Les collègues ont des erreurs après le force pushHistoriques divergentsIls doivent re-cloner ou git fetch --all && git reset --hard origin/main
La négation !fichier dans .gitignore ne marche pasLe dossier parent est ignoré avec /Ignorer le contenu dossier/* au lieu du dossier dossier/
  • git rm --cached arrête de suivre un fichier sans le supprimer du disque
  • .gitignore ne s'applique qu'aux fichiers non encore suivis, d'abord rm --cached, puis .gitignore
  • git check-ignore -v est l'outil de debug du .gitignore
  • Secret commité = secret compromis, changez-le immédiatement, avant le nettoyage Git
  • git filter-repo supprime un fichier de tout l'historique (remplace filter-branch)
  • Après un filter-repo, il faut force push et prévenir l'équipe
  • Git LFS prévient le problème des gros fichiers en les stockant séparément

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn