Annuler un rebase
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 :
- HEAD est placé, détaché de toute étiquette de branche, à l’emplacement du commit servant de base (
main
, donc implicitementm3
) ; - le commit
d1
est dupliqué end1’
à la suite dem3
, et HEAD pointe dessus, toujours en tête détachée ; - le commit
d2
est dupliqué end2’
à la suite ded1’
, et HEAD pointe dessus, toujours en tête détachée ; - l’étiquette de branche
dev
est déplacée pour pointer surd2’
; - 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 pouvez aussi regarder le programme de notre formation "Comprendre Git" ou nous poser vos questions sur notre forum discord.