Git pull * after * git rebase?

我有一个功能分支,和一个主分支。

主分支已经进化,我的意思是让这些更新尽可能少地从主分支发散。

所以我在两个分支中都是 git pullgit checkout feature/branch,最后是 git rebase master

现在,我要么期望一切顺利工作 或者冲突显示出来,我需要在继续 rebase 之前解决,直到所有主提交被成功地重新应用于功能分支。

现在,在我身上真正发生的事情是我不理解的:

$>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*

现在,尽管我可以理解他的拉后负荷冲突; 我不明白拉需要什么。逻辑上,它应该在分支时回滚到 master,保存在分支上的提交,转发到 master 上的最新提交,然后应用保存的提交。

我不明白 Applying消息指的是什么: 在哪个版本上应用提交是什么?

67524 次浏览

The have 27 and 2 different commits each is telling you that you now have 27 new commits from master and 2 new commits in your branch that are not present in origin/<yourbranch>.

Because origin/<yourbranch> has been massively changed by the rebase, it no longer has a common base with origin/<yourbranch>. Therefore, you don't want to then pull the changes from origin/<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 use push --force to put them into sync again. Then origin/<yourbranch> will again have the same base as your local branch and that base will contain all the latest master changes.

If the remote versions of master and feature/branch are up-to-date individually, then simply reset your local feature branch

git checkout feature/branch
git fetch origin feature/branch
git reset --hard origin/feature/branch

then if you want to bring in changes from the master branch,

git rebase origin/master

tl;dr You should update both master and feature with git pull and git pull --rebase before rebasing feature on top of master. feature0 git pull feature1 you have rebased your feature branch on top of master.

With your current workflow, the reason why git status is telling you this:

Your branch and 'origin/feature' have diverged, and have 27 and 2 different commits each, respectively.

is because your rebased feature branch now has 25 new commits that aren't reachable from origin/feature (since they came from the rebase on master) plus 2 commits that are reachable from origin/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 in origin/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 on master:

A - B - C (master)
\
D - E (feature)

After git pull, master got commit F:

A - B - C - F (master, origin/master)
\
D - E (feature)

At that point, you rebase feature on top of master, which applies D and E:

A - B - C - F (master, origin/master)
\
D' - E' (feature)

In the meantime, the remote branch origin/feature is still based off of commit C:

A - B - C - F (master, origin/master)
\   \
\   D' - E' (feature)
\
D - E (origin/feature)

If you do a git status on feature, Git will tell you that your feature branch has diverged from origin/feature with 3 (F, D', E') and feature0 (D, E) commits, respectively.

Note that D' and E' contain the same changes as D and E but have different commit IDs because they have been rebased on top of F.

The solution is to do git pull on both master and feature before rebasing feature on master. However, since you may have commits on feature that you haven't yet pushed to origin, you would want to do:

git checkout feature && git pull --rebase

to avoid creating a merge commit between origin/feature and your local feature.

Update on the consequences of rebasing:

In light of this comment, I expanded on the diverging branches. The reason why git status reports that feature and origin/feature diverge after the rebase is due to the fact that rebasing brings in new commits to feature, plus it rewrites the commits that were previously pushed to origin/feature.

Consider the situation after the pull but before the rebase:

A - B - C - F (master)
\
D - E (feature, origin/feature)

At this point, feature and origin/feature point to the same commit E—in other words, they're in "sync". After rebasing feature on top of master, history will look like this:

A - B - C - F (master)
\   \
\   D' - E' (feature)
\
D - E (origin/feature)

As you can see, feature and origin/feature have origin/feature4, their common ancestor being commit C. This is because feature now contains the new commit F from master plus D' and E' (read as "origin/feature5" and "origin/feature6") which are commits D and E applied on top of F. Even though they contain the same changes, Git considers them to be different because they have different commit IDs. Meanwhile, origin/feature still references D and E.

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 on feature this is what would happen:

A - B - C - F (master)
\   \
\   D' - E'- M (feature)
\         /
D - E - (origin/feature)

Since git pull does git fetch + git merge, this would result in the creation of the merge commit M, whose parents are E' and E.

If, instead, you ran git pull --rebase (that is, git fetch + git rebase) then Git would:

  1. Move feature to commit C (the common ancestor of feature and origin/feature)
  2. Apply D and E from origin/feature
  3. Apply F, D' and E'

However, noticing that D' and E' contain the same changes as D and E, Git would just discard them, resulting in a history looking like this:

A - B - C - F (master)
\
D - E - F' (feature)
^
(origin/feature)

Notice how commit F, previously reachable from feature, got applied on top of origin/feature resulting in F'. At this point, git status would tell you this:

Your branch is ahead of 'origin/feature' by 1 commit.

That commit being, of course, F'.

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:

C' (feature)
B'
A'
* (master, origin/master)
*
*
| C (origin/feature)
| B
| A
|/
* some base commit

While the commit A' contains a similar change set as commit A, 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:

* (feature)
|\
C'|
B'|
A'|
* | (master, origin/master)
* |
* |
| C (origin/feature)
| B
| A
|/
* some base commit

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 avoid git rebase altogether. This is the cleaner approach: It results in the history as it has happened, instead of telling lies about history as git rebase does. That's at least what I prefer.

This error came because git fetch origin has not been invoked after invoking git checkout feature/branch. To avoid this error in future, you could execute the below commands in sequence:

git checkout feature/branch
git fetch origin
git rebase master