Aller au contenu
Développement medium
🔐 Alerte sécurité — Incident supply chain Trivy : lire mon analyse de l'attaque

Réécrire l'historique Git

10 min de lecture

git commit --amend modifie le dernier commit, git rebase -i réorganise les N derniers commits, et git filter-repo nettoie tout l’historique. Réécrire l’historique produit des commits propres et logiques avant de les partager. Ce guide couvre les trois niveaux de réécriture.

Prérequis : Rebase fondamental et Staging interactif.

  • Modifier le dernier commit avec --amend (message ou contenu)
  • Réécrire plusieurs commits avec rebase -i : squash, fixup, reword, edit, drop
  • Supprimer un fichier de tout l’historique avec git filter-repo
  • Appliquer la règle d’or : ne jamais réécrire un historique déjà partagé

La forme la plus simple de réécriture. Modifie le message et/ou le contenu du dernier commit :

Fenêtre de terminal
git commit --amend -m "fix: corriger le calcul de TVA"
Fenêtre de terminal
git add fichier-oublie.py
git commit --amend --no-edit

--no-edit conserve le message original. Le commit ammendé remplace l’ancien (nouveau SHA).

Fenêtre de terminal
git commit --amend --author="Alice <alice@example.com>"

Le rebase interactif permet de réorganiser, fusionner, renommer, éditer ou supprimer une série de commits.

Fenêtre de terminal
# Réécrire les 5 derniers commits
git rebase -i HEAD~5

Git ouvre l’éditeur avec une liste de commits (du plus ancien au plus récent) :

pick a1b2c3d feat: ajouter le module pricing
pick e4f5a6b fix: corriger la remise
pick d7c8b9a wip: debug
pick f0e1d2c feat: ajouter les tests
pick b3a4c5d fix: typo dans le README
CommandeEffet
pick (p)Garder le commit tel quel
reword (r)Garder le commit, modifier le message
edit (e)S’arrêter pour modifier le commit (contenu + message)
squash (s)Fusionner avec le commit précédent (combiner les messages)
fixup (f)Fusionner avec le précédent (supprimer le message du fixup)
drop (d)Supprimer le commit

Vous pouvez aussi réordonner les lignes pour changer l’ordre des commits.

Historique initial :

pick a1b2c3d feat: ajouter le module pricing
pick e4f5a6b fix: corriger la remise
pick d7c8b9a wip: debug
pick f0e1d2c feat: ajouter les tests
pick b3a4c5d fix: typo dans le README

Réécriture souhaitée :

pick a1b2c3d feat: ajouter le module pricing
fixup e4f5a6b fix: corriger la remise
drop d7c8b9a wip: debug
pick f0e1d2c feat: ajouter les tests
fixup b3a4c5d fix: typo dans le README

Résultat : 2 commits propres au lieu de 5.

  1. Vérifiez que vos commits ne sont pas encore poussés :

    Fenêtre de terminal
    git log origin/main..HEAD --oneline
  2. Lancez le rebase interactif :

    Fenêtre de terminal
    git rebase -i HEAD~5
  3. Modifiez les commandes (picksquash, fixup, drop…) dans l’éditeur, sauvegardez, fermez.

  4. Si squash : Git ouvre l’éditeur pour combiner les messages. Rédigez un message propre.

  5. Si conflit : résolvez puis continuez :

    Fenêtre de terminal
    git add fichier-resolu.py
    git rebase --continue
  6. Pour annuler à tout moment :

    Fenêtre de terminal
    git rebase --abort

edit stoppe le rebase pour vous laisser modifier un commit intermédiaire :

Fenêtre de terminal
# Le rebase s'arrête au commit marqué "edit"
# Modifiez les fichiers...
git add .
git commit --amend
git rebase --continue

Vous pouvez même découper un commit en deux :

Fenêtre de terminal
# Au point d'arrêt "edit"
git reset HEAD~1 # Annuler le commit (garder les fichiers)
git add partie-1.py
git commit -m "feat: partie 1"
git add partie-2.py
git commit -m "feat: partie 2"
git rebase --continue

Git peut placer automatiquement les commits de fixup au bon endroit :

Fenêtre de terminal
# Créer un commit de fixup lié au commit a1b2c3d
git commit --fixup=a1b2c3d
# Lancer le rebase avec autosquash
git rebase -i --autosquash HEAD~10

Le commit fixup! feat: ajouter le module pricing est automatiquement placé juste après et marqué fixup.

Pour activer --autosquash par défaut :

Fenêtre de terminal
git config --global rebase.autoSquash true

Pour supprimer un fichier de tout l’historique (par exemple un secret commité par erreur), git filter-repo remplace l’ancien filter-branch (déprécié).

  • Le secret exposé est-il déjà changé/révoqué ? (sinon, faites-le d’abord)
  • Tout le monde est-il prévenu et ses branches locales sauvegardées ?
  • Avez-vous une sauvegarde du dépôt original (git bundle create backup.bundle --all) ?
  • Le dépôt est-il clôné en « fresh clone » (ne pas l’exécuter sur un dépôt avec des modifications locales) ?
Fenêtre de terminal
# Via pip
pip install git-filter-repo
# Via le gestionnaire de paquets
# Debian/Ubuntu
apt install git-filter-repo
# macOS
brew install git-filter-repo
Fenêtre de terminal
git filter-repo --invert-paths --path secrets/api-key.txt

Cette commande réécrit chaque commit pour supprimer le fichier. Tous les SHA changent.

Créez un fichier de remplacement :

replacements.txt
regex:AKIA[A-Z0-9]{16}==>REDACTED_AWS_KEY
literal:mon-mot-de-passe==>REDACTED
Fenêtre de terminal
git filter-repo --replace-text replacements.txt

BFG est plus simple que filter-repo pour des cas courants :

Fenêtre de terminal
# Installer (nécessite Java)
# Télécharger depuis https://rtyley.github.io/bfg-repo-cleaner/
# Supprimer un fichier de tout l'historique
bfg --delete-files api-key.txt
# Supprimer les fichiers > 100 Mo
bfg --strip-blobs-bigger-than 100M
# Remplacer du texte
bfg --replace-text passwords.txt
# Nettoyer après BFG
git reflog expire --expire=now --all
git gc --prune=now --aggressive
OutilPortéeCas d’usageComplexité
--amendDernier commitMessage ou contenu oubliéFaible
rebase -iN derniers commitsSquash, reword, réordonnerMoyenne
filter-repoTout l’historiqueSupprimer secrets/gros fichiersÉlevée
BFGTout l’historiqueSupprimer fichiers/texteMoyenne
SymptômeCause probableSolution
CONFLICT pendant rebase -iCommits interdépendants réordonnésRésolvez chaque conflit, git rebase --continue
Rebase boucle sur le même conflitCommit conflictuel non résolugit rebase --abort et repensez l’ordre
Cannot rebase: You have unstaged changesWorking dir salegit stash avant le rebase
filter-repo refuse de s’exécuterN’est pas un clone « fresh »Ajoutez --force ou clonez le repo d’abord
push rejected après réécritureSHA ont changégit push --force-with-lease (jamais --force seul)
  • --amend : rapide pour le dernier commit
  • rebase -i : squash, fixup, reword, edit, drop, réordonner
  • --fixup + --autosquash automatisent le nettoyage
  • filter-repo : pour les nettoyages profonds (secrets, gros fichiers)
  • Règle d’or : ne réécrivez jamais des commits déjà partagés
  • Après réécriture : --force-with-lease (pas --force)

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn