77 votes

Retour en arrière d'une fusion en arrière sur Mercurial

Comment inverser l'effet d'une fusion sur des branches polarisées sans mourir d'agonie ?

Ce problème me tourmente depuis mois et j'ai finalement abandonné.

Vous avez 1 référentiel, avec 2 Nommé Branches. A et B.

Les changements qui surviennent en A se répercutent inévitablement en B.

Les changements qui se produisent directement sur B NE DOIVENT JAMAIS se produire sur A.

Dans une telle configuration, la fusion de "B" en "A" produit un gros problème dans le référentiel, car toutes les modifications de B apparaissent dans A comme si elles avaient été faites dans A.

La seule façon "normale" de se remettre de cette situation semble être de "revenir en arrière" dans la fusion, c'est-à-dire d'annuler la fusion :

 hg up -r A 
 hg backout -r BadMergeRev --parent BadMergerevBeforeOnA 

Ce qui semble très bien, jusqu'à ce que vous décidiez de fusionner plus tard dans la bonne direction, et vous vous retrouvez avec toutes sortes de choses désagréables qui se produisent et le code qui a été effacé / commenté sur la branche spécifique B devient soudainement non effacé ou non commenté.

Jusqu'à présent, il n'y a pas eu de solution viable à ce problème, à part "laisser faire les choses, puis régler tous les problèmes à la main", ce qui, pour être honnête, est un peu foireux.

Voici une image qui clarifie le problème :

[Image originale perdue]

Les fichiers C et E (ou les modifications C et E) doivent apparaître uniquement sur la branche b, et non sur la branche a. La révision A9 ici ( branche a, revno 9 ) est le début du problème.

Les révisions A10 et A11 sont les phases de "fusion du backout" et de "fusion du backout".

Et la révision B12 est mercuriale, abandonnant par erreur et à plusieurs reprises un changement qui ne devait pas être abandonné.

Ce dilemme a causé beaucoup de frustration et de fumée bleue et je voudrais y mettre un terme.

Note

La réponse la plus évidente est peut-être d'essayer d'empêcher la fusion inverse de se produire, soit avec des crochets, soit avec des politiques, mais j'ai constaté que la capacité de faire échouer cette opération est plutôt élevée et que le risque qu'elle se produise est si probable que, même avec des contre-mesures, on ne peut pas faire autrement. doit suppose toujours qu'inévitablement, il sera pour que vous puissiez le résoudre quand il se produira.

Elaborer

Dans le modèle, j'ai utilisé des fichiers séparés. Ceux-ci font paraître le problème simple. Ils représentent simplement changements arbitraires qui pourrait être une ligne séparée.

De plus, pour ajouter l'insulte à l'injure, des modifications substantielles ont été apportées à la branche A, ce qui pose le problème suivant : "les modifications de la branche A entrent-elles en conflit avec les modifications de la branche B qui viennent d'apparaître (et qui ont été retirées) et qui ressemblent à une modification de la branche A ?

Sur les astuces de réécriture de l'histoire :

Le problème de toutes ces solutions rétroactives est le suivant :

  1. Nous avons 9000 commits.
  2. Le clonage à l'état frais prend donc une demi-heure
  3. S'il existe même un mauvais clone du référentiel quelque part il est probable qu'il revienne en contact avec le dépôt d'origine, et le perturbe à nouveau.
  4. Tout le monde a déjà cloné ce dépôt, et maintenant plusieurs jours ont passé avec des commits continus.
  5. L'un de ces clones se trouve être un site actif, donc "effacer ce site et repartir de zéro" = "grand nono".

(J'admets que la plupart des propositions ci-dessus sont un peu stupides, mais elles sont hors de mon contrôle).

Les seules solutions qui sont viables sont celles qui supposent que les gens peut et sera font tout de travers, et qu'il existe un moyen de "défaire" ces erreurs.

50voto

oenli Points 561

Je pense avoir trouvé une solution qui corrige définitivement la mauvaise fusion, et qui ne nécessite pas de vérifier manuellement les diffs. L'astuce consiste à remonter dans l'historique et à générer des commits parallèles à la mauvaise fusion.

Nous avons donc un référentiel avec des branches séparées par version maintenue d'un seul produit. Comme dans la situation posée dans la question, toutes les modifications apportées à une branche d'une version antérieure (c'est-à-dire les corrections de bogues de cette version) doivent toutes être fusionnées dans les branches des versions ultérieures.

Plus précisément, si un élément est enregistré sur BRANCH_V8, il doit être fusionné sur BRANCH_V9.

Maintenant, un des développeurs fait l'erreur suivante : il fusionne toutes les modifications de BRANCH_V9 dans BRANCH_V8 (c'est-à-dire une fusion dans le mauvais sens). De plus, après cette mauvaise fusion, il effectue quelques commits supplémentaires avant de s'apercevoir de son erreur.

La situation se présente donc comme indiqué dans le graphique ci-dessous.

o  BRANCH\_V8 - 13 - important commit right after the bad merge
|
o    BRANCH\_V8 - 12 - wrong merge from BRANCH\_V9
|\\
| o  BRANCH\_V8 - 11 - adding comment on BRANCH\_V8 (ie. last known good state)
| |
o |  BRANCH\_V9 - 10 - last commit on BRANCH\_V9
| |

Nous pouvons réparer cette erreur comme suit :

  1. mettez à jour votre répertoire local avec le dernier bon état de BRANCH_V8 : hg update 11

  2. Créer un nouvel enfant de ce dernier bon état :

    1. changer un fichier $EDITOR some/file.txt (ceci est nécessaire car Mercurial ne permet pas les commits vides)
    2. valider ces changements hg commit -m "generating commit on BRANCH_V8 to rectify wrong merge from BRANCH_V9"
      La situation se présente maintenant comme suit : o BRANCH_V8 - 14 - generating commit on BRANCH_V8 to rectify wrong merge from BRANCH_V9
      o BRANCH_V8 - 13 - important commit right after the bad merge
      o BRANCH_V8 - 12 - wrong merge from BRANCH_V9
      /
      o BRANCH_V8 - 11 - adding comment on BRANCH_V8
      o BRANCH_V9 - 10 - last commit on BRANCH_V9
  3. Fusionner la tête nouvellement générée avec la révision dans laquelle la mauvaise fusion s'est produite, et jeter tous les changements avant de commiter. Ne fusionnez pas simplement les deux têtes, car vous perdriez alors le commit important qui s'est produit après la fusion !

    1. fusionner : hg merge 12 (ignorez les conflits éventuels)
    2. jeter tous les changements : hg revert -a --no-backup -r 14
    3. +---o  BRANCH\_V8 - 13 - important commit right after the bad merge
      | |
      o |  BRANCH\_V8 - 12 - wrong merge from BRANCH\_V9
      |\\|
      | o  BRANCH\_V8 - 11 - adding comment on BRANCH\_V8
      | |
      o |  BRANCH\_V9 - 10 - last commit on BRANCH\_V9
      | |

    C'est-à-dire qu'il y a deux têtes sur BRANCH_V8 : une qui contient la correction de la mauvaise fusion, et l'autre qui contient le commit important restant sur BRANCH_V8 qui s'est produit juste après la fusion.

  4. Fusionner les deux têtes sur BRANCH_V8 :

    1. fusionner : hg merge
    2. commettre : hg commit -m "merged two heads used to revert from bad merge"

La situation à la fin sur BRANCH_V8 est maintenant corrigée, et ressemble à ceci :

o    BRANCH\_V8 - 16 - merged two heads used to revert from bad merge
|\\
| o    BRANCH\_V8 - 15 - throwing away wrong merge from BRANCH\_V9
| |\\
| | o  BRANCH\_V8 - 14 - generating commit on BRANCH\_V8 to rectify wrong merge from BRANCH\_V9
| | |
o | |  BRANCH\_V8 - 13 - important commit right after the bad merge
|/ /
o |  BRANCH\_V8 - 12 - wrong merge from BRANCH\_V9
|\\|
| o  BRANCH\_V8 - 11 - adding comment on BRANCH\_V8
| |
o |  BRANCH\_V9 - 10 - last commit on BRANCH\_V9
| |

Maintenant la situation sur BRANCH_V8 est correcte. Le seul problème qui subsiste est que la prochaine fusion de BRANCH_V8 vers BRANCH_V9 sera incorrecte, car elle intégrera également le "correctif" de la mauvaise fusion, ce que nous ne voulons pas sur BRANCH_V9. L'astuce ici est de fusionner de BRANCH_V8 à BRANCH_V9 dans des changements séparés :

  • Première fusion, de BRANCH_V8 vers BRANCH_V9, les changements corrects sur BRANCH_V8 d'avant la mauvaise fusion.
  • Ensuite, fusionner l'erreur de fusion et sa correction, et, sans avoir besoin de vérifier quoi que ce soit, jeter toutes les modifications.
  • Troisièmement, fusionnez les changements restants de BRANCH_V8.

En détail :

  1. Changez votre répertoire de travail en BRANCH_V9 : hg update BRANCH_V9

  2. Fusionnez dans le dernier bon état de BRANCH_V8 (c'est-à-dire le commit que vous avez généré pour corriger la mauvaise fusion). Cette fusion est une fusion comme une autre, c'est-à-dire que les conflits doivent être résolus comme d'habitude, et rien ne doit être jeté.

    1. fusionner : hg merge 14
    2. commettre : hg commit -m "Merging in last good state of BRANCH_V8" La situation est maintenant : @ BRANCH_V9 - 17 - Merging in last good state of BRANCH_V8 \ o BRANCH_V8 - 16 - merged two heads used to revert from bad merge \ +---o BRANCH_V8 - 15 - throwing away wrong merge from BRANCH_V9
      o BRANCH_V8 - 14 - generating commit on BRANCH_V8 to rectify wrong merge from BRANCH_V9
      o BRANCH_V8 - 13 - important commit right after the bad merge
      /
      +---o  BRANCH\_V8 - 12 - wrong merge from BRANCH\_V9
      | |/
      | o  BRANCH\_V8 - 11 - adding comment on BRANCH\_V8
      | |
      o |  BRANCH\_V9 - 10 - last commit on BRANCH\_V9
      | |
  3. Fusionner dans la mauvaise fusion sur BRANCH_V8 + son correctif, et jeter toutes les modifications :

    1. fusionner : hg merge 15
    2. annuler tous les changements : hg revert -a --no-backup -r 17
    3. valider la fusion : hg commit -m "Merging in bad merge from BRANCH_V8 and its fix and throwing it all away" Situation actuelle : @ BRANCH_V9 - 18 - Merging in bad merge from BRANCH_V8 and its fix and throwing it all away \ o BRANCH_V9 - 17 - Merging in last good state of BRANCH_V8 \ +-----o BRANCH_V8 - 16 - merged two heads used to revert from bad merge
      o---+ BRANCH_V8 - 15 - throwing away wrong merge from BRANCH_V9
      o BRANCH_V8 - 14 - generating commit on BRANCH_V8 to rectify wrong merge from BRANCH_V9
      +-----o  BRANCH\_V8 - 13 - important commit right after the bad merge
      | | |
      o---+  BRANCH\_V8 - 12 - wrong merge from BRANCH\_V9
      |/ /
      | o  BRANCH\_V8 - 11 - adding comment on BRANCH\_V8
      | |
      o |  BRANCH\_V9 - 10 - last commit on BRANCH\_V9
      | |
  4. Fusionnez les changements restants de BRANCH_V8 :

    1. fusionner : hg merge BRANCH_V8
    2. commettre : hg commit -m "merging changes from BRANCH_V8"

Au final, la situation ressemble à ceci :

@    BRANCH\_V9 - 19 - merging changes from BRANCH\_V8
|\\
| o    BRANCH\_V9 - 18 - Merging in bad merge from BRANCH\_V8 and its fix and throwing it all away
| |\\
| | o    BRANCH\_V9 - 17 - Merging in last good state of BRANCH\_V8
| | |\\
o | | |  BRANCH\_V8 - 16 - merged two heads used to revert from bad merge
|\\| | |
| o---+  BRANCH\_V8 - 15 - throwing away wrong merge from BRANCH\_V9
| | | |
| | | o  BRANCH\_V8 - 14 - generating commit on BRANCH\_V8 to rectify wrong merge from BRANCH\_V9
| | | |
o | | |  BRANCH\_V8 - 13 - important commit right after the bad merge
|/ / /
o---+  BRANCH\_V8 - 12 - wrong merge from BRANCH\_V9
|/ /
| o  BRANCH\_V8 - 11 - adding comment on BRANCH\_V8
| |
o |  BRANCH\_V9 - 10 - last commit on BRANCH\_V9
| |

Après toutes ces étapes, au cours desquelles vous n'avez pas à vérifier manuellement les différences, BRANCH_V8 et BRANCH_V9 sont correctes, et les fusions futures de BRANCH_V8 à BRANCH_V9 le seront également.

2voto

Justin Love Points 3073

En cas de besoin, vous pouvez exporter le dépôt vers un ensemble de diffs, modifier l'historique, puis recoller ce que vous voulez - dans un nouveau dépôt, donc sans risque de dommage. Probablement pas trop mal pour votre exemple, mais je ne sais pas à quoi ressemble l'histoire réelle.

J'ai référencé cette page en effectuant une opération plus simple :

http://strongdynamic.blogspot.com/2007/08/expunging-problem-file-from-mercurial.html

2voto

Kevin Points 15

OK, commencez par créer un nouveau vide dans un répertoire séparé du dépôt cassé (hg init). Maintenant, tirez jusqu'à et y compris la dernière bonne version connue dans le nouveau dépôt ; assurez-vous que vous avez ne pas tirer la mauvaise fusion et faire tirer tout ce qu'il y a avant. Dans l'ancien référentiel, mettez à jour la dernière bonne version connue de A, et faites ceci :

hg graft r1 r2 r3

où r1-3 sont les changements effectués après la fusion bâclée. Vous pouvez obtenir des conflits à ce stade ; corrigez-les.

Cela devrait produire de nouveaux changements contre la dernière bonne version connue de A . Mettez ces nouvelles modifications dans le nouveau référentiel. Juste pour vérifier que vous n'avez rien manqué, faites un hg incoming contre l'ancien dépôt. Si vous voyez autre chose que la fusion bâclée et r1-3, retirez-le.

Jetez l'ancien référentiel. Vous avez terminé. La fusion n'est pas du tout dans le nouveau dépôt et vous n'avez jamais eu à réécrire l'histoire.

1voto

Nathan Kitchen Points 2729

Pouvez-vous vous permettre de remplacer le dépôt par un clone jusqu'à A7 et B8 ?

1voto

Donc vous voulez fusionner juste quelques changesets de B en A ? Revenir en arrière sur les changesets comme vous l'avez fait est une très mauvaise idée, comme vous en avez déjà souffert.

Vous devriez soit utiliser l'extension de transplantation, soit avoir une troisième branche où vous faites des changements communs à fusionner dans A et B.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X