38

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?

rlandster
  • 939
  • 1
  • 8
  • 14
  • Hey I just noticed this last week too. Maybe we were doing the same tutorial. – detly Apr 12 '13 at 22:56
  • 6
    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 http://blog.wuwon.id.au/2010/09/painless-merge-conflict-resolution-in.html for an example). – deterb May 06 '13 at 18:58
  • 2
    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 09 '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 04 '16 at 16:49
  • 2
    This doesn't actually make logical sense. There is equal chance of two non-adjacent modified lines of the same file causing danger together than it is if the lines were adjacent. So I don't understand why adjacent lines are discriminated as such. – Attila Szeremi Mar 31 '16 at 18:50
  • One usecase of this is merging CSV files. – CMCDragonkai May 17 '16 at 15:39

4 Answers4

15

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.

ofaurax
  • 453
  • 4
  • 8
  • 7
    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 09 '17 at 06:00
13

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.

In order to avoid causing such problems, git usually refuses to automatically merge changes touching nearby lines. It gives you a chance to verify whether the program logic would be broken or not.

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.

Arsen7
  • 255
  • 2
  • 6
  • 15
    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
  • 6
    Yeah I don't get this answer. You could use the same logic for avoiding all automatic merges. – user541686 Aug 15 '19 at 01:14
  • 1
    There must be a line drawn between security and usefullness. Automatic merges bear the risk of corrupting the program flow - as I was trying to show in the answer -, but if git would just refuse to merge anything, it would become useless. The creators of git have just decided on some context, which shall catch most of the "dangerous" cases. Adding some unchanged line (as Darkhogg found out) fools git into believing the merge might be safe to perform. – Arsen7 Aug 21 '19 at 08:42
  • The logic here makes sense. Anyone happen to know if this behavior is mentioned anywhere in git documentation, and if so where? – dtmland May 08 '20 at 00:37
  • "then I would not want git to merge it into this" Why not? Looks fine to me. – endolith Sep 15 '21 at 21:49
11

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

deltacrux
  • 227
  • 2
  • 4
  • 5
    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 02:13
  • 1
    I haven't tried it yet but I came looking for a way to automate this, and was glad to find it here. Thanks :) you're awesome. – user541686 Aug 15 '19 at 01:14
  • 1
    No problem! I hope you find it useful, and please feel free to leave feedback on Github if you run into issues or have any suggestions for improvement. Thanks! – deltacrux Aug 21 '19 at 04:11
  • 1
    "why this was downvoted" Curmudgeons gonna curmudge. – Steven Lu Dec 08 '21 at 23:48
6

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.

Mike Gossmann
  • 868
  • 5
  • 6
  • 5
    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
  • 2
    Believe me Git only "thinks it knows". Git is a graveyard of wrong concepts, trying to be straightened! – user3833732 Oct 24 '19 at 11:47