Comment rétablir un commit de fusion déjà poussé vers une branche distante?

963

git revert <commit_hash>seul ne fonctionnera pas. -mdoit être spécifié, et je suis assez confus à ce sujet.

Quelqu'un a déjà vécu ça?

Yaz
la source
3
Jetez un œil à la réponse à cette question: stackoverflow.com/questions/2318777/…
eugen
2
En relation: Annuler une fusion Git? .
Le lien ici est le meilleur exemple qui illustre la restauration du commit fusionné: christianengvall.se/undo-pushed-merge-git
SK Venkat
Il s'agit d'un exemple où la conception de gitne correspond pas au git-flowflux de travail -ish que tout le monde utilise. Si vous avez developvérifié, bien sûr, vous souhaitez revenir à la branche de fonctionnalité 2-commit qui a introduit un bogue et non à la branche de développement partagée de plusieurs années. Se sent ridicule d'avoir besoin de le choisir -m 1.
pkamb
2
Juste une autre suggestion qui ne m'est jamais venue à l'esprit auparavant - si l'une des listes de validations des branches est petite, vous pourriez vous sentir plus à l'aise de revenir sur des validations individuelles au lieu d'une branche entière de validations.
Sridhar Sarnobat

Réponses:

1155

L' -moption spécifie le numéro parent . En effet, une validation de fusion a plusieurs parents et Git ne sait pas automatiquement quel parent était la ligne principale et quel parent était la branche que vous souhaitez annuler la fusion.

Lorsque vous affichez une validation de fusion dans la sortie de git log, vous verrez ses parents répertoriés sur la ligne qui commence par Merge:

commit 8f937c683929b08379097828c8a04350b9b8e183
Merge: 8989ee0 7c6b236
Author: Ben James <[email protected]>
Date:   Wed Aug 17 22:49:41 2011 +0100

Merge branch 'gh-pages'

Conflicts:
    README

Dans cette situation, git revert 8f937c6 -m 1vous obtiendrez l'arbre tel qu'il était 8989ee0et vous git revert -m 2le rétablirez tel qu'il était 7c6b236.

Pour mieux comprendre les ID parents, vous pouvez exécuter:

git log 8989ee0 

et

git log 7c6b236
Ben James
la source
127
à partir de deux numéros 8989ee0, 7c6b236lequel choisir. Comment pourrais-je comprendre?
Arup Rakshit
12
Après le retour, je ne pense pas que l'on sera en mesure de corriger facilement le code dans la branche source et de fusionner à nouveau? kernel.org/pub/software/scm/git/docs/howto/…
IsmailS
10
En cherchant sur Google une meilleure explication, j'ai trouvé cet article qui, à mon avis, a fait un excellent travail pour passer en revue les détails. J'ai découvert après avoir lu que ce que je cherchais vraiment était la commande RESET, suivie d'une poussée forcée. Peut-être que cela aidera quelqu'un d'autre. atlassian.com/git/tutorials/…
Funktr0n
46
@ArupRakshit si vous exécutez git log 8989ee0et git log 7c6b236, vous devez connaître la réponse.
BMW
4
git log - fusionne pour voir toutes les fusions et git log --no-merges pour voir l'historique sans fusions. La fusion d'une branche ramène l'historique des branches fusionnées dans la cible et rend difficile la différenciation à l'aide d'un journal
Git
370

Voici un exemple complet dans l'espoir que cela aide quelqu'un:

git revert -m 1 <commit-hash> 
git push -u origin master

Où se <commit-hash>trouve le hachage de validation de la fusion que vous souhaitez annuler et, comme indiqué dans l'explication de cette réponse , -m 1indique que vous souhaitez revenir à l'arborescence du premier parent avant la fusion.

La git revert ...ligne valide essentiellement vos modifications tandis que la deuxième ligne rend vos modifications publiques en les poussant vers la branche distante.

Saheed
la source
21
Je pensais que la git revertcommande avait déjà validé l'objet commit créé. Pour que cela ne se produise pas, vous devez saisir le --no-commitdrapeau
Delfic
2
Comme @Delfic l'a mentionné, le commit est déjà géré par la première ligne (j'avais besoin d'un: wq pour le valider) donc la deuxième ligne n'est pas nécessaire.
eka808
1
Ceci est déroutant. il n'y a que 2 lignes et aucun git commit .. quelqu'un peut-il éditer.
Jay Random
176

Ben vous a expliqué comment annuler un commit de fusion, mais il est très important que vous vous rendiez compte qu'en procédant ainsi

"... déclare que vous ne voudrez jamais que les modifications de l'arborescence soient apportées par la fusion. Par conséquent, les fusions ultérieures n'apporteront que les modifications de l'arborescence introduites par des validations qui ne sont pas les ancêtres de la fusion précédemment annulée. Cela peut ou non être ce que vous voulez. " (page de manuel git-merge) .

Un article / liste de diffusion lié à partir de la page de manuel détaille les mécanismes et les considérations impliqués. Assurez-vous simplement de comprendre que si vous annulez la validation de fusion, vous ne pouvez pas fusionner à nouveau la branche plus tard et vous attendre à ce que les mêmes modifications reviennent.

Ryan Stewart
la source
81
Mais vous pouvez annuler le retour pour les récupérer si nécessaire.
dalore
5
Merci. Il est très utile de savoir que le cas d'utilisation pour annuler une fusion - en raison d'un bogue, par exemple - puis fusionner à nouveau la branche entière une fois le bogue corrigé, est un cas courant.
Composante 10
3
Si vous êtes comme moi et que vous vouliez plus tard la fusion, vous pouvez soit annuler le retour, soit choisir le changement que vous avez annulé.
UnitasBrooks
Dans ma situation, j'ai frappé le devoir de «revenir en arrière» pour obtenir mon problème de modifications. La cueillette des cerises pourrait être une façon plus soignée? Je vais essayer ça la prochaine fois ...
Steven Anderson
79

Vous pouvez suivre ces étapes pour rétablir les validations incorrectes ou pour réinitialiser votre branche distante à l'état HEAD / état correct.

  1. extraire la succursale distante du dépôt local.
    git checkout development
  2. copier le hachage de validation (c'est-à-dire l'identifiant de la validation immédiatement avant la mauvaise validation) depuis le journal git git log -n5

    production:

    commit 7cd42475d6f95f5896b6f02e902efab0b70e8038 "Fusionner la branche 'false-commit' dans 'development'"
    commit f9a734f8f44b0b37ccea769b9a2fd774c0f0c012 "c'est un mauvais commit"
    commit 3779ab50e72908da92 "

  3. réinitialiser la branche sur le hachage de validation copié à l'étape précédente
    git reset <commit-hash> (i.e. 3779ab50e72908da92d2cfcd72256d7a09f446ba)

  4. exécutez le git statuspour afficher toutes les modifications qui faisaient partie du mauvais commit.
  5. exécutez simplement git reset --hardpour annuler tous ces changements.
  6. forcez-vous à pousser votre branche locale à distance et notez que votre historique de commit est propre comme il était avant qu'il ne soit pollué.
    git push -f origin development
ssasi
la source
4
que se passe-t-il si entre-temps 20 développeurs ont tiré la dernière fusion de dev?
Ewoks
2
Je ne forcerais pas à pousser une branche de développement lorsqu'il y a 20 développeurs dans l'équipe utilisant cette branche. :) Dans ce cas, il est sage de simplement faire un commit de retour.
ssasi
4
C'est une très bonne solution lorsque vous travaillez seul ou si vous êtes certain qu'aucun autre développeur n'a tiré les engagements que vous avez ratés
Kyle B
52
git revert -m 1 <merge-commit>
Neeraj Kumar
la source
2
Cette réponse manque de beaucoup de détails. C'est peut-être pour ça que c'est bon.
Gustavo Straube
30

Pour garder le journal propre car rien ne s'est passé (avec quelques inconvénients avec cette approche (en raison de push -f)):

git checkout <branch>
git reset --hard <commit-hash-before-merge>
git push -f origin HEAD:<remote-branch>

'commit-hash-before-merge' provient du journal (git log) après la fusion.

nvd
la source
Astuce: si vous faites cela dans votre entreprise, vous pourriez ne pas avoir l'autorisation.
eneski
3
ne jamais faire un push -frepo partagé
Baptiste Mille-Mathias
17

Parfois, le moyen le plus efficace de revenir en arrière consiste à prendre du recul et à remplacer.

git log

Utilisez le 2e hachage de validation (hachage complet, celui auquel vous souhaitez revenir avant l'erreur répertoriée), puis rebranchez-le à partir de là.

git checkout -b newbranch <HASH>

Supprimez ensuite l'ancienne branche, copiez la nouvelle branche à sa place et redémarrez à partir de là.

git branch -D oldbranch
git checkout -b oldbranch newbranch

Si elle a été diffusée, supprimez l'ancienne branche de tous les référentiels, poussez la branche refaite vers la plus centrale et ramenez-la vers le bas pour toutes.

ppostma1
la source
4
L'avertissement concernant la diffusion devrait vraiment être plus explicite sur la façon dont cette idée est horrible. Cela corrompra la version de cette branche pour tout le monde et n'est vraiment utile que si vous travaillez avec un référentiel distant (github / bitbucket) auquel vous seul avez accès.
RobbyD
Pas aussi mauvais que de pousser les fichiers de configuration modifiés en aval vers la production. Il ne sera pas corrompu, c'est juste un re-branchement d'un commit précédent, donc c'est un moyen détourné de déplacer le pointeur des branches vers une version antérieure. Espérons que cela
n'impacte
5

Si vous souhaitez annuler un mergecommit, voici ce que vous devez faire.

  1. Tout d'abord, vérifiez le git logpour trouver l'ID de votre commit de fusion. Vous trouverez également plusieurs identifiants parents associés à la fusion (voir l'image ci-dessous).

entrez la description de l'image ici

Notez l'ID de validation de fusion affiché en jaune. Les ID parents sont ceux écrits sur la ligne suivante sous la formeMerge: parent1 parent2 . Maintenant...

Histoire courte:

  1. Basculez vers la branche sur laquelle la fusion a été effectuée. Ensuite, faites simplement ce git revert <merge commit id> -m 1qui ouvrira une viconsole pour entrer le message de validation. Écrivez, enregistrez, quittez, c'est fait!

Longue histoire:

  1. Basculez vers la branche sur laquelle la fusion a été effectuée. Dans mon cas, c'est la testbranche et j'essaie d'en supprimer la feature/analytics-v3branche.

  2. git revertest la commande qui annule tout commit. Mais il y a une mauvaise astuce lors de l'annulation d'un mergecommit. Vous devez entrer le -mdrapeau sinon il échouera. À partir de là, vous devez décider si vous souhaitez rétablir votre branche et la faire ressembler exactement à ce qu'elle était sur parent1ou parent2via:

git revert <merge commit id> -m 1(revient à parent2)

git revert <merge commit id> -m 2(revient à parent1)

Vous pouvez enregistrer ces parents pour savoir dans quelle direction vous voulez aller et c'est la racine de toute la confusion.

saran3h
la source
5

Toutes les réponses couvraient déjà la plupart des choses, mais j'ajouterai mes 5 cents. En bref, annuler un commit de fusion est assez simple:

git revert -m 1 <commit-hash>

Si vous en avez la permission, vous pouvez le pousser directement vers la branche "master", sinon il vous suffit de le pousser vers votre branche "revert" et de créer une requête pull.

Vous trouverez peut-être des informations plus utiles à ce sujet ici: https://itcodehub.blogspot.com/2019/06/how-to-revert-merge-in-git.html

xproph
la source
1

J'ai trouvé que la création d'un patch inverse entre deux points d'extrémité connus et l'application de ce patch fonctionneraient. Cela suppose que vous avez créé des instantanés (balises) à partir de votre branche principale ou même une sauvegarde de votre branche principale, par exemple master_bk_01012017.

Supposons que la branche de code que vous avez fusionnée dans master soit mycodebranch.

  1. Maître de caisse.
  2. Créez un patch inverse binaire complet entre le maître et votre sauvegarde. git diff --binary master..master_bk_01012017 > ~/myrevert.patch
  3. Vérifiez votre patch git apply --check myrevert.patch
  4. Appliquer le patch avec la signature git am --signoff < myrevert.patch
  5. Si vous devez introduire à nouveau ce code une fois qu'il est résolu, vous devrez dériver le maître rétabli et extraire la branche de correction git branch mycodebranch_fix git checkout mycodebranch_fix
  6. Ici, vous devez trouver la clé SHA pour le retour et revenir au retour git revert [SHA]
  7. Maintenant, vous pouvez utiliser votre mycodebranch_fix pour résoudre les problèmes, valider et re-fusionner dans master une fois terminé.
alexplain
la source
1

La réponse correctement marquée a fonctionné pour moi mais j'ai dû passer un peu de temps à déterminer ce qui se passait .. J'ai donc décidé d'ajouter une réponse avec des étapes simples pour des cas comme le mien ..

Disons que nous avons obtenu les branches A et B .. Vous avez fusionné la branche A dans la branche B et poussé la branche B vers elle-même alors maintenant la fusion en fait partie .. Mais vous voulez revenir au dernier commit avant la fusion .. Que faire tu fais?

  1. Accédez à votre dossier racine git (généralement le dossier du projet) et utilisez git log
  2. Vous verrez l'historique des validations récentes - les validations ont des propriétés commit / author / date tandis que les fusions ont également une propriété merge - donc vous les voyez comme ceci:

    commit: <commitHash> Merge: <parentHashA> <parentHashB> Author: <author> Date: <date>

  3. Utilisez git log <parentHashA>et git log <parentHashB>- vous verrez les historiques de validation de ces branches parentes - les premiers validations de la liste sont les plus récentes

  4. Prenez le <commitHash>commit que vous voulez, allez dans votre dossier racine git et utilisez git checkout -b <newBranchName> <commitHash>- cela créera une nouvelle branche à partir du dernier commit que vous avez choisi avant la fusion .. Voila, prêt!
Божидар Йовчев
la source
0

J'ai également rencontré ce problème sur un PR qui a été fusionné avec la branche principale d'un dépôt GitHub.

Depuis que je voulais juste modifier quelques fichiers modifiés , mais pas tout change PR apporté, je devais amendle merge commitavec git commit --am.

Pas:

  1. Accédez à la branche dont vous souhaitez modifier / rétablir certains fichiers modifiés
  2. Faites les changements que vous souhaitez en fonction des fichiers modifiés
  3. courir git add *ougit add <file>
  4. exécuter git commit --amet valider
  5. courir git push -f

Pourquoi c'est intéressant:

  • Il garde le commit de l'auteur du PR inchangé
  • Il ne casse pas l'arbre git
  • Vous serez marqué comme committer (fusionner l'auteur du commit restera inchangé)
  • Git agit comme si vous aviez résolu des conflits, il supprimera / changera le code dans les fichiers modifiés comme si vous demandiez manuellement à GitHub de ne pas le fusionner tel quel
Maxime Lafarie
la source
0

J'ai trouvé une bonne explication pour annuler la fusion à partir de ce lien et j'ai copié collé l'explication ci-dessous et il serait utile au cas où le lien ci-dessous ne fonctionne pas.

Comment annuler une fusion défectueuse Alan ([email protected]) a déclaré:

J'ai une branche principale. Nous avons une branche sur laquelle certains développeurs travaillent. Ils prétendent qu'il est prêt. Nous le fusionnons dans la branche principale. Il casse quelque chose donc nous revenons à la fusion. Ils apportent des modifications au code. ils parviennent à un point où ils disent que c'est ok et nous fusionnons à nouveau. Une fois examinés, nous constatons que les modifications de code effectuées avant le retour ne sont pas dans la branche principale, mais les modifications de code après sont dans la branche principale. et a demandé de l'aide pour se remettre de cette situation.

L'histoire immédiatement après le "retour de la fusion" ressemblerait à ceci:

---o---o---o---M---x---x---W
              /
      ---A---B

où A et B sont sur le développement latéral qui n'était pas si bon, M est la fusion qui apporte ces changements prématurés dans la ligne principale, x sont des changements sans rapport avec ce que la branche latérale a fait et déjà effectués sur la ligne principale, et W est le " annuler la fusion M "(W ne regarde-t-il pas M à l'envers?). IOW, "diff W ^ .. W" est similaire à "diff -RM ^ .. M".

Un tel "retour" d'une fusion peut être effectué avec:

$ git revert -m 1 M Une fois que les développeurs de la branche latérale ont corrigé leurs erreurs, l'historique peut ressembler à ceci:

---o---o---o---M---x---x---W---x
              /
      ---A---B-------------------C---D

où C et D doivent réparer ce qui a été cassé en A et B, et vous pouvez déjà avoir d'autres changements sur la ligne principale après W.

Si vous fusionnez la branche latérale mise à jour (avec D à son extrémité), aucune des modifications apportées dans A ou B ne sera dans le résultat, car elles ont été annulées par W. C'est ce qu'Alan a vu.

Linus explique la situation:

La restauration d'un commit régulier annule simplement ce que ce commit a fait et est assez simple. Mais l'annulation d'un commit de fusion annule également les données modifiées par le commit, mais cela n'a absolument rien à voir avec les effets sur l' historique de la fusion. Ainsi, la fusion existera toujours, et elle sera toujours considérée comme joignant les deux branches, et les futures fusions verront cette fusion comme le dernier état partagé - et le retour qui a annulé la fusion apportée n'affectera pas du tout cela. Donc, un "retour" annule les modifications de données, mais ce n'est pas du tout un "défaire" dans le sens où il n'annule pas les effets d'un commit sur l'historique du référentiel. Donc, si vous pensez à "revenir" comme "annuler", alors vous allez toujours manquer cette partie des retours. Oui, cela annule les données, mais non, il n'annule pas l'historique. Dans une telle situation, vous voudrez d'abord revenir à la précédente, ce qui donnerait à l'histoire l'aspect suivant:

---o---o---o---M---x---x---W---x---Y
              /
      ---A---B-------------------C---D

où Y est le retour de W. Un tel "retour du retour" peut se faire avec:

$ git revert W Cet historique (en ignorant les conflits possibles entre ce que W et W..Y ont changé) équivaudrait à ne pas avoir du tout W ou Y dans l'historique:

---o---o---o---M---x---x-------x----
              /
      ---A---B-------------------C---D

et la fusion à nouveau de la branche latérale n'aura pas de conflit résultant d'un retour antérieur et d'un retour du retour.

---o---o---o---M---x---x-------x-------*
              /                       /
      ---A---B-------------------C---D

Bien sûr, les modifications apportées en C et D peuvent toujours entrer en conflit avec ce qui a été fait par l'un des x, mais ce n'est qu'un conflit de fusion normal.

MK446
la source
-2

Comme Ryan l'a mentionné, cela git revertpourrait rendre la fusion difficile sur la route, donc ce git revertn'est peut-être pas ce que vous voulez. J'ai trouvé que l'utilisation de la git reset --hard <commit-hash-prior-to-merge>commande était plus utile ici.

Une fois que vous avez terminé la partie de réinitialisation matérielle, vous pouvez alors forcer le push vers la branche distante, c'est git push -f <remote-name> <remote-branch-name>-à- dire où <remote-name>est souvent nommé origin. À partir de là, vous pouvez refusionner si vous le souhaitez.

Harry Wang
la source
4
Tout ce qui implique des poussées de force est une mauvaise idée, sauf si vous êtes le seul à utiliser le référentiel et que vous savez exactement ce que vous faites. Revenir en arrière avec git revert, puis éventuellement revenir en arrière avec git revert (si vous avez besoin de revenir en arrière) est une alternative beaucoup plus sûre.
oyvind