Annuler un merge/une fusion
Tu viens de terminer une fusion (sur ta machine ou via une interface de type pull request ou merge request) et tu souhaites annuler ton opération. Avec Git, ça se fait à l’aide d’une toute petite commande, et c’est tant mieux !
La méthode décrite dans cet article fonctionne pour n’importe quel type de fusion, classique ou fast-forward.
Cet article fait partie de notre série pour savoir annuler, défaire et corriger.
Contexte initial
Pour les scenarios ci-après, nous partirons de l’historique initial suivant, avec les branches main
et dev
. La branche main
pointe sur le commit m2
, la branche dev
pointe sur le commit d3
:
%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { 'commitLabelFontSize': '16px' }, 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'} } }%% gitGraph commit id: "m1" commit id: "m2" branch dev commit id: "d1" commit id: "d2" commit id: "d3"
Si tu réalises la fusion depuis un serveur Git, il te faudra rapatrier l’historique à jour de la fusion pour pouvoir le modifier. Tu devras ensuite écraser l’historique distant en forçant le push.
Annuler une fusion classique
Une fusion classique (aussi appelée “true merge”) produit un commit qui lie les 2 branches et permet d’intégrer le travail de la seconde dans la première. On l’appelle « commit de fusion ».
%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { 'commitLabelFontSize': '16px' }, 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'} } }%% gitGraph commit id: "m1" commit id: "m2" branch dev commit id: "d1" commit id: "d2" commit id: "d3" checkout main merge dev
Suite à une fusion classique, la branche récipiendaire (qui reçoit la fusion) évolue, son étiquette est déplacée pour pointer sur le commit de fusion.
Pour annuler cette fusion, il faut ramener l’étiquette de main
à son emplacement précédent, à savoir le commit m2
. On utilise à cet effet la commande reset :
# Depuis la branche "main"
git reset --keep m2
Notre étiquette main
est donc replacée à sa position d’avant la fusion. Le commit de fusion est alors déréférencé. Ça signifie qu’on peut très bien refaire pointer l’étiquette dessus, annuler l’annulation.
Annuler une fusion en fast-forward
La fusion en fast-forward n’est réalisable que lorsque les 2 branches visées ne divergent pas l’une de l’autre. Dit autrement, la branche à fusionner doit être une série de commits qui partent de l’autre branche, et cette autre branche ne doit pas avoir reçu de nouveau commit entre temps.
Quand le contexte est favorable, la fusion en fast-forward ne produira pas de nouveau commit et déplacera seulement l’étiquette de branche récipiendaire sur le dernier commit de la seconde branche, intégrant de cette manière le travail.
%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { 'commitLabelFontSize': '16px' }, 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main/dev'} } }%% gitGraph commit id: "m1" commit id: "m2" commit id: "d1" commit id: "d2" commit id: "d3"
Note au passage que ce procédé ne nous permet pas/plus de distinguer de quel commit partait initialement la branche dev
.
Revenons à cette fusion et à l’action produite : l’étiquette de la branche main
a été déplacée pour pointer sur le commit d3
. Même si l’historique résultant de la fusion diffère de l’exemple donné en fusion classique, l’opération d’annulation reste la même, à savoir ramener l’étiquette de main
à sa position d’avant fusion, donc m2
:
# Depuis la branche "main"
git reset --keep m2
Une alternative “globale”
Et si je te disais qu’il existe une commande qui permet de défaire aussi bien une fusion qu’un merge ou encore un pull… Cette commande, nous l’avons déjà vue dans l’article précédent « Annuler une rebase ». C’est à peu de chose près celle qu’on vient d’utiliser, à la différence qu’on utilise une syntaxe issue du reflog :
# Depuis la branche main
git reset --keep main@{1}
Et oui, le dernier emplacement de la branche main
est disponible dans le reflog via la syntaxe main@{1}
, peut importe l’opération qu’on vient d’effectuer !
On peut donc généraliser son emploi suite à n’importe quelle opération ayant modifié une branche :
# Attention, l'option "keep" part de l'hypothèse
# qu’aucun travail en cours/non commité n’existe.
git reset --keep <branche-courante>@{1}
Vous pouvez aussi regarder le programme de notre formation "Comprendre Git" ou nous poser vos questions sur notre forum discord.