Reputation: 4385
I recently hit unexpected conflicts when running a git cherry-pick
:
git checkout myBranch
git cherry-pick begin..end
I thought "that makes no sense", aborted, and tried what I understand to be the equivalent git rebase --onto
:
git rebase --onto myBranch begin end
Voilà! A clean rebase, no unexpected conflicts.
Am I right to be surprised? Do you think there was user error somewhere, or is there a fundamental difference in the processes underlying rebase --onto
and cherry-pick
that could result in this?
Edit: In the cherry-pick
, begin
and end
were SHAs. In the rebase
, begin
was that same SHA used for the cherry-pick
's begin
, and end
was the name of a branch the tip of which was at the cherry-pick
's end
commit.
Upvotes: 0
Views: 41
Reputation: 487883
TL;DR one line summary: git rebase
omits any already-cherry-picked commits.
What git rebase
copies is not quite what you think. (I admit I am guessing a bit about what you think here ... but if the result was a surprise, this must be true.)
Formally, the arguments are, according to the documentation, [--onto newbase] upstream [branch]
. That is:
git rebase --onto myBranch begin end
means that end
is treated as a branch name:
git checkout end
After that, according to the documentation (but it lies), the commits to be cherry-picked are those produced by git rev-list
or git log
when given upstream..HEAD
:
All changes made by commits in the current branch but that are not in <upstream> are saved to a temporary area. This is the same set of commits that would be shown by
git log <upstream>..HEAD
; or bygit log 'fork_point'..HEAD
, if--fork-point
is active (see the description on--fork-point
below); or bygit log HEAD
, if the--root
option is specified.
Neither of the --fork-point
or --root
options applies, so we are left with:
git log begin..HEAD
Since HEAD
now refers to end
, this would be the same as begin..end
. However, as I just noted, the documentation lies! Well, at least a little bit. It then corrects things to say:
Note that any commits in HEAD which introduce the same textual changes as a commit in HEAD..<upstream> are omitted ...
It should also mention here that git rebase
omits merge commits as well. What we actually need is more complicated:
git rev-list --cherry-pick --right-only --no-merges begin...HEAD
(note three dots instead of two). That is, we examine begin..HEAD
, to find commits to possibly take (--right-only
); but then we look through HEAD..begin
(the three dots in begin...HEAD
) for commits that we could take, but decide not to, as they are already taken (--cherry-pick
). From the resulting list of commits, we also throw out any merge commits1 (--no-merges
).
Given that the two two-dot X..Y
formulations (begin..HEAD
and begin..end
) would produce the same list, and the point at which we would begin copying commits (newBranch
) is also the same, it follows that any change in behavior must be due to the omitting step. If you were to check out the original tip commit of myBranch
and run:
git rev-list ... | git cherry-pick --stdin
(where ...
uses the --cherry-pick --right-only --no-merges
three-dot formulation and specifies the commits that begin
and end
used to identify before all these changes), it should also just work, the same way the git rebase
did.
1Merge commits are not the problem here. To cherry-pick a merge, git cherry-pick
requires that you supply a -m
parameter. But if there are any non-merge commits, git cherry-pick
forbids a -m
parameter. Since there were clearly some non-merges and no complaint regarding -m
, we did not hit a merge commit.
Upvotes: 1