I have a feature branch, and a master branch.
Master branch has evolved and I mean to have those updates to diverging as little as possible from master branch.
So I git pull
in both branches, git checkout feature/branch
and finally git rebase master
.
Now here I either expect everything to work smoothly or conflicts showing up that I need to resolve before continuing rebase until all master commits are re-applied successfully on feature branch.
Now what really happened in my case is something I do not understand:
$>git rebase master
First, rewinding head to replay your work on top of it...
Applying: myFirstCommitDoneOnTheBranch
Applying: myOtherCommitDoneOnTheBranch
$>git status
On branch feature/branch
Your branch and 'origin/feature/feature' have diverged,
and have 27 and 2 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
nothing to commit, working tree clean
$>git pull
*load of conflicts*
Now, as much as I can understand he load of conflicts after the pull; I do not understand the need for a pull. Logically, it should rollback to master when it got branched, save the commits made on the branch, forward to latest commit on master and then apply the saved commits.
I do not understand to what the Applying
message refers to: what is applying the commits on which version?
tl;dr You should update both
master
andfeature
withgit pull
andgit pull --rebase
before rebasingfeature
on top ofmaster
. There is no need to do agit pull
after you have rebased yourfeature
branch on top ofmaster
.With your current workflow, the reason why
git status
is telling you this:is because your rebased
feature
branch now has 25 new commits that aren't reachable fromorigin/feature
(since they came from the rebase onmaster
) plus 2 commits that are reachable fromorigin/feature
but have different commit IDs. Those commits contain the same changes (i.e. they're patch equivalent) but they have different SHA-1 hashes because they are based off of a different commit inorigin/feature
than the one you rebased them on in your local repository.Here's an example. Let's assume that this is your history before doing
git pull
onmaster
:After
git pull
,master
got commitF
:At that point, you rebase
feature
on top ofmaster
, which appliesD
andE
:In the meantime, the remote branch
origin/feature
is still based off commitC
:If you do a
git status
onfeature
, Git will tell you that yourfeature
branch has diverged fromorigin/feature
with 3 (F
,D'
,E'
) and 2 (D
,E
) commits, respectively.The solution is to do
git pull
on bothmaster
andfeature
before rebasingfeature
onmaster
. However, since you may have commits onfeature
that you haven't yet pushed toorigin
, you would want to do:to avoid creating a merge commit between
origin/feature
and your localfeature
.Update on the consequences of rebasing:
In light of this comment, I expanded on the diverging branches. The reason why
git status
reports thatfeature
andorigin/feature
diverge after the rebase is due to the fact that rebasing brings in new commits tofeature
, plus it rewrites the commits that were previously pushed toorigin/feature
.Consider the situation after the pull but before the rebase:
At this point,
feature
andorigin/feature
point to the same commitE
—in other words, they're in "sync". After rebasingfeature
on top ofmaster
, history will look like this:As you can see,
feature
andorigin/feature
have diverged, their common ancestor being commitC
. This is becausefeature
now contains the new commitF
frommaster
plusD'
andE'
(read as "D prime" and "E prime") which are commitsD
andE
applied on top ofF
. Even though they contain the same changes, Git considers them to be different because they have different commit IDs. Meanwhile,origin/feature
still referencesD
andE
.At this point, you've rewritten history: you've modified existing commits by virtue of rebasing them, effectively creating "new" ones.
Now, if you were to run
git pull
onfeature
this is what would happen:Since
git pull
doesgit fetch
+git merge
, this would result in the creation of the merge commitM
, whose parents areE'
andE
.If, instead, you ran
git pull --rebase
(that is,git fetch
+git rebase
) then Git would:feature
to commitC
(the common ancestor offeature
andorigin/feature
)D
andE
fromorigin/feature
F
,D'
andE'
However, noticing that
D'
andE'
contain the same changes asD
andE
, Git would just discard them, resulting in a history looking like this:Notice how commit
F
, previously reachable fromfeature
, got applied on top oforigin/feature
resulting inF'
. At this point,git status
would tell you this:That commit being, of course,
F'
.If the remote versions of
master
andfeature/branch
are up-to-date individually, then simply reset your local feature branchthen if you want to bring in changes in the
master
branch,The
have 27 and 2 different commits each
is telling you that you now have 27 new commits frommaster
and 2 new commits in your branch that are not present inorigin/<yourbranch>
.Because
origin/<yourbranch>
has been massively changed by the rebase, it no longer has a common base withorigin/<yourbranch>
. Therefore, you don't want to then pull the changes fromorigin/<yourbranch>
after the rebase, because, as you see, all H*** breaks loose.If you know there are changes in
origin/<yourbranch>
that you need in your local branch, then pull those before you rebase.If you are sure no one has changed
origin/<yourbranch>
since your last push (a safe bet if this is your own feature branch), you can usepush --force
to put them into sync again. Thenorigin/<yourbranch>
will again have the same base as your local branch and that base will contain all the latestmaster
changes.When you rebased your feature branch on top of master, you created a bunch of new commits. However, your
origin/feature
branch is still pointing to the old ones. This is the situation after the rebase:While the commit
A'
contains a similar change set as commitA
, it is by no means the same commit. It contains a different tree, and has a different parent.Now, when you try to pull
feature
again, you try to create this history:You are merging two branches that have introduced very similar, jet different changes. This is bound to create a ton of conflicts, apart from being entirely pointless.
What you need to do is inform your upstream repo about the rebase by using
git push -f
. This will loose the old history, and replace it with the rewritten one.The alternative is to avoid using
git rebase
on branches that you have already pushed to any other repository, or avoidgit rebase
altogether. This is the cleaner approach: It results in the history as it has happened, instead of telling lies about history asgit rebase
does. That's at least what I prefer.