To explain what happened, try this as an experiment:
$ git checkout -b exp1 master
<modify some file; git add; all the usual stuff here>
$ git commit -m commit-on-exp1
At this point you have an experimental branch named exp1
with one commit that's not on master
:
...--A--B <-- master
\
C1 <-- exp1
Now we'll make an exp2
branch pointing to commit B
, and copy commit C1
to a new commit C2
on branch exp2
:
$ git checkout -b exp2 master
$ git cherry-pick exp1
The result is:
C2 <-- exp2
/
...--A--B <-- master
\
C1 <-- exp1
Now let's repeat with exp3
, creating it so that it points to commit B
and then copying exp1
again:
$ git checkout -b exp3 master
$ git cherry-pick exp1
Do you expect exp3
to point to commit C2
? If so, why? Why did exp2
point to C2
rather than to C1
like exp1
?
The issue here is that commits C1
and C2
(and now C3
on exp3
) are not bit-for-bit identical. It's true they have the same snapshot, the same author, the same log message, and even the same parent (all three have B
as their one parent). But all three have different committer date-and-time-stamps, so they are different commits. (Use git show --pretty=fuller
to show both date-and-time-stamps. Cherry-pick, and hence rebase too, copies the original author information including date-and-time, but because it's a new commit, uses the current date-and-time for the committer timestamp.)
When you use git rebase
, in general, you have Git copy commits, as if by cherry-pick. At the end of the copying, Git then moves the branch name so that it points to the last copied commit:
...--A--B <-- mainline
\
C--D--E <-- sidebranch
becomes:
C'-D'-E' <-- sidebranch
/
...--A--B <-- mainline
\
C--D--E
Here C'
is the copy of C
that's changed to use B
as its parent (and perhaps has a different source snapshot than C
), D'
is the copy of D
, and E'
is the copy of E
. There was only one name pointing to E
; that name is now moved, so there is no name pointing to E
.
But if you have two names pointing to E
originally, one of those two names still points to E
:
C'-D'-E' <-- sidebranch
/
...--A--B <-- mainline
\
C--D--E <-- other-side-branch
If you ask Git to copy C-D-E
again, it does that—but the new copies are not C'-D'-E'
because they have new date-and-time stamps. So you end up with what you saw.
Hence, if you want to move two or more names while copying some chain of commits, you'll be OK using git rebase
to move the first name, but you will have to do something else—such as run git branch -f
—to move the remaining names, so that they point to the commit copies made during the one rebase.
(I've always wanted to have a fancier version of git rebase
that can do this automatically, but it's clearly a hard problem in general.)