[Note: edited to add diff3
, re the latest edit to the question; see bottom-most section]
The cherry-pick
command takes, as its arguments, a list of commit IDs:
git cherry-pick [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...
But you say you are using:
git cherry-pick branch-name commit-hash
That means you're asking git to pick two cherries: the one associated with the tip of the named branch (the SHA-1 ID to which branch-name
resolves), and the one associated with the given commit-hash
.
To put it another way, run this first:
git rev-list --no-walk branch-name commit-hash
The output from rev-list
is what cherry-pick
"sees", in effect.
git rebase
does something rather more extensive than git cherry-pick
. Typically, people run this to move a "branch arm" (this is a term I just made up) from one point to another:
A - B - C - D - E <-- origin/master
\
K - L <-- local
This kind of situation arises all the time when you make a local branch to do some work (commits K
and L
on local
), and meanwhile someone else does their own work (commits D
and E
on master) and you decide you want to "re-base your local
branch" on top of their work, so you do something like:
git fetch
(then you look around and see commits D
and E
, so you want to bring your local master
up to date):
git checkout master
git merge --ff-only origin/master
(local master now up to date, now you decide to rebase branch local
):
git checkout local
git rebase master
Rebase is basically an automated "repeatedly cherry-pick until done, then change branch labels" operation. Given the commit graph I drew above, it starts by doing a detached-HEAD
git checkout
to get to commit E
, then it does a git cherry-pick
of commit K
, repeats for commit L
, and (if all goes well) finally adjusts the local branch label local
to point to the last of the new cherry-picked commits:
[start]
A - B - C - D - E <-- master, origin/master
\
K - L <-- HEAD=local
[step 1, get HEAD
set directly to E
and then cherry-pick a copy of K
, giving:]
A - B - C - D - E <-- master, origin/master
\ \
\ K' <-- HEAD [detached]
\
K - L <-- local
[step 2, cherry-pick a copy of L
, giving:]
A - B - C - D - E <-- master, origin/master
\ \
\ K'-L' <-- HEAD [detached]
\
K - L <-- local
[done with cherry pick sequence, move label, giving:]
A - B - C - D - E <-- master, origin/master
\ \
\ K'-L' <-- HEAD=local
\
K - L <-- [no label, abandoned]
The repeated cherry-pick sequence will stop (with a merge conflict) if needed, but if so, it leaves files in the .git
directory to remember what it was doing and how far along it had gotten, so that you can run git rebase --continue
after you fix the problem.
Essentially, the two big differences between cherry-pick
and rebase
are:
cherry-pick
uses--no-walk
when askinggit rev-list
for the commits to pick up;rebase
walks commits (thoughrebase
shuffles arguments around so you can't just rungit rev-list
to see them in advance this way).cherry-pick
never moves branch labels;rebase
does when done.
The new example in the question for illustrating the diff issue is slightly broken: when you git checkout HEAD~3
and then try to git cherry-pick HEAD
, HEAD
will refer to the commit whose ID is master~3
at that point. But cherry-pick master
will do the trick. Here's an example where I set merge.conflictstyle
to diff3
so that you can see the specifics of what cherry-pick
is applying:
$ echo 'line 1' > file && git add file && git commit -m 'add file with 1 line'
[master 72f46c5] add file with 1 line
1 file changed, 1 insertion(+)
create mode 100644 file
$ echo 'line 2' >> file && git add file && git commit -m 'add line 2'
[master 134cdd1] add line 2
1 file changed, 1 insertion(+)
$ echo 'line 3' >> file && git add file && git commit -m 'add line 3'
[master e8634fc] add line 3
1 file changed, 1 insertion(+)
$ echo 'line 4' >> file && git add file && git commit -m 'add line 4'
[master 50810b6] add line 4
1 file changed, 1 insertion(+)
$ git checkout master~3
...
HEAD is now at 72f46c5... add file with 1 line
$ git config merge.conflictstyle diff3
$ git cherry-pick master
error: could not apply 50810b6... add line 4
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
$ cat file
line 1
<<<<<<< HEAD
||||||| parent of 50810b6... add line 4
line 2
line 3
=======
line 2
line 3
line 4
>>>>>>> 50810b6... add line 4
Some like the diff3
style enough to git config --global
(I'm still considering this myself :-) ).