Annuler un rebase

Par Maxime Bréhin • Publié le 13 février 2025 • 4 min

Tu viens de faire un rebase et tu t’aperçois que tu l’as fait à l’envers ou que ça n’était pas une bonne idée ? Pas d’inquiétude, tu peux revenir en arrière à l’ aide d’une seule commande !

Cet article fait partie de notre série pour savoir annuler, défaire et corriger.

Petit rappel utile sur rebase

Pour maîtriser l’annulation du rebase, il faut comprendre la manière dont il fonctionne.

Un rebase est une réplication d’une partie d’historique sur un commit qui servira de nouvelle base, de nouveau point de départ. On peut le voir comme une série de copiés/collés de commits (sous forme de cherry-picking si tu connais).

L’essentiel du temps, un rebase servira à mettre à jour une branche. Il terminera donc sa série de copiés/collés par le déplacement de l’étiquette de la branche désignée (ou implicite selon la syntaxe utilisée).

Étapes du rebase

Prenons un exemple simple mais classique. Voici notre historique initial qui comprend 2 branches, main et dev.

%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { 'commitLabelFontSize': '16px' }, 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'} } }%%
  gitGraph
    commit id: "m1"
    branch dev
    commit id: "d1"
    commit id: "d2"
    checkout main
    commit id: "m2"
    commit id: "m3"

La branche main a évolué depuis la création de dev. Elle contient 2 nouveaux commits m2 et m3. On met à jour dev “par dessus” main avec un rebase.

git rebase main dev

On obtient alors l’historique suivant :

%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { 'commitLabelFontSize': '16px' }, 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'} } }%%
  gitGraph
    commit id: "m1"
    commit id: "m2"
    commit id: "m3"
    branch dev
    commit id: "d1’"
    commit id: "d2’"

Les étapes “cachées”

Ce que ne montre pas ces diagrammes statiques, c’est le détail des opérations entre la référence HEAD et l’étiquette dev. Voici ce qui se passe :

  1. HEAD est placé, détaché de toute étiquette de branche, à l’emplacement du commit servant de base (main, donc implicitement m3) ;
  2. le commit d1 est dupliqué en d1’ à la suite de m3, et HEAD pointe dessus, toujours en tête détachée ;
  3. le commit d2 est dupliqué en d2’ à la suite de d1’, et HEAD pointe dessus, toujours en tête détachée ;
  4. l’étiquette de branche dev est déplacée pour pointer sur d2’ ;
  5. HEAD sort du mode “détaché” pour pointer sur dev.

Là, tu te demandes certainement pourquoi je te décris tout ça ! Tu vas vite comprendre. Pour l’instant, retiens bien cette information : le rebase ne déplace l’étiquette de la branche qu’une fois.

Annuler le rebase : un saut unique en “arrière”

Tu l’attendais avec impatience, voilà la solution : pour annuler un rebase, il suffit de ramener l’étiquette de branche à sa position précédente (d’avant le rebase). Cette étiquette n’ayant été déplacée qu’une seule fois, ça signifie un saut d’un cran en arrière dans les opérations.

Cette opération est possible car Git ne supprime pas nos anciens commits, il les déréférence. Imagine que nous ayons une branche sans nom qui les contient encore. Mais comme elle n’a pas de nom, elle n’est pas affichée dans l’historique. Là, je triche pour que tu puisses t’en faire une représentation mentale.

%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { 'commitLabelFontSize': '16px', 'git1': '#dddddd' }, 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'} } }%%
  gitGraph
    commit id: "m1"
    branch "(ancien dev)"
    commit id: "d1"
    commit id: "d2"
    checkout main
    commit id: "m2"
    commit id: "m3"
    branch dev
    commit id: "d1’"
    commit id: "d2’"

L’emplacement précédent de l’étiquette dev était le commit d2. Sauf que l’historique (le log) ne nous le montre plus. Il faut donc trouver un autre moyen pour récupérer cette référence.

Comment savoir quelle était cette position ?

C’est le reflog qui va nous le dire. On peut d’ailleurs vérifier les opérations menées sur notre branche à l’aide de la commande :

git reflog -10 <nom-de-branche>
# ici on aurait fait : "git reflog -10 dev"

L’affichage obtenu ressemble à ça :

0092d61 dev@{0}: rebase (finish): refs/heads/dev onto …
fa1ece1 dev@{1}: commit: …
46ed548 dev@{2}: commit: …
…

L’information que nous apporte le reflog ici, c’est qu’on peut utiliser la syntaxe dev@{1} pour revenir avant l’opération de rebase qui vient de se terminer (signalée en première ligne : dev@{0}: rebase (finish):…).

Il ne nous reste plus qu’à combiner ça avec un appel à la commande reset :

# On s'assure d’être bien sur la branche dev avant de lancer la commande.
git reset --keep dev@{1}

Et le tour est joué ! Il ne reste qu’à vérifier l’historique pour voir si on a bien fait.

Précisons encore une dernière chose : annuler le rebase revient à déréférencer les commit “rejoués”. Si on reprend cette idée de branche sans nom, on peut modéliser le résultat comme ci-dessous :

%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { 'commitLabelFontSize': '16px', 'git2': '#dddddd' }, 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'} } }%%
  gitGraph
    commit id: "m1"
    branch dev
    commit id: "d1"
    commit id: "d2"
    checkout main
    commit id: "m2"
    commit id: "m3"
    branch "(dev d’après rebase)"
    commit id: "d1’"
    commit id: "d2’"

Ça signifie qu’on peut aussi annuler l’annulation…

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.