Annuler un merge/une fusion

Par Maxime Bréhin • Publié le 17 février 2025 • 3 min

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 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.