History : réécrire facilement des commits

Par Maxime Bréhin • Publié le 4 septembre 2026

Avec la sortie de Git 2.54 et 2.55, respectivement en avril et juin 2026, Git s’est vu doté d’une nouvelle commande git history dont la vocation est de faciliter certaines opérations de réécriture de l’historique des commits. Ces opérations étaient déjà possibles avec la commande git rebase mais la complexité de cette commande tendait à décourager les utilsateurs·rices. Vous l’aurez compris, la vocation de git history est d’éliminer ces frictions et encourager la clareté de l’historique projet.

Quelles opérations peut-on réaliser ?

À l’heure ou j’écris ces lignes, la commande est considérée expérimentale, même si elle remplie très bien ses fonctions. Comme je l’ai écrit en introduction, l’objectif est la simplification d’une partie des opérations déjà proposées par git rebase et qui correspondent le plus à un usage “courant”, à savoir :

  • la réécriture d’un message de commit ;
  • l’ajout de modifications oubliées dans un commit ;
  • le découpage d’un commit.

Ces opérations effectuent par défaut le déplacement des étiquettes de branches présentes dans l’historique réécrit (ce que fait l’option --update-refs de rebase).

Il existe toutefois deux limitations importantes : git history ne peut pas agir sur une portion d’historique contenant une fusion ni traiter des conflits.

Voyons à présent les différents cas d’usage avec des exemples.

Réécriture d’un message de commit

Prenons l’historique suivant et les deux derniers commits sur la branche feat/home :

%%{init: { 'theme': 'default' , 'themeVariables': { 'commitLabelFontSize': '16px' } }%%
gitGraph BT:
  commit id: '0e57f25'
  commit id: '0cc28c6'
  branch "feat/home"
  commit id: '897be87'
  commit id: '47f8640'

Dans le terminal, un log graphique nous donnerait quelque chose comme ceci :

* 47f8640 - (HEAD -> feat/home) style(home): basic layout
* 897be87 - feat(home): create pge skeleton
* 0cc28c6 - (main) chore: dev tools
* 0e57f25 - chore: application setup

On s’aperçoit après avoir réalisé le dernier commit, que le message du commit 897be87 est mal renseigné : il manque le “a” de “page”.

Étant donné que nous sommes déjà sur la branche feat/home (indication de la position de HEAD), on peut simplement lancer la commande suivante en lui passant la référence du commit dont on souhaite modifier le message :

git history reword 897be87

Notre éditeur s’ouvre alors et nous permet de changer le message du commit. Il suffit de l’éditer, de sauvegarder, de fermer la fenêtre, et le tour est joué !

Tant qu’à faire, on peut vérifier si la modification a bien été prise en compte en affichant à nouveau l’historique :

* 3b89ce5 - (HEAD -> feat/home) style(home): basic layout
* 687f729 - feat(home): create page skeleton
* 0cc28c6 - (main) chore: dev tools
* 0e57f25 - chore: application setup

Notez que les identifiants des commits à partir de celui modifié ont été changés. C’est un comportement normal, il suffit de comprendre comment sont construits les commits et leurs identifiants.

Ajouter des modifications à un commit

Partons d’une version plus avancée de cet historique :

* 6d3e9eb - (HEAD -> feat/home) feat(home): add hero section
* 3b89ce5 - style(home): basic layout
* 687f729 - feat(home): create page skeleton
* 0cc28c6 - (main) chore: dev tools
* 0e57f25 - chore: application setup

Imaginons que nous ayons oublié d’ajouter un fichier style.css et sa référence dans le fichier home.html dans le commit “create page skeleton” (identifiant 687f729). Il nous suffit alors d’ajouter ces modifications au stage avec un git add … par exemple, puis de lancer la commande :

git history fixup 687f729

Celle-ci va alors extraire les modifications du commit désigné, lui ajouter les modifications de notre stage et créer un commit de remplacement. Enfin, les commits qui suivaient dans l’historique vont être répliqués, mais là aussi avec des nouveaux identifiants, leur commit parent ayant changé.

On vérifie le résultat en regardant le log :

* 6628860 - (HEAD -> feat/home) feat(home): add hero section
* c018ccf - style(home): basic layout
* 7f97cd7 - feat(home): create page skeleton
* 0cc28c6 - (main) chore: dev tools
* 0e57f25 - chore: application setup

On peut aussi vérifier l’état de nos fichiers pour être certain d’avoir ajouté les bonnes choses au commit :

$ git status

Sur la branche feat/home
rien à valider, la copie de travail est propre

Parfois, pris d’un doute, je vérifie aussi le contenu du commit, soit en listant simplement les fichiers avec un git show --name-only 7f97cd7ou encore en regardant les modifications qu’il contient git show 7f97cd7. À vous de voir quelles vérifications vous avez besoin d’effectuer au cas par cas.

Découper un commit en deux

C’est probablement le cas d’usage le moins fréquent des trois, mais il reste néanmoins intéressant. Admettons que nous ayons l’historique suivant

* 358fbad - (HEAD -> feat/home) feat(home): add hero block
* 598548d - feat(home): add header and footer blocks
* c76c722 - feat(home): setup basic layout
* 7f97cd7 - feat(home): create page skeleton

Notre gestion de projet se veut précise dans le détail des tâches et nous devons nous conformer à une règle précise : associer les commits aux tâches qui les concernent. Idéalement on visera un commit par micro-tâche.

Dans cet historique, on s’aperçoit que cette règle n’a pas été strictement suivie et que le commit 598548d contient le travail de 2 tâches, à savoir “ajout du block d’entête” et “ajout du block de pied de page”. Pour garantir le bon suivi projet, nous allons donc découper ce commit pour retranscrire ces 2 tâches :

git history split 598548d

Une fois la commande lancée, Git nous lance un assistant de découpe. Celui-ci nous permet aussi bien de choisir des fichiers complets que des blocks de modifications distincts. On va pouvoir commencer avec le commit pour l’ajout d’entête qui représente :

  • l’ajout d’un fichier partials/header.njk ;
  • la modification dans le fichier layout.njk pour référencer ce premier fichier.

J’insiste ici sur le fait que cette commande git history split découpe un commit en 2, pas plus. Si vous avez besoin d’une découpe en plus de 2 commits, vous avez le choix de lancer plusieurs fois à la suite cette commande ou de passer par la commande rebase.

L’assistant va nous présenter des blocks de modifications en nous demandant si oui (y) ou non (n) nous souhaitons les ajouter au premier commit.

─────────────────────
modified: layout.njk
─────────────────────
@ layout.njk:11 @
    <!--[if lt IE 7]>
      <script>window.location.href = 'http://browsehappy.com/'</script>
    <![endif]-->

    \{\% include "partials/header.njk" \%}

    <main id="main" class="info"  tabindex="-1">
      <div class="main-wrapper">

(1/2) Indexer cette section [y,n,q,a,d,?] ? y


@ layout.njk:26 @
        </div>
      </div>
    </main>

    \{\% include "partials/footer.njk" \%}

  </body>
</html>

(2/2) Indexer cette section [y,n,q,a,d,?] ? n



───────────────────────────
added: partials/footer.njk
───────────────────────────
@ partials/footer.njk:1 @
<footer></footer>
(1/1) Indexer l’ajout [y,n,q,a,d,?] ? n



───────────────────────────
added: partials/header.njk
───────────────────────────
@ partials/header.njk:1 @
<header id="header"></header>
(1/1) Indexer l'ajout [y,n,q,a,d,?] ? y

Une fois notre sélection effectuée, Git ouvre notre éditeur et nous propose de changer le message de commit initial. Ce message décrira donc les modifications sélectionnées pour notre premier commit :

feat(home): add header block

# Veuillez saisir le message de validation pour les modifications split-out. Les lignes
# commençant par '#' seront ignorées, et un message vide abandonne la validation.
# Modifications qui seront validées :
# modifié : layout.njk
# nouveau fichier : partials/header.njk
#

Une fois le message renseigné, enregistré, et la fenêtre fermée, il enchaînera automatiquement avec l’ouverture d’une autre fenêtre pour saisir le message visant le reste des modifications, donc le second commit :

feat(home): add footer block

# Veuillez saisir le message de validation pour les modifications split-out. Les lignes
# commençant par '#' seront ignorées, et un message vide abandonne la validation.
# Modifications qui seront validées :
# modifié : layout.njk
# nouveau fichier : partials/footer.njk
#

Cette dernière étape conclue notre opération de découpage. On peut vérifier le log et constater la présence de nos 2 commits :

* 751a0ce - (HEAD -> feat/home) feat(home): add hero block
* 6801137 - feat(home): add footer block
* 4586199 - feat(home): add header block
* c76c722 - feat(home): setup basic layout
* 7f97cd7 - feat(home): create page skeleton

Autres options de l’assistant lors de la découpe

Peut-être avez-vous remarqué que l’assistant nous propose plus d’options que le y (oui) et le n (non) ? En saisissant ?, on peut obtenir la signification de ces options :

y - indexer cette section
n - ne pas indexer cette section
q - quitter ; ne pas indexer cette section ni les autres restantes
a - indexer cette section et toutes les suivantes de ce fichier
d - ne pas indexer cette section ni les suivantes de ce fichier
j - aller à section non-décidée suivante et reboucler au début si en bas
J - aller à section suivante et reboucler au début si en bas
k - aller à section non-décidée précédente et reboucler à la fin si en haut
K - aller à section précédente et reboucler à la fin si en haut
g - sélectionner une section et s'y rendre
/ - rechercher une section correspondant à une regex donnée
p - afficher la section actuelle
P - afficher la section actuelle avec un paginateur
? - afficher l'aide

Ces options permettent d’avancer quand on est pas décidé (j / J / k / K), d’accélerer les sélections lorsqu’on sait qu’on prend ou non tout le bloc restant dans un fichier (a / d), de quitter la procédure (q)… En somme, elles peuvent nous faire gagner du temps dans certaines situations.

Le mode “essai” / dry-run

La commande history offre une option --dry-run, sorte de simulation sans application effective. À première vue, cela semble intéressant, mais en y regardant de plus près, c’est assez décevant car elle ne fournit qu’une indication technique :

$ git history fixup --dry-run 687f729

update refs/heads/feat/home a087ed4ce2df56734626f97bf16ab6555e18aa0f 6d3e9eb3eb4b5eed8b0bb717a47c7c31b9a87157

En conclusion

Je trouve cette nouvelle commande très intéressante pour les personnes novices. Elle enlève clairement la difficulté d’apprentissage de la fonctionnalité de rebasing pour ces opérations.

D’un point de vue plus personnel, j’y vois un peu trop de verbosité et j’atteins vite les limites. Aussi, je continuerai d’utiliser mes alias basés sur la commande rebase pour au moins 2 de ces opérations :

Pourquoi ? Parce que je sais que je peux les utiliser en toute circonstance, sans limite (pas de blocage si j’ai fusionné des branches dans l’historique visé, pas de problème en cas de conflit).

Ces limites seront très certainement éliminées avec les améliorations à venir de la commande. Je reste donc aux aguets et mettrai à jour cet article au fil des évolutions.

Vous voulez aller plus loin et maîtriser pleinement les fondamentaux de Git ou être accompagné pour garantir la qualité de vos projets grâce à une bonne mise en place de Git ? On peut vous aider ou vous former, il suffit de nous décrire votre besoin !
Vous pouvez aussi regarder le programme de notre formation "Comprendre Git" ou nous poser vos questions sur notre forum discord.