43

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?

7
  • Hey I just noticed this last week too. Maybe we were doing the same tutorial.
    – detly
    Commented Apr 12, 2013 at 22:56
  • 6
    git's merging abilities are actually quite poor, IMO
    – James
    Commented Apr 13, 2013 at 13:40
  • 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
    Commented Sep 9, 2013 at 13:30
  • 1
    The only common context is A and D, so why isn't A/C1/B1/D the correct merge?
    – Izkata
    Commented Mar 4, 2016 at 16:49
  • 3
    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. Commented Mar 31, 2016 at 18:50

4 Answers 4

17

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.

1
  • 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
    Commented Nov 9, 2017 at 6: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.

5
  • 16
    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
    Commented Sep 11, 2018 at 14:24
  • 6
    Yeah I don't get this answer. You could use the same logic for avoiding all automatic merges.
    – user541686
    Commented Aug 15, 2019 at 1:14
  • 2
    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
    Commented Aug 21, 2019 at 8:42
  • The logic here makes sense. Anyone happen to know if this behavior is mentioned anywhere in git documentation, and if so where?
    – dtmland
    Commented May 8, 2020 at 0:37
  • "then I would not want git to merge it into this" Why not? Looks fine to me.
    – endolith
    Commented Sep 15, 2021 at 21:49
12

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

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
    Commented Sep 12, 2018 at 2: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
    Commented Aug 15, 2019 at 1: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
    Commented Aug 21, 2019 at 4:11
  • 1
    "why this was downvoted" Curmudgeons gonna curmudge.
    – Steven Lu
    Commented Dec 8, 2021 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.

2
  • 6
    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
    Commented Apr 13, 2013 at 12:53
  • 2
    Believe me Git only "thinks it knows". Git is a graveyard of wrong concepts, trying to be straightened! Commented Oct 24, 2019 at 11:47

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