This would be easier to answer with a concrete example. It's simple enough in principle though: git rebase
means: Copy commits, as if by git cherry-pick
. It's the copy step that is failing here.
We start on branch R (for rebase) and pick some new target branch T (for Target). We then identify some set of commits to copy: these are, for the most part, commits that are on R but not on T. (In a bit of a white lie, the git rebase
documentation suggests that these are the commits listed by git log T..R
or git log T..HEAD
. That's mostly true, except that there are three extra fiddles added, or maybe "subtracted" would be the word.)
Then, having listed the commit hashes to copy, Git does a detached-HEAD checkout of the target branch, so that the current (HEAD
) commit is the same commit that branch T identifies. Note that we're not on branch T, we're just on the same commit. Now, for each commit whose hash ID we saved, Git effectively (and sometimes literally) runs git cherry-pick <hash-id>
:
....... HEAD
v
...--o--o--o--o--o--o <-- T
\
A--B--C <-- R
We run git cherry-pick A
to copy commit A
. If we call the new copy A'
, the result looks like this:
A' <-- HEAD
/
...--o--o--o--o--o--o <-- T
\
A--B--C <-- R
Then we run git cherry-pick B
to copy B
to B'
:
A'-B' <-- HEAD
/
...--o--o--o--o--o--o <-- T
\
A--B--C <-- R
We repeat until we have all the commits copied, and then as the last step, Git "peels the branch label" R
off the original chain and pastes it at the end of the copied chain:
A'-B'-C' <-- R (HEAD)
/
...--o--o--o--o--o--o <-- T
\
A--B--C <-- (only in reflogs and ORIG_HEAD)
Conflicts and other problems
Each copy—each cherry-pick step—is done using Git's merge machinery, or what I call the "verb form" of to merge (as opposed to the noun or adjective form, a merge or a merge commit—git merge
normally makes a merge commit after doing the verb form to merge, and we are just using the first half of this). You get, e.g., an add/add conflict when you merge a commit that adds a new file with a change that also adds the same new file.
The most likely place to get such a conflict is when making copy A'
from A
, because the merge base of a cherry-pick operation is the parent commit of the commit being copied. A merge operation—the to merge verb—compares this merge base with two commits. In this case, the two commits are commit A
itself, and the tip-most commit of branch T, i.e., the commit to which T points. Let's call this X
, and mark the merge base with *
:
? [merge in progress] HEAD
/
...--o--o--*--o--o--X <-- T
\
A--B--C <-- R
If both commits A
and X
, compared to *
, add a file path/to/file.txt
, you get an add/add conflict. The cherry-pick stops, leaving a merge conflict in progress. You must solve it and tell rebase to resume.
What if A
adds path/to/file.txt
and it's not in commit X
? Normally, that would be no problem: Git would just create the file and put it in the new commit. There would be no add/add conflict at all, just a perfectly good copy A'
(and we would go on and do the rest of the rebase).
But perhaps file path/to/file.txt
exists in the work-tree for some reason (e.g., as an untracked file, and perhaps ignored as well). In this case, Git can't just copy path/to/file.txt
out of commit A
, overwriting the work-tree version. You get an error message here, just as for a regular merge conflict. You must solve the problem and tell rebase to resume.
There are some additional cases here for files that are in the index/staging-area (and hence can be in various commits) but are marked --assume-unchanged
or --skip-worktree
. In newer versions of Git, the precise error message identifies whether the file that "would be overwritten" is truly untracked, or marked like this. (This might be true in older versions of Git as well but I do not remember off hand and have not checked.) This is why a concrete example is better: there are several different causes for this particular problem.
For completeness: the subtractions I mentioned
These are also covered (somewhat) in the rebase documentation, but it's worth mentioning the commits git rebase
deliberately doesn't copy:
It cannot copy any merge. When using --preserve
, git rebase
will re-perform merges to try to reconstruct them, but it cannot copy the original merges. So normally it does not even try.
It omits merges that have an upstream equivalent (e.g., those that have already been cherry-picked). In the original diagram (with T
and R
), Git checks to see if the git patch-id
value for A
, B
, or C
match the git patch-id
value for any of the commits on T
to the right of the merge base commit. If so, Git drops the A
, B
, and/or C
commits whose patch ID already occurs upstream.
If used with --fork-point
, the rebase code runs git merge-base --fork-point
to attempt to find upstream commits that were deliberately dropped. This usually works pretty well with remote-tracking branches, and usually works less well if you set your own local branches as the upstreams for your own branches. Using --fork-point
is the default when rebasing on the upstream using the automatic mechanisms, so one must be careful here. See Git rebase - commit select in fork-point mode for more.