Max Koretskyi
Max Koretskyi

Reputation: 105517

Does cherry-pick apply changes or try to merge the contents of the entire file?

I'm reading this article about cherry-picking and there is the following picture there:

enter image description here

However, this picture seems misleading to me. From my simple test, it appears that, not just the difference, but the entire file content is merged. Here is the experiment: I have this commit graph:

A--B
 \
  C

Commit A had this content in file.txt:

l1
l2
l3

Commit B had small change:

l1
l2-new
l3

Commit C had small change:

l1
l2
l3-new

So now I'm trying to replay commit B on the commit C using cherry-pick,

git cherry-pick B

and I get a conflict,

<<<<<<< HEAD
l2
l3-new
=======
l2-3
l3
>>>>>>> C

which should not be there if only changes were applied. Since I didn't touch l2 line in my C commit, it should have applied smoothly. Am I right?

Upvotes: 1

Views: 332

Answers (3)

axiac
axiac

Reputation: 72256

Git stores files, but it generates and uses diffs in a lot of places.

A diff records the state of the affected lines before and after the commit; it also stores one line of text from before and after the modified area. Git uses this information to avoid data loss and inconsistencies.

Let's say we created branches named A, B and C on the commits having these names in your description.

$ git diff A B -- file.txt
diff --git a/file.txt b/file.txt
index f0f2307..0acba21 100644
--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
 l1
-l2
+l2-new
 l3

I asked Git to display the changes on file file.txt between commits A and B.

The format is a variation of the uniffied diff format and it is explained on the Wikipedia page about the diff utility.

In simple words, this diff says: change line 2 of the file from l2 to l2-new but only if line 1 is l1 and line 3 is l3. Even that only line 2 of the file was changed, Git includes also the lines 1 and line 3 of the file in the diff. They are the context of the change that happened on line 2.

Remark: In this case the file is small and lines 1-3 represent the entire file. Git doesn't use the entire file, it only guards any block of lines that changed with 1 line before the change and 1 line after the change. If, for example, the file has 20 lines and we change lines 12 and 13, the diff contains lines 11-14.

Back to what our diff, on commit C the file looks like:

l1
l2
l3-new

but in order to apply the diff, Git expects it to look like:

l1
l2
l3

Because its expectations are not met, Git cannot apply the diff safely and decides this is a conflict.

Why does Git need the context lines?

Let's say on commit C we deleted the second line. The file will look like:

l1
l3

Then we cherry-pick commit B, and apply the changes it introduced (change the line 2 from l2 to l2-new) without verifying the context. The file will look now like:

l1
l2-new

Wait a moment! Where is l3?
I didn't delete the line l3 on commit C and I didn't touch it on commit B either. Applying a diff without checking the context, can lead to data loss. Git always checks the context when it applies a diff and I suppose all the programs that work with diffs do the same.

Upvotes: 5

John Szakmeister
John Szakmeister

Reputation: 47062

You end up with a conflict because the changes are too close together (it terms of line number). The context (generally the 3 lines surrounding the actual diff hunk) need to match, otherwise Git (and other version control systems) will flag the change as a conflict. Since part of the context changed in your example, it was flagged as a conflict.

Upvotes: 2

Andrew_Lvov
Andrew_Lvov

Reputation: 4668

Git is storing files, not diffs, so mb since lines are close, git wasn't able to resolve it correctly.

Upvotes: 0

Related Questions