Git fusionne avec écrasement forcé


101

J'ai une branche appelée demoque je dois fusionner avec la masterbranche. Je peux obtenir le résultat souhaité avec les commandes suivantes:

git pull origin demo
git checkout master
git pull origin master
git merge demo
git push origin master

Ma seule préoccupation est que s'il y a des problèmes de fusion , je veux dire gitd'écraser les modifications dans la masterbranche sans me donner d'invite de fusion. Donc, fondamentalement, les changements de demobranche devraient automatiquement écraser les changements de masterbranche.

J'ai regardé autour de moi, il y a plusieurs options, mais je ne veux pas prendre de risques avec la fusion.


git push -f origin master
MD XF

4
@MDXF: Peut-être que je me trompe, mais ne devrais-je pas utiliser l' -foption avec la mergecommande et non avec la pushcommande
OpenStack

Vous pouvez essayer les deux et voir ce qui fonctionne pour vous
MD XF

Voir le lien ci-dessous pour une solution d'écrasement de force: stackoverflow.com/a/42454737/984471
Manohar Reddy Poreddy

Réponses:


102

Pas vraiment lié à cette réponse, mais je laisserais tomber git pull, qui ne fait que git fetchsuivre git merge. Vous effectuez trois fusions, ce qui va obliger votre Git à exécuter trois opérations d'extraction, lorsqu'une extraction est tout ce dont vous aurez besoin. Par conséquent:

git fetch origin   # update all our origin/* remote-tracking branches

git checkout demo         # if needed -- your example assumes you're on it
git merge origin/demo     # if needed -- see below

git checkout master
git merge origin/master

git merge -X theirs demo   # but see below

git push origin master     # again, see below

Contrôle de la fusion la plus délicate

La partie la plus intéressante ici est git merge -X theirs. Comme root545 l'a noté , les -Xoptions sont transmises à la stratégie de fusion, et la recursivestratégie par défaut et la resolvestratégie alternative prennent -X oursou -X theirs(l'une ou l'autre, mais pas les deux). Pour comprendre ce qu'ils font, cependant, vous devez savoir comment Git trouve et traite les conflits de fusion .

Un conflit de fusion peut se produire dans un fichier 1 lorsque la version de base diffère à la fois de la version actuelle (également appelée locale, HEAD ou --ours) et de l'autre version (également appelée distante ou --theirs) de ce même fichier. Autrement dit, la fusion a identifié trois révisions (trois commits): la base, la nôtre et la leur. La version "de base" provient de la base de fusion entre notre commit et leur commit, comme on le trouve dans le graphe de commit (pour beaucoup plus à ce sujet, voir d'autres publications de StackOverflow). Git a alors trouvé deux ensembles de changements: "ce que nous avons fait" et "ce qu'ils ont fait". Ces changements sont (en général) trouvés sur une ligne par ligne, purement textuellebase. Git n'a aucune compréhension réelle du contenu des fichiers; il s'agit simplement de comparer chaque ligne de texte.

Ces changements sont ce que vous voyez dans la git diffsortie et, comme toujours, ils ont également un contexte . Il est possible que les choses que nous avons modifiées soient sur des lignes différentes de celles qu'elles ont modifiées, de sorte que les changements semblent ne pas entrer en collision, mais le contexte a également changé (par exemple, en raison de notre changement proche du haut ou du bas du fichier, pour que le fichier s'épuise dans notre version, mais dans la leur, ils ont également ajouté plus de texte en haut ou en bas).

Si les modifications se produisent sur des lignes différentes - par exemple, nous passons colorà la colourligne 17 et elles passent fredà la barneyligne 71 - alors il n'y a pas de conflit: Git prend simplement les deux modifications. Si les modifications se produisent sur les mêmes lignes, mais sont des modifications identiques , Git prend une copie de la modification. Seulement si les changements sont sur les mêmes lignes, mais sont des changements différents, ou ce cas particulier de contexte interférant, vous obtenez un conflit de modification / modification.

Les options -X ourset -X theirsindiquent à Git comment résoudre ce conflit, en ne sélectionnant qu'un seul des deux changements: le nôtre ou le leur. Puisque vous avez dit que vous fusionnez demo(le leur) dans master(le nôtre) et que vous voulez les changements demo, vous voudriez -X theirs.

L'application aveugle -X, cependant, est dangereuse. Ce n'est pas parce que nos modifications ne sont pas en conflit ligne par ligne que nos modifications ne sont pas réellement en conflit! Un exemple classique se produit dans les langages avec des déclarations de variables. La version de base peut déclarer une variable inutilisée:

int i;

Dans notre version, nous supprimons la variable inutilisée pour faire disparaître un avertissement du compilateur - et dans leur version, ils ajoutent une boucle quelques lignes plus tard, en utilisant icomme compteur de boucle. Si nous combinons les deux changements, le code résultant ne se compile plus. L' -Xoption n'est d'aucune aide ici puisque les changements sont sur des lignes différentes .

Si vous disposez d'une suite de tests automatisés, la chose la plus importante à faire est d'exécuter les tests après la fusion. Vous pouvez le faire après la validation et réparer les choses plus tard si nécessaire; ou vous pouvez le faire avant de valider, en ajoutant --no-commità la git mergecommande. Nous laisserons les détails pour tout cela à d'autres publications.


1 Vous pouvez également obtenir des conflits en ce qui concerne les opérations "à l'échelle du fichier", par exemple, peut-être que nous corrigeons l'orthographe d'un mot dans un fichier (afin que nous ayons un changement), et qu'ils suppriment le fichier entier (afin qu'ils aient un supprimer). Git ne résoudra pas ces conflits tout seul, quels que soient les -Xarguments.


Faire moins de fusions et / ou des fusions plus intelligentes et / ou utiliser le rebase

Il y a trois fusions dans nos deux séquences de commandes. La première consiste à introduire origin/demodans le local demo(vos utilisations git pullqui, si votre Git est très ancien, échoueront à se mettre à jour origin/demomais produiront le même résultat final). Le second est de faire origin/masterentrer master.

Ce n'est pas clair pour moi qui met à jour demoet / ou master. Si vous écrivez votre propre code sur votre propre demobranche et que d' autres écrivent du code et le poussent vers la demobranche origin, alors cette première fusion peut avoir des conflits ou produire une véritable fusion. Le plus souvent, il est préférable d'utiliser le rebase, plutôt que de fusionner, pour combiner le travail (certes, c'est une question de goût et d'opinion). Si tel est le cas, vous voudrez peut-être utiliser à la git rebaseplace. D'un autre côté, si vous ne faites jamais aucun de vos propres commits demo, vous n'avez même pas besoin d' une demobranche. Sinon, si vous souhaitez automatiser une grande partie de cela, mais être en mesure de vérifier soigneusement quand il y a des commits que vous et d'autres avez faits, vous voudrez peut-être utilisergit merge --ff-only origin/demo: cela vous fera avancer rapidement demopour correspondre à la mise à jour origin/demosi possible, et échouera simplement si ce n'est pas le cas (à quel point vous pouvez inspecter les deux ensembles de modifications et choisir une véritable fusion ou un rebase selon le cas).

Cette même logique s'applique master, bien que vous faites la fusion sur master , vous avez certainement besoin d' un master. Cependant, il est encore plus probable que vous souhaitiez que la fusion échoue si elle ne peut pas être effectuée en tant que non-fusion à avance rapide, donc cela devrait probablement également l'être git merge --ff-only origin/master.

Disons que vous ne faites jamais vos propres engagements demo. Dans ce cas, nous pouvons democomplètement abandonner le nom :

git fetch origin   # update origin/*

git checkout master
git merge --ff-only origin/master || die "cannot fast-forward our master"

git merge -X theirs origin/demo || die "complex merge conflict"

git push origin master

Si vous êtes en train de faire vos propres democommits de branche, ce n'est pas utile; vous pouvez aussi bien conserver la fusion existante (mais peut-être l'ajouter en --ff-onlyfonction du comportement souhaité), ou la changer pour effectuer un rebase. Notez que les trois méthodes peuvent échouer: la fusion peut échouer avec un conflit, la fusion avec --ff-onlypeut ne pas être en mesure d'avancer rapidement, et le rebase peut échouer avec un conflit (le rebase fonctionne, essentiellement, en sélectionnant les commits, qui utilise la fusion machines et peut donc provoquer un conflit de fusion).


21

J'ai eu un problème similaire, où je devais remplacer efficacement tout fichier qui avait des changements / conflits avec une branche différente.

La solution que j'ai trouvée était d'utiliser git merge -s ours branch.

Notez que l'option est -set non -X. -sindique l'utilisation de ourscomme stratégie de fusion de niveau supérieur, -Xappliquerait l' oursoption à la recursivestratégie de fusion, ce qui n'est pas ce que je (ou nous) voulons dans ce cas.

Steps, où se oldbranchtrouve la branche avec laquelle vous souhaitez écraser newbranch.

  • git checkout newbranch vérifie la succursale que vous souhaitez conserver
  • git merge -s ours oldbranch fusionne dans l'ancienne branche, mais conserve tous nos fichiers.
  • git checkout oldbranch vérifie la branche que vous souhaitez écraser
  • get merge newbranch fusionne dans la nouvelle branche, écrasant l'ancienne branche

Remarque: aucune des autres solutions ici n'a fonctionné pour moi, c'est pourquoi je publie ma réponse.
Niall Mccormack

1
Cela n'a pas fonctionné pour moi. Il a résolu le conflit (résolu les fichiers en conflit) mais le fichier n'est pas fusionné. Et ne peut pas fusionner non plus.
c-an

Si quelqu'un se trouve bloqué là où vous êtes invité à «Veuillez entrer un message de validation pour expliquer pourquoi cette fusion est nécessaire»: entrez votre message, puis appuyez sur la touche Échap de votre clavier, tapez: wq et appuyez sur ENTRÉE pour quitter l'invite.
topherPedersen

19

Cette approche de fusion ajoutera un commit sur masterlequel se colle dans tout ce qui se trouve feature, sans se plaindre de conflits ou d'autres conneries.

entrez la description de l'image ici

Avant de toucher quoi que ce soit

git stash
git status # if anything shows up here, move it to your desktop

Maintenant, prépare le maître

git checkout master
git pull # if there is a problem in this step, it is outside the scope of this answer

Obtenez featuretout habillé

git checkout feature
git merge --strategy=ours master

Allez pour le tuer

git checkout master
git merge --no-ff feature

1
git push to finish
Kochchy

git-scm.com/docs/git-merge#Documentation/git-merge.txt-ours. Cela fonctionnait lorsque les commits ne fusionnaient pas proprement. Mais cette approche ne fonctionnera pas toujours, pour citer la source Changes from the other tree that do not conflict with our side are reflected in the merge result. Cela n'a pas fonctionné pour moi, car j'avais des commits fusionnés proprement dans ma branche qui était en cours d'écrasement. Est-ce qu'il y a un autre moyen?
NiharGht

11

Vous pouvez essayer l'option "notre" dans git merge,

git merge branch -X le nôtre

Cette option oblige les morceaux en conflit à être résolus automatiquement en favorisant notre version. Les modifications de l'autre arbre qui ne sont pas en conflit avec notre côté sont reflétées dans le résultat de la fusion. Pour un fichier binaire, tout le contenu est pris de notre côté.


3
Ce serait à l'envers, car l'OP a dit qu'il voulait que la demoversion soit préférée même s'il fusionne avec master. Il y -X theirsen a aussi, mais il n'est pas clair que ce soit ce que le PO veut vraiment.
torek

Vous n'avez pas lu tout le chemin. Votre note décrit ce que oursfait lors de l'utilisation sur le backend avec l' -soption. Lors de l'utilisation oursavec -X: Il supprime tout ce que l'autre arbre a fait, déclarant que notre historique contient tout ce qui s'est passé. source
jimasun

2

Quand j'ai essayé d'utiliser -X theirset d'autres commutateurs de commande connexes, j'ai continué à obtenir un commit de fusion. Je ne l'ai probablement pas bien compris. Une alternative facile à comprendre est simplement de supprimer la branche puis de la suivre à nouveau.

git branch -D <branch-name>
git branch --track <branch-name> origin/<branch-name>

Ce n'est pas exactement une "fusion", mais c'est ce que je cherchais quand je suis tombé sur cette question. Dans mon cas, je voulais extraire des modifications d'une branche distante qui ont été forcées.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.