21

I recently learned that when merging two branches in git, if there are changes on two adjacent lines git declares this a conflict. For example, if file test.txt has this content:

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

and in branch master we change this to

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

while in branch testing we change this to

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

and then attempt to merge testing into master, git declares a merge conflict. My naive expectation was that the merge would happen without conflict and yield this:

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

I am sure there is a good reason why git doesn't merge this way. Can someone explain this reason?

  • Hey I just noticed this last week too. Maybe we were doing the same tutorial. – detly Apr 12 '13 at 22:56
  • 3
    git's merging abilities are actually quite poor, IMO – James Apr 13 '13 at 13:40
  • @James have you tried using the patience algorithm? I find I get better results with it, in particular when dealing with where hunks are split (e.g. grabbing one function body instead of two). If you don't like git's, you can also use your own (see blog.wuwon.id.au/2010/09/… for an example). – deterb May 6 '13 at 18:58
  • 1
    The root cause is that git tries to do the merge itself, instead of factoring it out to a specialized tool. Entirely not the Unix philosophy. For source files, you can actually use the language grammar to reliably determine diffs. – MSalters Sep 9 '13 at 13:30
  • The only common context is A and D, so why isn't A/C1/B1/D the correct merge? – Izkata Mar 4 '16 at 16:49
12

Assuming that this snippet of code

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

has been changed in one branch into this

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

and in another branch into this

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

then I would not want git to merge it into this

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

without alarming me.

This example is trivial, but when merging huge branches the risk of similar "logical" conflicts is much bigger. Sometimes I would even love the context to be even bigger than it currently is.

  • 3
    That has nothing to do with it, though, simply add an unchanging line between those two and suddenly git merges them without a problem. – Darkhogg Sep 11 '18 at 14:24
11

Is this git-only behavior?

After discussion with a colleague, I just tried, and SVN handles it without problem: you get the 2 lines modified.

The merge capabilities of several VCS are tested here for bazaar, darcs, git and mercurial: https://github.com/mndrix/merge-this

It seems only darcs successfully merge the "adjacent lines" case.

Applying adjacent changes to files is not a difficult problem. I really think this behavior has been chosen on-purpose.

Why would someone decide that modifying adjacent lines produces a conflict?

I would think this is to force you to look at it.

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

Modif number 1, on master:

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

Modif number 2, merged from a branch:

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

After merge, you don't want that:

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);

Seeing this behavior as a feature

You can turn the git merging behavior to an advantage. When you need to keep 2 lines consistent but you can't detect it (at compilation time, early in your tests or else), you can try to join them.

Rewrite this...:

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

...to this:

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

So when you merge 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

... with 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 will produce a conflict, and you will force you to look at it.

  • 2
    I'm just going to go ahead and say that I think your answer is perfectly reasonable, but depending on intricate, implementation defined interplay between your code and your source control for sanity checking is a fast track to thedailywtf.com. Blindly merging code without a language parser is ALWAYS best effort anyway, and I've had a few instances where git helpfully automerged something it shouldn't have and produced code that wouldn't even compile. – Wug Nov 9 '17 at 6:00
5

I'm mostly guessing, but I think it has to do with line 2 being used as context for the line 3 change.

Git can't just say that "The line with C became a line with C1" because there could be another line with "C", so it says "The line with C, that's right after the start of the file, the line with A, and the line with B, is now C1"

If "the line with B" is no longer there, then some of the context is lost, and git can only tell roughly where the new line has to go.

  • 4
    its also very likely that C depends on B, so a naive merge may be troublesome even if git "knows how" to do it – Lucina Apr 13 '13 at 12:53
0

The other answers here are all on point, but to me this always seemed like an unnecessary limitation.

As others have said, in these cases you definitely wouldn't want Git to merge the lines without warning.

But I still wanted the option to do it automatically, after being warned. So I wrote a custom git merge driver that could merge conflicts on adjacent (or individual) lines interactively:

enter image description here

It saves me a huge amount of time, as I manage a project where people are often working on the same files and refactoring lots of code.

The script is available on GitHub under a GPLv3+ license. Maybe you'll find it useful:

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

  • 3
    Would someone mind explaining why this was downvoted? I'm fairly new here, so if I've done something wrong I'd like to know what it was so I can avoid it in future. I realise my post doesn't exactly answer the question as asked, but it's still relevant and I think most people who come here would want to know not just why git does this but also what they can do about it (as I did when I first reached this question from a Google search). – deltacrux Sep 12 '18 at 2:13

protected by gnat Sep 11 '18 at 6:29

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.