1
Commit Graph -> Going Forwards in time -->

  A 
 / \
O   M
 \ /
  B 

O = Original commit
A = Commit on Branch 1 (main)
B = Commit on Branch 2
M = Merge Commit 

MyFile (Commit = O)
======
line1
line2
line3


MyFile (Commit = A ... only modifies line1)
======
line1 - commit A modification
line2
line3


MyFile (Commit = B ... only modifies line2)
======
line1
line2 - commit B modification
line3

Why does this result in a merge conflict - when these changes are not overlapping?

In stark contrast however, in the very similar scenario - where we modify line3 instead of line2 in commit B... we then DON'T have a merge conflict.

Does anybody have a clear explanation what git is doing to identify the first case as a merge conflict? Neither have overlapping changes.

The best explanation of how git calculates merges was the answer from this post, but it does not help explain my first case's conflict.

matt
  • 49
  • 7
  • 2
    You get a merge conflict if there are no unmodified lines between the changes. So if the changes are adjacent or overlap -- not just if they overlap. – Chris Dodd Dec 30 '21 at 22:51
  • Oh for reals? Well I guess that answers that then. Thanks @ChrisDodd for your quick response. If I could follow up with ... (A) Does this make sense to you why that would be the case (can the algorithm not figure out that they are seperate I guess?) and (B) I don't suppose you know of any good / clear guides / articles that cover this point / general topic of git merge logic? Thanks (PS - do you want to post your comment as an anwer so I can mark it as such) – matt Dec 30 '21 at 22:55
  • 2
    The reason that changes in neighboring lines are a conflict is that the authors of the merge algorithm find this to be a case that is worth bringing to the attention of the writer. It is as simple as that. – j6t Dec 30 '21 at 23:50
  • @j6t - Thinking through what the algorithm could possibly figure out, its more than just worth bring to the attention of the writer - it is physically unable to tell what order the new lines should go in (if there are no seperating unmodified lines). There is effectively an overlap of new lines from each commit diff. – matt Jan 01 '22 at 23:37

1 Answers1

0

Summary of answer = "You get a merge conflict if there are no unmodified lines between the changes" - (@Chris Dodd)

More detailed reason why this is the case = I have rationalised the reason for this logic to myself, and I will try and convey this below (probably could do so more concisely, but here goes) ...

Although it seems the two separate "modified / replaced" lines are not overlapping - actually, due to there being no unmodified lines seperating these "new lines", the order Git should place them in is in fact indeterminable (ambiguous). The ambiguity comes from the second possible intention Git considers (that the OP (I) did not) which reveals an overlap. Had there been a seperating unmodified line to clearly mark a sepration between the two changes there would not be any such overlap...

Possible Intention 1

(the "replacement of lines" intention - the OP (I) thought this was the only way Git could interpret the commits / merge)

The two new lines (one per commit) are intended as replacement lines for their respective deleted lines. This intention does indeed have a determinable / unambiguous merge outcome - so considering this intention - there would be no conflict ...

The DIFFs to reflect this intention ...

MyFile - Commit A (DIFF from base)
---------------------------------
- item1
+ new line A (item1 replacement)
  item2


MyFile - Commit B (DIFF from base)
---------------------------------
  item1
- item2
+ new line B (item2 replacement)

(The above could seem possibly not conflicting)

Possible Intention 2 (other intention)

(the "addition and deletion" seperately - i.e. no intended direct replacements)

The commitA intends for the "new line" simply to be added after the line "item1" - and then the line "item1" happens to be deleted (ie the "new line" is not intended as line "item1" replacement). The commitB intends for it's "new line" to be added before the line "item2" - and then the line "item2" deleted. This intention results in the two (different) "new lines" (one per commit) sitting side by side ... Git does not know which to place first, thus Git see's a conflict.

The two DIFFs (below) although Commit B's "new line" placement is shuffled to above its "item2" deletion - it is still the equivilant DIFF (as presented before (above in Intention 1), but perhaps now better shows where the conflict comes from. We can now clearly see the new lines overlap, thus order is ambiguous.

MyFile - Commit A (DIFF from base)
---------------------------------
- item1
+ new line A (placed after item1)
  item2

MyFile - Commit B (DIFF from base)
---------------------------------
  item1
+ new line B (placed before item2)
- item2

TAKEAWAYS -

(1) Just because a line addition (+) follows a line deletion (-)... Git does not assume they are intimately connected (bound as a replacement pair). They are not. Their love means nothing. Is not real. Just a figment of the users imagination.

(2) To reinforce the above - and to help further this understanding, I affirm the following law (I just created, but pretty sure is valid - please correct me if I'm wrong).

MY LAW:

DIFFs can be shuffled in the following ways (and still retain the same meaning, validity, equivilance)

First, some definitions:

"An island of changes": A group of (+) or (-) line changes, which are not seperated by other unmodified lines.

The Law:

Within a DIFF, within an island of changes - we can shuffle the changes in any order - so long as the (+) changes are presented in the correct / same order.

Shuffling the DIFF as such will retain the exact same meaning and continue to be a valid and equivilant DIFF.

EXAMPLES of One DIFF - with equivilent but different orders

This order reflects the order in which I actually made some hyperthetical changes ...

  item1
+ new line A    <- I
- item2         <- S
+ new line B    <- L
- item3         <- A
+ new item C    <- N
+ new item D    <- D
  item4

This is the order Git seems to present the changes - it groups the (-) changes first and then the (+) changes second ...

  item1
- item2         <- I
- item3         <- S
+ new line A    <- L
+ new line B    <- A
+ new item C    <- N
+ new item D    <- D
  item4

This order groups the (+) first and then the (-) second ...

  item1
+ new line A    <- I
+ new line B    <- S
+ new item C    <- L
+ new item D    <- A
- item2         <- N
- item3         <- D
  item4

This order mixes (-) into (+) - but preserves order of each type ...

  item1
+ new line A    <- I
+ new line B    <- S
- item2         <- L
+ new item C    <- A
- item3         <- N
+ new item D    <- D
  item4

This order mixes (-) into (+) AND furthermore the order of (-) is different, but this is still valid and equivilant, as it is irrelevant which order lines disappear, so long as they disappear, however the order of (+) changes MUST be preserved (as this is still very relevant) ...

  item1
+ new line A    <- I
+ new line B    <- S
- item3         <- L
+ new item C    <- A
- item2         <- N
+ new item D    <- D
  item4
matt
  • 49
  • 7