They are not the same, and this can get complicated by the --fork-point
option as well. I think this may be what bit you, although it's not possible to be sure, just from what you have described, as one of the steps you outlined merely produces an error.
I start with a reasonable guess, but it is a guess
To see what's really happening it is very helpful to draw (part of) the commit graph, with special attention to labeling since you are using multiple names that all point to a single commit.
Let's assume the current branch is FeatureABC
it is perfectly in sync with the remote branch.
Hence we have something like this—but something like is not really good enough; you have the repository, so you should draw the graph; I have to guess:
...--o--A--B--C--D--E <-- FeatureABC (HEAD), origin/FeatureABC
Now you run:
#---create two identical branches, behind current branch by 5 commits
(FeatureABC) git branch Demo1-Rebase-ABC HEAD~4
(FeatureABC) git branch Demo2-Rebase-onto-ABC HEAD~4
Since HEAD~4
names commit A
(HEAD~1
is D
, HEAD~2
is C
, and so on), we need to do something to mark the fact that these two new names point to commit A
. I'm going to shorten the names to just Demo1
and Demo2
though. (I've created a repository with only commits o
through E
at this point, and actually run git branch Demo1 HEAD~4; git branch Demo2 HEAD~4
here.)
...--o--A <-- Demo1, Demo2
B--C--D--E <-- FeatureABC (HEAD), origin/FeatureABC
Incidentally, git log --all --decorate --oneline --graph
("get help from A DOG" as someone put it) shows this test repository this way (there is no origin/
branch in my case):
* c4a0671 (HEAD -> master) E
* a7b8ae4 D
* 3deea72 C
* b11828d B
* ffc29b5 (Demo2, Demo1) A
* 3309a8d initial
Next, you check out Demo1, moving HEAD
:
git checkout Demo1-Rebase-ABC
...--o--A <-- Demo1 (HEAD), Demo2
B--C--D--E <-- FeatureABC, origin/FeatureABC
and modify the work-tree, add the modified file to the index, and commit, to make a new commit which I will call F
, which updates the HEAD
branch and therefore separates Demo1
and Demo2
. I'll now use my own commands and their output here:
$ git checkout Demo1
Switched to branch 'Demo1'
$ echo demo1 > demo1.txt && git add demo1.txt && git commit -m F
[Demo1 89773b6] F
1 file changed, 1 insertion(+)
create mode 100644 demo1.txt
Drawing the graph gets a bit harder; I'll use a row up:
F <-- Demo1 (HEAD)
/
...--o--A <-- Demo2
B--C--D--E <-- FeatureABC, origin/FeatureABC
Now we get to your first git rebase
command. I have to use master
, of course:
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: F
This works on the current branch (HEAD
or Demo1
). It finds commits that are on HEAD
that are not on FeatureABC
(FeatureABC..
in gitrevisions syntax). That's commit F
. These commits get put into a list of commits to maybe copy—git rebase
will check for commits with the same git patch-id
and skip them, although clearly that did not happen here. So now commit F
is copied to new commit F'
, with different hash ID and different base:
F [abandoned]
/
...--o--A <-- Demo2
B--C--D--E <-- FeatureABC, origin/FeatureABC
F' <-- Demo1 (HEAD)
(Here's the actual git log
output, showing the new commit hash for the copy. The original, now-abandoned F
is not shown unless I add Demo1@{1}
to the command, which I did here. The original is the second F
shown, i.e., the earlier commit:
$ git log --all --decorate --oneline --graph Demo1@{1}
* c1d0896 (HEAD -> Demo1) F
* c4a0671 (master) E
* a7b8ae4 D
* 3deea72 C
* b11828d B
| * 89773b6 F
|/
* ffc29b5 (Demo2) A
* 3309a8d initial
I like the horizontal graph better, but this one has more information, specifically the abbreviated hash IDs.)
Reproducer fails, and I'll have to guess again
Now we try to repeat this with Demo2
, but it fails. Here are my actual commands, cut-and-pasted. The first step works fine:
$ git checkout Demo2
Switched to branch 'Demo2'
$ echo demo2 > demo2.txt && git add demo2.txt && git commit -m G
[Demo2 ae30665] G
1 file changed, 1 insertion(+)
create mode 100644 demo2.txt
No longer drawing the original F
, here is the new graph. I put G
where F
used to be, although I could draw this as just ...--o--A--G
:
G <-- Demo2 (HEAD)
/
...--o--A
B--C--D--E <-- FeatureABC, origin/FeatureABC
F <-- Demo1
The rebase, however, does not work. Again I have to use master
instead of FeatureABC
, but this would behave the same way in your example, given that the git branch
command did not set an upstream ("tracking") name:
$ git rebase --onto master
There is no tracking information for the current branch.
Please specify which branch you want to rebase against.
See git-rebase(1) for details.
git rebase <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream-to=<remote>/<branch> Demo2
The reason git rebase
failed with this error message is that --onto
has absorbed the argument as <newtarget>
, leaving us with no <upstream>
:
If <upstream>
is not specified, the upstream configured in branch.<name>.remote
and branch.<name>.merge
options will be used (see git-config(1) for details) and the --fork-point
option is assumed. If you are currently not on any branch or if the current branch does not have a configured upstream, the rebase will abort.
The boldface here is mine, but it's also, I think, the key. I assume you ran a git rebase --onto <somename>
that did not fail. For it to have not-failed, your branch must have had an upstream set. That upstream probably was origin/FeatureABC
or similar, and that meant that as far as Git was concerned, you were running:
git rebase --onto FeatureABC --fork-point origin/FeatureABC
and not:
git rebase --onto FeatureABC --no-fork-point origin/FeatureABC
Some further reading in the (overly cryptic, in my opinion) git rebase
documentation will turn up this sentence:
If either <upstream>
or --root
is given on the command line, then
the default is --no-fork-point
, otherwise the default is
--fork-point
.
In other words:
git rebase FeatureABC
turns off the --fork-point
option, as does:
git rebase --onto FeatureABC FeatureABC
but:
git rebase
or:
git rebase --onto FeatureABC
leaves the --fork-point
option on.
What --fork-point
is about
The goal of --fork-point
is to specifically drop commits that used to be, at one time, in your upstream, but are no longer in your upstream. See Git rebase - commit select in fork-point mode for an example. The specific mechanism is complicated and relies on the upstream branch's reflog. Since I don't have either your repository or your reflog, I cannot test out your specific case—but that's one reason, and probably the most likely reason given the hints in your question, that a commit that would affect the rebase tree result would get dropped. The commits that are dropped due to having the same patch ID as an upstream commit are ones that [edit:] often1 will not affect the final tree of the last-copied commit: they would just cause merge conflicts and/or force you to use git rebase --skip
to skip over them, if they were included.
1It occurred to me after writing this that there is an important exception (which probably has nothing to do with the original question, but which I should mention). Rebasing a feature or topic branch onto a more mainline branch, when a commit was first cherry-picked out of the feature into the mainline, and then reverted in the mainline, will cause a problem. Consider, e.g.:
...--o--*--P--Q--C'-R--S--X--T <-- mainline
A--B--C--D--E <-- topic
where C'
is a copy of commit C
, and X
is a revert of commit C
that should not have been put into mainline
yet. Doing:
git checkout topic
git rebase mainline
will instruct Git to put commits A
through E
into the "candidates to copy" list, but also look at P
through T
to see if any were already adopted. Commit C
was adopted, as C'
. If C
and C'
have the same patch ID—usually, they will—Git will drop C
from the list as "already copied". However, C
was explicitly reverted in commit X
.
Whoever does the rebase needs to notice, and carefully restore C
if it is required and appropriate.
This particular behavior is not a problem with git merge
(since merge ignores intermediate commits), only with