2

Look, I make a modification in a branch and then pick a commit from a similar branch, which does not have this modification. I wonder if modification must be rolled back or not.

Initially, both A and B branches have a full copy of the same file

begin
 123
 456
 def f
 789
 klm
end

But they diverge. First, A moves def f into the end of file, producing refactored A

begin
 123
 456
 789
 klm
end
def f

Now, if we cherry-pick B on top of this A, the original file is recovered (def f is back in the middle of the file). This is fine because I stated to ask this question once I was informed that cherry-pick with -theirs produces an overriding alternative to cherry-pick. B is 'their' version of the file and it is what I expected because we see that B wins indeed: the only difference between A and B is in the place of A-refactoring and B version is preferred in this case.

I however started to ask this question because it is not always the case. If we add a bit of change to B, e.g rewrite the first line of the procedure for instance, 123 to 222 (I label this new version of B C in the bash code below) what will be the result of picking this C into A do you think? The result of picking A <- C is puzzling

begin
 222
 456
 789
 klm
end
def f

You see, the first line is 222 from C but def f is also in the end, which means that refactoring of A has preserved and C did not override it. That is a mystery of inconsistent behaviour IMO. You think that B is different from A by the whole file but it is not, once you further modify a little bit. The unrelated change stop the rollback or I just cannot figure out the git rules out. Which changes should I expect in the cherry-pick op?

I think that it is related situation where picking B tells that whole file has changed whereas if you pick modified C, diff proceeds per normal detecting only single line change.

You can reconstruct the situation using

mkdir preserving ; cd preserving
git init ; echo rrr > root
git add root ; git commit -m root

git checkout -b B ; git checkout -b A

function makeABC {
    echo begin > abc
    echo " 123" >> abc
    echo " 456" >> abc
    echo " def f" >> abc
    echo " 789" >> abc
    echo " klm" >> abc
    echo end >> abc
}

echo commiting ABC into branch A
makeABC ; git add abc ; git commit -m abc

echo refactoring A, def f moved into the end of file
git checkout A
sed -i -e '/def f/d' abc
echo "def f" >> abc
git add abc ; git commit -m "refactoring 'def f'"

echo posting abc into B
git checkout B ; makeABC ; git add abc ; git commit -m "abc in B"

echo choosing which branch to pick
picking="B" ; case $picking in
    "B") ;;
    "C") git checkout -b C ; sed -i -e 's/123/CCC/g' abc
        git add abc ; git commit -m CCC ;;
esac

git checkout A ; git cherry-pick $picking -Xtheirs 

echo observe if refactoring def f is in place in A
gitk --all & 

echo 'preserving' folder created

Set value of picking variable to "B" or "C" to choose the branch that you want to pick upon A.

Community
  • 1
  • 1
  • I clone your repository and a little do not understand what the point is? What changes did you lost, from which commit? Cherry-pick in git is very powerful command but unfortunately creates new hash for changes. – gauee Dec 09 '15 at 20:37
  • I did not loose anything. I want to understand it logic. Can you explain exactly what is asked: why the piece of code called `activate` is in the refactored position after I overwrite it with unrefactored commit? – Valentin Tihomirov Dec 09 '15 at 20:39
  • It looks strange, I am guessing that You cherry-pick commit 802cfb6 from branch sf2 into branch typedfields (these commits has the same message) But additionally in this cherry-pick commit 858677d is added new file .gitignore so I had to amend this commit. How exactly your cherry-pick command looked like? – gauee Dec 09 '15 at 20:59
  • @gauee I exposed commands in the [readme‌​](https://github.com/valtih1978/git-cherry-pick-override-bug/blob/master/README.md). I have fixed the commands. Now you can clone the repo and reach the repo state that bothers me automatically. `Typefields` was indeed picked fromsing `single_file` branch, taking its first commit. I have applied the refactoring to ProxyDB during this operation. Do you think that it is the culprit? – Valentin Tihomirov Dec 09 '15 at 21:23
  • I verified that is different amount of conflicts in this two cherry-pick process. But I do not know why... I am guessing that it is connected with 3-way merge algorithm, but i will verify it. – gauee Dec 09 '15 at 22:23
  • @gauee My discoveries suggest that we should to blame diff. It says that whole file has changed if we pick B but only appropriate line if we pick C = modified B. – Valentin Tihomirov Dec 10 '15 at 07:28
  • It looks like a different comparision when both files are marked as new or one of them is marked as modified. Thats interesting. – gauee Dec 10 '15 at 07:47

2 Answers2

2

This is because the -Xtheirs part of git cherry-pick -Xtheirs only says what to do with conflicting changes, and there are no conflicting changes between the two commits in your last example. The merge strategy is recursive (the default in this case), and theirs is an option to this strategy.

If there was a strategy called theirs, then you would have gotten the result which you expected.

Now, there is no theirs strategy, but there is an ours strategy that can demonstrate the point. Instead of git cherry-pick -Xtheirs C, try git cherry-pick --strategy=ours C and see what happens. It will effectively ignore the changes from C and say that there is nothing to commit. This strategy is of course useless when cherry-picking, but it may help to understand.

To achieve what you really wanted in the first place, here is one way to do it:

git checkout A
git read-tree -m -u C
git commit --no-edit -c C

More information about merge strategies can be found in man git-merge.

jsageryd
  • 4,234
  • 21
  • 34
1

From what I got on freenode#git, people say that in contrast to rebase, picking works with updates done by picked commit alone, it does not look at cumulative changes since the common root. This means that since B has introduced abc file, file will be re-introduced when picked on top of A and, thus overwriting the whole file. C however introduces only a small change in the file and therefore will leave A modification intact.