Pourquoi git ne fusionne-t-il pas les lignes adjacentes sans conflit?

25

J'ai récemment appris que lors de la fusion de deux branches dans git, s'il y a des changements sur deux lignes adjacentes , git déclare que c'est un conflit. Par exemple, si le fichier test.txta ce contenu:

Line 1: A
Line 2: B
Line 3: C
Line 4: D

et en branche masternous changeons cela en

Line 1: A
Line 2: B1
Line 3: C
Line 4: D

tandis que dans la branche, testingnous changeons cela en

Line 1: A
Line 2: B
Line 3: C1
Line 4: D

et ensuite tenter de fusionner testingdans master, git déclare un conflit de fusion. Mon attente naïve était que la fusion se ferait sans conflit et produirait ceci:

Line 1: A
Line 2: B1
Line 3: C1
Line 4: D

Je suis sûr qu'il y a une bonne raison pour laquelle git ne fusionne pas de cette façon. Quelqu'un peut-il expliquer cette raison?

rlandster
la source
Hé, je viens juste de le remarquer la semaine dernière. Peut-être que nous faisions le même tutoriel.
detly
5
les capacités de fusion de git sont en fait assez pauvres, IMO
James
@James, as-tu essayé d'utiliser l'algorithme de patience? Je trouve que j'obtiens de meilleurs résultats avec cela, en particulier quand il s'agit de savoir où les mecs sont divisés (par exemple, saisir un corps de fonction au lieu de deux). Si vous n'aimez pas les git, vous pouvez également utiliser les vôtres (voir blog.wuwon.id.au/2010/09/… pour un exemple).
deterb
1
La cause profonde est que git essaie de faire la fusion lui-même, au lieu de la factoriser vers un outil spécialisé. Pas du tout la philosophie Unix. Pour les fichiers source, vous pouvez réellement utiliser la grammaire de la langue pour déterminer de manière fiable les différences.
MSalters
Le seul contexte commun est A et D, alors pourquoi A / C1 / B1 / D n'est-il pas la fusion correcte?
Izkata

Réponses:

13

En supposant que cet extrait de code

x=0
x+=1 if foo
x+=1 if bar
return x

a été changé dans une branche dans ce

x=0
x+=1 if foo && xyzzy
x+=1 if bar
return x

et dans une autre branche dans ce

x=0
x+=1 if foo
x+=1 if bar && xyzzy
return x

alors je ne voudrais pas que git le fusionne dans ce

x=0
x+=1 if foo && xyzzy
x+=1 if bar && xyzzy
return x

sans me alarmer.

Afin d'éviter de causer de tels problèmes, git refuse généralement de fusionner automatiquement les modifications touchant les lignes voisines. Il vous donne une chance de vérifier si la logique du programme serait cassée ou non.

Cet exemple est trivial, mais lors de la fusion d'énormes branches, le risque de conflits "logiques" similaires est beaucoup plus grand. Parfois, j'aimerais même que le contexte soit encore plus grand qu'il ne l'est actuellement.

Arsen7
la source
5
Cela n'a rien à voir avec cela, cependant, ajoutez simplement une ligne immuable entre ces deux et soudainement, git les fusionne sans problème.
Darkhogg
Ouais je n'ai pas cette réponse. Vous pouvez utiliser la même logique pour éviter toutes les fusions automatiques.
Mehrdad
Il doit y avoir une frontière entre sécurité et utilité. Les fusions automatiques comportent le risque de corrompre le flux du programme - comme j'essayais de le montrer dans la réponse -, mais si git refusait simplement de fusionner quoi que ce soit, cela deviendrait inutile. Les créateurs de git viennent de se décider sur un certain contexte, qui va attraper la plupart des cas "dangereux". Ajouter une ligne inchangée (comme Darkhogg l'a découvert) trompe les imbéciles en pensant que la fusion pourrait être sans danger.
Arsen7
11

Ce comportement est-il réservé aux git?

Après discussion avec un collègue, je viens d'essayer, et SVN le gère sans problème: vous obtenez les 2 lignes modifiées.

Les capacités de fusion de plusieurs VCS sont testées ici pour bazaar, darcs, git et mercurial : https://github.com/mndrix/merge-this

Il semble que seuls les darcs fusionnent avec succès le cas des "lignes adjacentes".

L'application de modifications adjacentes à des fichiers n'est pas un problème difficile. Je pense vraiment que ce comportement a été choisi exprès.

Pourquoi quelqu'un déciderait-il que la modification de lignes adjacentes produit un conflit?

Je pense que c'est pour vous forcer à le regarder .

int max = MAX_ITEMS;
for(unsigned int i = 0; i < max; i++)
    do_stuff(i);

Modif numéro 1, sur maître:

int max = MAX_ITEMS/2; // Do stuff only on the first half
for(unsigned int i = 0; i < max; i++)
    do_stuff(i);

Modif numéro 2, fusionné à partir d'une branche:

int max = MAX_ITEMS;
for(unsigned int i = 0; i < max/2; i++) // max/2: only on 1st half
    do_stuff(i);

Après la fusion, vous ne voulez pas que:

int max = MAX_ITEMS/2; // Do stuff only on the first half
for(unsigned int i = 0; i < max/2; i++) // max/2: only on 1st half
    do_stuff(i);

Voir ce comportement comme une fonctionnalité

Vous pouvez transformer le comportement de fusion git en avantage. Lorsque vous devez conserver 2 lignes cohérentes mais que vous ne pouvez pas les détecter (au moment de la compilation, au début de vos tests ou autre), vous pouvez essayer de les joindre.

Réécrivez ceci ...:

for(unsigned int i = 0; i < max; i++)
    r = do_stuff(i);
    // Need to do something else
    do_something_else(r);

...pour ça:

for(unsigned int i = 0; i < max; i++)
    r = do_stuff(i);
    do_something_else(r); // Need to do something else

Donc, lorsque vous fusionnez Modif 1 ...:

for(unsigned int i = 0; i < max; i++)
    r = do_stuff(i)/2; // we need only the half
    do_something_else(r); // Need to do something else

... avec Modif 2 ...:

for(unsigned int i = 0; i < max; i++)
    r = do_stuff(i);
    if(r < 0) // do_stuff can return an error
        handle_error(r);
    do_something_else(r/2); // Need to do something else

..., git produira un conflit, et vous vous forcerez à le regarder.

ofaurax
la source
2
Je vais juste aller de l'avant et dire que je pense que votre réponse est parfaitement raisonnable, mais en fonction de l'interaction complexe et définie par l'implémentation entre votre code et votre contrôle de source pour la vérification d'intégrité est une voie rapide vers thedailywtf.com. La fusion aveugle de code sans analyseur de langage est TOUJOURS le meilleur effort de toute façon, et j'ai eu quelques cas où git a utilement fusionné quelque chose qu'il ne devrait pas avoir et produit du code qui ne serait même pas compilé.
Wug
5

Je devine principalement, mais je pense que cela a à voir avec la ligne 2 utilisée comme contexte pour le changement de ligne 3.

Git ne peut pas simplement dire que "La ligne avec C est devenue une ligne avec C1" car il pourrait y avoir une autre ligne avec "C", donc il dit "La ligne avec C, c'est juste après le début du fichier, la ligne avec A, et la ligne avec B, est maintenant C1 "

Si "la ligne avec B" n'est plus là, alors une partie du contexte est perdue, et git ne peut que dire approximativement où la nouvelle ligne doit aller.

Mike Gossmann
la source
5
il est également très probable que C dépend de B, donc une fusion naïve peut être gênante même si git "sait comment" le faire
Lucina
Croyez-moi, Git "pense qu'il sait". Git est un cimetière de mauvais concepts, essayant de se redresser!
user3833732
2

Les autres réponses ici sont toutes pertinentes, mais pour moi, cela m'a toujours semblé une limitation inutile.

Comme d'autres l'ont dit, dans ces cas, vous ne voudriez certainement pas que Git fusionne les lignes sans avertissement.

Mais je voulais toujours l'option de le faire automatiquement, après avoir été averti. J'ai donc écrit un pilote de fusion git personnalisé qui pourrait fusionner les conflits sur les lignes adjacentes (ou individuelles) de manière interactive:

entrez la description de l'image ici

Cela me fait gagner beaucoup de temps, car je gère un projet où les gens travaillent souvent sur les mêmes fichiers et refactorisent beaucoup de code.

Le script est disponible sur GitHub sous une licence GPLv3 +. Vous le trouverez peut-être utile:

https://github.com/paulaltin/git-subline-merge

deltacrux
la source
4
Quelqu'un pourrait-il expliquer pourquoi cela a été rejeté? Je suis assez nouveau ici, donc si j'ai fait quelque chose de mal, j'aimerais savoir ce que c'était pour que je puisse l'éviter à l'avenir. Je réalise que mon message ne répond pas exactement à la question posée, mais il est toujours pertinent et je pense que la plupart des gens qui viennent ici voudraient savoir non seulement pourquoi git fait cela, mais aussi ce qu'ils peuvent faire à ce sujet (comme je l'ai fait quand j'ai atteint cette question à partir d'une recherche Google).
deltacrux
Je ne l'ai pas encore essayé mais je suis venu à la recherche d'un moyen d'automatiser cela, et j'étais heureux de le trouver ici. Merci :) tu es génial.
Mehrdad
1
Aucun problème! J'espère que vous le trouverez utile, et n'hésitez pas à laisser des commentaires sur Github si vous rencontrez des problèmes ou avez des suggestions d'amélioration. Merci!
deltacrux