Reputation: 59
main branch:
lark branch:
after rebase
Strange!, where is the lark_file? Well, there should be three files (init, main_file, lark_file), right?
The script to create the environment.
#!/bin/env bash
git init
touch init
git add -- .
git commit -am init
echo a >> main_file
git add --all
git commit -am 'main a'
echo b >> main_file
git commit -am 'main b'
echo c >> main_file
git commit -am 'main c'
echo d >> main_file
git commit -am 'main d'
echo e >> main_file
git commit -am 'main e'
git checkout HEAD~5
git checkout -B lark
echo a >> lark_file
git add --all
git commit -am 'lark a'
echo b >> lark_file
git commit -am 'lark b'
echo c >> lark_file
git commit -am 'lark c'
echo d >> lark_file
git commit -am 'lark d'
echo e >> lark_file
git commit -am 'lark e'
echo f >> lark_file
git commit -am 'lark f'
Upvotes: 2
Views: 80
Reputation: 490078
TL;DR: because of rebase's rename detection, Git is applying the change to the wrong file. (The --onto
flag is not required to get this effect. You just have to have a diff in which Git produces a false/incorrect rename.)
First, let me modify your reproducer a bit so that everyone can use it:
$ cat repro.sh
#! /bin/sh -e
mkdir t
cd t
git init
touch init
git add -- .
git commit -am init
echo a >> main_file
git add --all
git commit -am 'main a'
echo b >> main_file
git commit -am 'main b'
git tag tag-onto
echo c >> main_file
git commit -am 'main c'
echo d >> main_file
git commit -am 'main d'
echo e >> main_file
git commit -am 'main e'
git checkout HEAD~5
git checkout -B lark
echo a >> lark_file
git add --all
git commit -am 'lark a'
echo b >> lark_file
git commit -am 'lark b'
echo c >> lark_file
git commit -am 'lark c'
echo d >> lark_file
git commit -am 'lark d'
echo e >> lark_file
git commit -am 'lark e'
echo f >> lark_file
git commit -am 'lark f'
git rebase --onto tag-onto HEAD~3
This has a few simple changes: it makes a temporary directory t
and cd
-s into it, so that we can remove the temporary directory when we're done, and so that the reproducer script itself doesn't get stuck in to the repository. More usefully, it changes the last git rebase
to:
git rebase --onto tag-onto HEAD~3
That is, we drop the final HEAD
—which produces a detached HEAD pointlessly—and we use a tag, tag-onto
, as the --onto
target on which we're going to copy the lark d
commit.
Running this script reproduces the issue:
$ ./repro.sh
[much output snipped]
CONFLICT (content): Merge conflict in main_file
error: could not apply 1a3193f... lark d
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 1a3193f... lark d
The setup here is that we're trying to cherry-pick commit lark~2
onto commit tag-onto
(master~3
or main~3
depending on your initial branch).
What we need to realize is that git cherry-pick
is a kind of git merge
. It does a three-way merge, with the merge base being the parent of the commit being cherry-picked. That means it runs two git diff
s, from parent lark~3
to current commit HEAD
, and from parent lark~3
to commit lark~2
. Let's look at the first of these two diffs:
$ git diff lark~3 HEAD
diff --git a/lark_file b/main_file
similarity index 66%
rename from lark_file
rename to main_file
index de98044..422c2b7 100644
--- a/lark_file
+++ b/main_file
@@ -1,3 +1,2 @@
a
b
-c
This says we should rename the file: it should be called main_file
now, not lark_file
.
The second diff of course shows what you added in commit lark d
:
$ git diff lark~3 lark~2
diff --git a/lark_file b/lark_file
index de98044..d68dd40 100644
--- a/lark_file
+++ b/lark_file
@@ -1,3 +1,4 @@
a
b
c
+d
So Git decides that we need to rename lark_file
to main_file
and add d
to the end, at the same time as removing c
from the end of main_file
.
That is indeed what Git has done: we now have one file main_file
instead of two separate files lark_file
and main_file
, and we see the conflict. Mine is in diff3
style rather than merge
style, so it has more information:
$ cat main_file
a
b
<<<<<<< HEAD:main_file
||||||| parent of 1a3193f... lark d:lark_file
c
=======
c
d
>>>>>>> 1a3193f... lark d:lark_file
Git is behaving correctly according to the Git rules.
The fact that the Git merge rules produce surprises now and then is why you must always inspect any merge result. This includes cherry-pick results, which are merge results.
Upvotes: 2