roselletabuena/réécriture-sûre-de-l-historique-git

Réécrivez l'historique Git sans perdre de travail ni écraser celui des autres – amend, reset, squash, réorganisation, suppression ou modification de commits, changement de message, retrait d'un commit d'une branche, ou force-push en toute sécurité.

Qu'est-ce que réécriture-sûre-de-l-historique-git ?

Cette compétence permet de réécrire en toute sécurité l'historique Git en automatisant les décisions critiques : elle détermine d'abord si la réécriture est appropriée (un commit déjà partagé doit être révoqué, pas effacé), puis effectue les opérations demandées (amend, reset, squash, réorganisation, suppression ou édition de commits) sans ouvrir d'éditeur interactif qui bloquerait dans un terminal non interactif. Elle sauvegarde systématiquement un point de récupération via une branche nommée avant toute modification, vérifie que le résultat correspond bien à l'intention (via diff ou range-diff), et publie avec `--force-with-lease` et `--force-if-includes` plutôt qu'un `--force` nu. Conçue pour Claude Code, Cursor et Codex, elle supporte aussi `git commit --fixup` avec autosquash, la réécriture de messages de merge, et les branchements empilés. Ses garde-fous incluent le refus de réécrire des commits d'autrui, la détection des branches partagées (main, develop, release/*), et l'obligation d'une confirmation explicite avant de forcer le push.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/roselletabuena/skills/tree/HEAD/plugins/git-workflows/skills/git-safe-history-rewrite

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Réécrire l'historique en toute sécurité

Chaque réécriture – commit --amend, reset, rebase, filter-repo – produit des commits nouveaux et déplace le pointeur de branche des anciens. Cela est inoffensif tant que personne d'autre ne possède les anciens commits, et destructeur dès que c'est le cas : leur clone conserve encore l'ancien historique, et la prochaine fois qu'ils poussent ou tirent, il revient sous forme de doublons ou de conflits. La première question n'est donc jamais « comment », mais « qui d'autre possède ces commits ? »

1. Décider : réécrire ou révoquer

git fetch --prune
git status --short --branch                  # en avance/en retard, et état modifié
git branch -r --contains <commit>            # quelles branches distantes le contiennent déjà
git log --oneline @{u}..HEAD                 # commits qui n'existent que localement
Où vit le commitFaites ceci
Local uniquement, jamais pousséRéécrivez librement.
Poussé sur une branche que seul cet utilisateur utilise (sa branche PR)Réécrivez, puis poussez avec un lease (étape 5).
Sur une branche partagée ou protégée : main, master, develop, release/*, ou tout ce qui a d'autres commits au-dessusNe réécrivez pas. Utilisez git revert.
Déjà publié ou déployégit revert. Une réécriture ne peut pas annuler le déploiement.

Quand l'utilisateur dit « supprime ce commit de main », il veut généralement que son effet disparaisse, pas son SHA. git revert <sha> fait cela, maintient l'historique honnête, et ne nécessite qu'un push normal. Pour un commit de merge, git revert -m 1 <merge-sha>. Expliquez pourquoi vous avez choisi revert, et si l'utilisateur insiste pour l'effacer d'une branche partagée, nommez qui cela affectera et obtenez un accord explicite d'abord.

Un revert entre en conflit lorsque des commits ultérieurs ont modifié les mêmes lignes – c'est exactement le cas où des commits ont été construits au-dessus du mauvais. Résolvez comme tout conflit (voir git-conflict-resolution) : supprimez la mauvaise modification, conservez le travail ultérieur, puis git revert --continue.

Un piège du revert : après avoir révoqué un merge, fusionner à nouveau la même branche ne ramène rien, car git pense que ces commits ont déjà atterri. Pour rétablir le travail, révoquez d'abord le revert.

2. Prendre un point de récupération avant de toucher à quoi que ce soit

git rev-parse HEAD                           # notez-le
git branch backup/<branche>-pre-rewrite       # une vraie référence, pas seulement le reflog

Le reflog enregistre aussi où se trouvait la branche, mais il est local à ce clone, expire (90 jours pour les entrées atteignables, 30 pour les inatteignables par défaut), et git gc après reflog expire y met fin. Une branche de sauvegarde nommée ne coûte rien. Laissez-la en place jusqu'à ce que l'utilisateur ait confirmé le résultat.

3. Choisir la commande la moins destructrice

ObjectifCommandeRemarques
Corriger le message du dernier commit ou ajouter un fichier oubliégit commit --amend-m "..." ou --no-edit évite d'ouvrir un éditeur.
Annuler le commit mais garder les modifications indexéesgit reset --soft HEAD~1
Annuler le commit et désindexer, garder les fichiersgit reset --mixed HEAD~1 (par défaut)
Reculer une branche, refuser de perdre les modifications localesgit reset --keep <commit>Abandonne si cela écraserait des modifications non commitées. Préférez ceci à --hard.
Jeter aussi le travail non commitégit reset --hardLe travail non commité n'est pas dans le reflog et ne peut pas être récupéré. Après avoir confirmé que rien n'est souhaité ; git stash -u d'abord si incertain.
Annuler les modifications d'un seul fichiergit restore <fichier>
Squasher ou fusionner des correctifs dans des commits antérieursgit commit --fixup=<sha> puis autosquashVoir ci-dessous.
Supprimer ou réorganiser des commitsrebase -i avec un todo fourniVoir ci-dessous.

Un hook pré-commit échoué signifie qu'aucun commit n'a été fait. Exécuter git commit --amend pour « réessayer » modifie alors le commit précédent, fusionnant les nouvelles modifications dans le mauvais. Après un échec de hook, corrigez le problème, ré-indexez, et faites un nouveau commit normal.

Rebase interactif sans éditeur interactif

git rebase -i ouvre un éditeur, qui bloque dans un shell non interactif. Ne vous y fiez pas. Deux méthodes fonctionnent :

# Fusionner les commits de correction dans leurs cibles - accepter le todo que git génère :
git commit --fixup=<sha-cible>
GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash <base>

# Supprimer ou réorganiser : écrire le todo vous-même.
GIT_SEQUENCE_EDITOR="sed -i '/^pick <short-sha> /d'" git rebase -i <base>

<base> est le commit avant le premier que vous voulez modifier. Si un rebase s'arrête sur un conflit, résolvez-le, git add, puis GIT_EDITOR=true git rebase --continue. Si ça tourne mal, git rebase --abort revient au point de départ.

4. Vérifier que la réécriture a bien fait ce que vous vouliez

Une édition de message, une réorganisation ou un squash devraient laisser l'arbre final identique :

git diff backup/<branche>-pre-rewrite HEAD --stat      # vide = même contenu
git range-diff backup/<branche>-pre-rewrite...HEAD     # comparaison commit par commit

Si vous vouliez supprimer du contenu, le diff devrait montrer exactement ce contenu et rien d'autre. Tout ce qui est inattendu signifie restaurer depuis la sauvegarde et recommencer.

5. Publier – seulement avec l'accord de l'utilisateur

Pousser une réécriture est l'étape tournée vers l'extérieur, difficile à annuler. Confirmez d'abord, sauf si l'utilisateur a déjà demandé le push.

git push --force-with-lease=<branche>:<sha-distant-attendu> --force-if-includes origin <branche>
  • Jamais de --force nu. Il écrase tout ce qui est sur le distant, y compris un push d'un collègue que vous n'avez pas vu.
  • Nommez le SHA attendu. Un simple --force-with-lease compare avec votre ref de suivi distant, et un git fetch en arrière-plan de l'IDE met tranquillement à jour cette ref, donc le lease ne protège alors rien. Utilisez le SHA que vous avez vu à l'étape 1 (git rev-parse origin/<branche>).
  • --force-if-includes (git 2.30+) refuse en plus si le distant s'est déplacé vers quelque chose que votre branche locale n'a jamais intégré.
  • Ne forcez jamais le push sur main/master/develop/release/*. Si une règle d'hébergement le bloque, cette règle est correcte ; ne la contournez pas.

Après avoir poussé, dites à l'utilisateur que quiconque a l'ancienne branche extraite doit réinitialiser vers la nouvelle plutôt que de tirer, et que les commentaires de révision d'une PR ouverte peuvent perdre leurs ancres.

Garde-fous

  • Les commits signés perdent leurs signatures lors de la réécriture ; re-signez avec -S ou informez l'utilisateur.
  • Ne réécrivez pas les commits d'un autre auteur sans demander, même sur une branche de fonctionnalité.
  • Les branches empilées au-dessus de celle réécrite sont maintenant obsolètes. Utilisez git-stacked-pr-rebase pour les ré-empiler plutôt que de rebaser chacune à la main.
  • Un secret commité n'est pas corrigé par une réécriture. Ruptez d'abord l'identifiant ; supposez-le compromis.
  • Rapportez clairement : quel était l'ancien sommet, quel est le nouveau, où se trouve la ref de sauvegarde, et si quelque chose n'est pas poussé. Si revert était la bonne décision, dites que c'est ce que vous avez fait.

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Skills associés