Reputation: 38899
I have two branches: let's call them master
and feature
. I'm now trying to merge recent changes from master into feature. I normally prefer rebases over merges, but these two branches have diverged significantly, so I decided to do a merge instead to have a record of all the conflict resolution. I finished up the merge, and committed it on feature, and thought I was done. Everything looked good.
Now, I made a small change to master and wanted to just rebase that into feature, but git rebase master
now gives me conflicts with older commits that were already dealt with in my last merge. Weirdly, git merge master
gives no conflicts as expected. What gives?
Upvotes: 2
Views: 1095
Reputation: 487725
Your phrasing ("rebase [a small change made to master] into feature") seems a bit weird to me, since normally you'll be rebasing feature
onto master
. (The word "into" doesn't really apply well.) That aside, I think it always helps to draw the commit graph. You can use gitk
or a similar viewer to draw it, or git log --graph
, perhaps with --oneline
as well, to draw a vertically oriented graph. But here I'll draw a horizontal one, with single uppercase letters representing particularly interesting commits, and lowercase o
s representing more-boring commits.
For our purposes here I will leave out any configured upstreams (origin/master
and/or origin/feature
). If they do exist, you might want to add them to your own drawing, and then note that when git rebase
makes copies of commits, it does not move any of the other labels (including these remote-tracking branches) pointing to existing commits: it only moves one single label, that of the current branch.
... - A - o - o - o - F <-- master
\ \
B - C - D - E - G <-- HEAD -> feature
With any luck this is reasonably close to your pre-rebase setup, and accurately reflects the result of your git merge
. Both before and after you did the merge, commit A
is the original base at which feature
split off from master
; commits B
through E
were made on feature
; the various less-interesting o
commits were made on master
; and commit F
was the tip of master. You were on branch feature
(so that HEAD
named branch feature
, and git status
said "on branch feature") and you ran git merge master
and did the merge and committed.
This merge created the tip-most commit on feature
, which is commit G
, which is a merge commit.
After that, you checked out branch master
(making HEAD
point to the name master
) and made a new commit that moved the tip of master
, so let's add that to our graph-so-far:
... - A - o - o - o - F - H <-- HEAD -> master
\ \
B - C - D - E - G <-- feature
Finally, you wanted to rebase feature
on the (new tip of) master
so you did git checkout feature
:
... - A - o - o - o - F - H <-- master
\ \
B - C - D - E - G <-- HEAD -> feature
and now you run the command git rebase master
.
What rebase
does is copy commits.
First, it has to find which commits to copy. The commits it should copy are those that are reachable from the current branch—i.e., from the name feature
—but not from the branch whose name you give as the upstream, i.e., master
.
Here's where we run into a rather large problem. Look at this (rather dense) paragraph from recent git rebase
documentation:
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.
The fork point stuff is itself a bit complicated but we can ignore it for now because using the command git rebase master
means it's turned off. You can therefore see the commits that are to be rebased with git log master..HEAD
. This is commits B
, C
, D
, E
, and G
(except that rebase normally tosses out merges).
You might wonder why B
through E
are included here, given that the merge base of master
and feature
is commit G
. The problem is that, while merge commit G
points back at commit F
(reachable from master), commit F
does not (and cannot) "point forward" to G
. So when we start from the tip of master
(new commit H
) and work backwards, we get H
, F
, all the boring o
s, A
, and everything before A
: these are the commits excluded. When we start from the tip of feature
(commit G
) and work backwards, we get G
, E
, D
, C
, and B
before we hit the first excluded one (A
). Thus these are the candidates for rebasing.
If you allow the rebase to proceed, and resolve all the conflicts, you'll end up with:
B' - C' - D' - E' <-- HEAD -> feature
/
... - A - o - o - o - F - H <-- master
\ \
B - C - D - E - G [abandoned]
(assuming all of the to-be-copied commits have actual changes to copy; commit G
won't need to be copied as it won't contribute anything this time).
Upvotes: 2
Reputation: 24406
That behaviour is happening because when you merge
master in, the changes go on the top/end of feature
.
When you rebase, the changes from master
exist at the beginning of feature
.
This is why when you merge over the top, you fix all your conflicts, you merge again, then those conflicts are already resolved. When you rebase however, you have to merge the conflicts again because the previous commit where you resolved everything is at the end of the branch.
Sounds like you're best to choose either merging or rebasing and stick with it for the life of feature
.
Upvotes: 2