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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- 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é
git commit --amend : modifier le dernier commit
Section intitulée « git commit --amend : modifier le dernier commit »La forme la plus simple de réécriture. Modifie le message et/ou le contenu du dernier commit :
Modifier le message
Section intitulée « Modifier le message »Le cas le plus courant : vous venez de committer et vous voyez la faute de
frappe, ou vous avez oublié le préfixe Conventional Commits. Avec -m,
Git remplace le message sans ouvrir l'éditeur.
git commit --amend -m "fix: corriger le calcul de TVA"Vérifiez le résultat, le SHA affiché n'est plus le même qu'avant :
git log -1 --onelineAjouter des fichiers oubliés
Section intitulée « Ajouter des fichiers oubliés »--amend ne sert pas qu'au message : tout ce qui se trouve dans l'index
au moment de l'appel rejoint le commit précédent. C'est la façon propre de
rattraper un fichier oublié sans polluer l'historique d'un commit
« oups, fichier manquant ».
git add fichier-oublie.pygit commit --amend --no-edit--no-edit conserve le message original. Le commit amendé remplace
l'ancien (nouveau SHA).
Modifier l'auteur
Section intitulée « Modifier l'auteur »Corriger l'auteur devient nécessaire quand un commit part avec la mauvaise
adresse e-mail, typiquement une adresse personnelle sur un dépôt
professionnel. Attention à deux effets vérifiables avec git log, la
date d'auteur d'origine est conservée, et vous devenez le committer
du commit sans que cela apparaisse dans l'affichage par défaut.
git commit --amend --author="Alice <alice@example.com>"Contrôlez les deux identités d'un coup :
git log -1 --format='auteur=%an <%ae> committer=%cn <%ce>'git rebase -i : réécrire une série de commits
Section intitulée « git rebase -i : réécrire une série de commits »Le rebase interactif permet de réorganiser, fusionner, renommer, éditer ou supprimer une série de commits.
Lancer un rebase interactif
Section intitulée « Lancer un rebase interactif »HEAD~5 désigne le point de départ du rebase, pas le nombre de commits à
modifier : Git rejoue tous les commits situés après cette référence, soit
les 5 derniers. Si votre branche compte moins de commits que le chiffre indiqué,
la commande échoue avec fatal: invalid upstream. Sur une branche partant de
main, git rebase -i main évite le comptage.
# Réécrire les 5 derniers commitsgit rebase -i HEAD~5Git ouvre l'éditeur avec une liste de commits (du plus ancien au plus récent) :
pick a1b2c3d feat: ajouter le module pricingpick e4f5a6b fix: corriger la remisepick d7c8b9a wip: debugpick f0e1d2c feat: ajouter les testspick b3a4c5d fix: typo dans le READMELes commandes disponibles
Section intitulée « Les commandes disponibles »Chaque ligne de la liste porte une commande que vous remplacez à la main. Deux
d'entre elles se ressemblent et sont la source d'erreur numéro un :
squash conserve les deux messages et vous fait ouvrir l'éditeur pour les
combiner, alors que fixup jette le message du commit fusionné sans rien
vous demander. Retenez aussi que la fusion se fait toujours avec la ligne
au-dessus, jamais celle du dessous.
| Commande | Effet |
|---|---|
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.
Exemple : nettoyer avant un push
Section intitulée « Exemple : nettoyer avant un push »Voici le scénario type d'une branche de travail : cinq commits, dont un correctif immédiat, un commit de debug et une correction de faute de frappe. Comparez les deux listes ligne à ligne : seul le mot de la première colonne change, vous ne touchez ni à l'ordre des lignes ni aux SHA affichés par Git dans l'éditeur.
Historique initial :
pick a1b2c3d feat: ajouter le module pricingpick e4f5a6b fix: corriger la remisepick d7c8b9a wip: debugpick f0e1d2c feat: ajouter les testspick b3a4c5d fix: typo dans le READMERéécriture souhaitée :
pick a1b2c3d feat: ajouter le module pricingfixup e4f5a6b fix: corriger la remisedrop d7c8b9a wip: debugpick f0e1d2c feat: ajouter les testsfixup b3a4c5d fix: typo dans le READMERésultat : 2 commits propres au lieu de 5.
Workflow rebase -i pas à pas
Section intitulée « Workflow rebase -i pas à pas »La première étape n'est pas décorative : elle vérifie que vous réécrivez bien
des commits locaux. Si git log origin/main..HEAD ne renvoie rien, c'est
que tout est déjà poussé et que la règle d'or s'applique. Gardez également en
tête que git rebase --abort ramène la branche à son état de départ tant que
le rebase n'est pas terminé, ce qui rend l'opération sans risque à l'essai.
-
Vérifiez que vos commits ne sont pas encore poussés :
Fenêtre de terminal git log origin/main..HEAD --oneline -
Lancez le rebase interactif :
Fenêtre de terminal git rebase -i HEAD~5 -
Modifiez les commandes (
pick→squash,fixup,drop...) dans l'éditeur, sauvegardez, fermez. -
Si
squash: Git ouvre l'éditeur pour combiner les messages. Rédigez un message propre. -
Si conflit : résolvez puis continuez :
Fenêtre de terminal git add fichier-resolu.pygit rebase --continue -
Pour annuler à tout moment :
Fenêtre de terminal git rebase --abort
L'option edit en détail
Section intitulée « L'option edit en détail »edit stoppe le rebase pour vous laisser modifier un commit
intermédiaire :
# Le rebase s'arrête au commit marqué "edit"# Modifiez les fichiers...git add .git commit --amendgit rebase --continueVous pouvez même découper un commit en deux :
# Au point d'arrêt "edit"git reset HEAD~1 # Annuler le commit (garder les fichiers)git add partie-1.pygit commit -m "feat: partie 1"git add partie-2.pygit commit -m "feat: partie 2"git rebase --continue--autosquash : automatiser le fixup
Section intitulée « --autosquash : automatiser le fixup »Git peut placer automatiquement les commits de fixup au bon endroit :
# Créer un commit de fixup lié au commit a1b2c3dgit commit --fixup=a1b2c3d
# Lancer le rebase avec autosquashgit rebase -i --autosquash HEAD~10Le commit fixup! feat: ajouter le module pricing est automatiquement
placé juste après et marqué fixup.
Depuis Git 2.32, --fixup accepte deux variantes utiles : --fixup=amend:<sha>
pour modifier à la fois le contenu et le message du commit visé, et
--fixup=reword:<sha> pour ne toucher que le message. Vérifiez la syntaxe
disponible sur votre poste avec git commit -h.
Pour activer --autosquash par défaut :
git config --global rebase.autoSquash truegit filter-repo : nettoyage profond
Section intitulée « git filter-repo : nettoyage profond »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é).
Checklist avant d'utiliser filter-repo
Section intitulée « Checklist avant d'utiliser filter-repo »Cette opération n'est pas réversible côté serveur une fois le force push effectué. Le point le plus souvent oublié est le premier de la liste : tant que le secret n'est pas révoqué, réécrire l'historique ne protège rien, puisque la valeur circule déjà dans les clones, les caches de la forge et les journaux de CI. Traitez la révocation d'abord, le nettoyage ensuite.
- 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 cloné à neuf (« fresh clone ») (ne pas l'exécuter sur un dépôt avec des modifications locales) ?
Installation
Section intitulée « Installation »git-filter-repo est un unique script Python, ce qui explique la pluralité des
modes d'installation. Le paquet système reste préférable quand il existe : il
évite d'installer dans l'environnement Python global, ce que pip refuse
d'ailleurs par défaut sur les distributions récentes (erreur
externally-managed-environment).
# Debian/Ubuntusudo apt install git-filter-repo
# macOSbrew install git-filter-repo
# Sinon, dans un environnement Python isolépipx install git-filter-repoContrôlez que Git voit bien la sous-commande :
git filter-repo --versionSupprimer un fichier de tout l'historique
Section intitulée « Supprimer un fichier de tout l'historique »--path sélectionne ce sur quoi filter-repo travaille, et --invert-paths
inverse la sélection : sans cette seconde option, la commande conserverait
uniquement le fichier indiqué et supprimerait tout le reste. C'est l'erreur la
plus coûteuse de cette page, et elle passe inaperçue jusqu'au premier
git log.
git filter-repo --invert-paths --path secrets/api-key.txtCette commande réécrit chaque commit pour supprimer le fichier. Tous les SHA changent.
Remplacer du texte dans tout l'historique
Section intitulée « Remplacer du texte dans tout l'historique »Supprimer le fichier ne suffit pas quand le secret a été copié dans plusieurs
fichiers, ou collé dans un message de commit. --replace-text parcourt le
contenu de tous les blobs et substitue les motifs que vous décrivez, ligne par
ligne, dans un fichier de règles. Chaque ligne accepte un préfixe literal:
(valeur par défaut), glob: ou regex:, et la partie après ==> fixe le texte
de remplacement. Sans ==>, filter-repo écrit ***REMOVED***.
Contenu de replacements.txt :
regex:AKIA[A-Z0-9]{16}==>REDACTED_AWS_KEYliteral:mon-mot-de-passe==>REDACTEDgit filter-repo --replace-text replacements.txtBFG Repo Cleaner : alternative rapide
Section intitulée « BFG Repo Cleaner : alternative rapide »BFG couvre un périmètre plus étroit que filter-repo, mais avec une ligne
de commande plus directe pour les deux besoins les plus fréquents : supprimer
des fichiers par nom et purger les gros blobs. Deux différences de comportement
comptent. BFG ne modifie pas le contenu du dernier commit de la branche
HEAD, qu'il considère comme l'état propre que vous venez de corriger à la main :
si le secret y est encore, il survivra au nettoyage, sauf à passer
--no-blob-protection. Il exige par ailleurs un Java Runtime en version 11
ou supérieure, ce qui le rend moins commode sur un runner de CI minimal. En
contrepartie, il traite les très gros dépôts nettement plus vite.
# Installer (nécessite Java)# Télécharger depuis https://rtyley.github.io/bfg-repo-cleaner/
# Supprimer un fichier de tout l'historiquebfg --delete-files api-key.txt
# Supprimer les fichiers > 100 Mobfg --strip-blobs-bigger-than 100M
# Remplacer du textebfg --replace-text passwords.txt
# Nettoyer après BFGgit reflog expire --expire=now --allgit gc --prune=now --aggressiveComparatif des outils
Section intitulée « Comparatif des outils »Le bon réflexe consiste à choisir l'outil dont la portée est la plus étroite
pour le besoin. Passer par filter-repo là où un --amend suffirait force
toute l'équipe à recloner pour une faute de frappe. La colonne « Portée » est
donc celle à lire en premier ; la colonne « Complexité » ne sert qu'à départager
filter-repo et BFG une fois que vous savez qu'il faut réécrire tout
l'historique.
| Outil | Portée | Cas d'usage | Complexité |
|---|---|---|---|
--amend | Dernier commit | Message ou contenu oublié | Faible |
rebase -i | N derniers commits | Squash, reword, réordonner | Moyenne |
filter-repo | Tout l'historique | Supprimer secrets/gros fichiers | Élevée |
| BFG | Tout l'historique | Supprimer fichiers/texte | Moyenne |
Dépannage
Section intitulée « Dépannage »Les incidents de réécriture se ressemblent : un conflit qui revient, un
dépôt qui refuse le push, ou un filter-repo qui s'arrête avant de commencer.
Le point commun est qu'aucun n'est destructif tant que vous n'avez pas forcé le
push. Retenez surtout la dernière ligne : --force-with-lease vérifie que la
référence distante est bien celle que vous aviez récupérée, et refuse le push si
un collègue a poussé entre-temps, là où --force écraserait son travail sans
prévenir.
| Symptôme | Cause probable | Solution |
|---|---|---|
CONFLICT pendant rebase -i | Commits interdépendants réordonnés | Résolvez chaque conflit, git rebase --continue |
| Rebase boucle sur le même conflit | Commit conflictuel non résolu | git rebase --abort et repensez l'ordre |
Cannot rebase: You have unstaged changes | Working dir sale | git stash avant le rebase |
| filter-repo refuse de s'exécuter | N'est pas un clone « fresh » | Ajoutez --force ou clonez le repo d'abord |
push rejected après réécriture | SHA ont changé | git push --force-with-lease (jamais --force seul) |
À retenir
Section intitulée « À retenir »--amend: rapide pour le dernier commitrebase -i: squash, fixup, reword, edit, drop, réordonner--fixup+--autosquashautomatisent le nettoyagefilter-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)