在git rebase之后进行git pull操作?

112

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

主分支已经发生了变化,我希望将这些更新尽可能地与主分支分叉得越少越好。

因此我在两个分支中都执行了git pull,然后执行了git checkout feature/branch,最后执行了git rebase master

现在,我期望一切都能顺利进行或者会出现冲突,直到所有主分支提交成功地应用到功能分支上。

但实际上,在我的情况下发生了我不理解的事情:

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

尽管我可以理解在拉取后出现的冲突,但我不明白为什么需要拉取。从逻辑上讲,当它被分支时,应该回滚到主分支,保存在分支上进行的提交,转到主分支上最新的提交,然后应用已保存的提交。

我不理解 Applying 消息所指的是什么:哪个版本正在应用提交?


你是否有远程分支的备份? - ashmaroli
6个回答

147

简而言之: 在基于master重命名feature之前,请使用git pullgit pull --rebase更新masterfeature。在您将feature分支重命名到master之后,无需再进行git pull操作。

根据您当前的工作流程,git status告诉您以下原因:

您的分支和 'origin/feature' 分别有27个和2个不同的提交记录。

这是由于重命名后的feature分支包含了 25个新的提交记录,这些提交记录无法通过origin/feature访问(因为它们来自于master的重新命名),另外还有2个提交记录可以通过origin/feature访问,但由于与origin/feature上的提交记录不同,则具有不同的SHA-1哈希值。这些提交记录包含相同的更改(即它们是“修补等效”的),但是因为它们基于本地仓库中不同的提交而具有不同的SHA-1哈希值。

下面是一个示例。假设这是您在对master运行git pull操作之前的历史记录:

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

在执行了git pull之后,master分支得到了提交记录F:
A - B - C - F (master, origin/master)
         \
          D - E (feature)

这时,您将featuremaster的基础上进行变基,这会应用DE

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

与此同时,远程分支 origin/feature 仍然基于提交记录 C:
A - B - C - F (master, origin/master)
         \   \
          \   D' - E' (feature)
           \
             D - E (origin/feature)

如果你在feature上执行git status,Git 会告诉你feature分支相对于origin/feature分支有3个(FD'E')和2个(DE)提交版本存在差异。

请注意,D'E'包含与DE相同的更改,但具有不同的提交ID,因为它们已经被重新基于F

解决方案是在将feature分支重新基于master之前,在masterfeature分支上都执行git pull。然而,由于你可能在feature上有尚未推送到origin的提交版本,因此你需要执行以下操作:
git checkout feature && git pull --rebase

为了避免在 `origin/feature` 和本地 `feature` 之间创建一个合并提交(`merge commit`),请使用变基操作(`rebasing`)。
关于变基操作的影响更新:
根据这条评论,我扩展了分叉分支的原因。在变基操作后,为什么 `git status` 报告 `feature` 和 `origin/feature` 分叉是因为变基操作会将新的提交插入到 `feature` 中并且重写之前推送到 `origin/feature` 的提交。
考虑在拉取(`pull`)之后但在变基操作之前的情况。
A - B - C - F (master)
         \
          D - E (feature, origin/feature)

在这一点上,featureorigin/feature 指向相同的提交记录 E——换句话说,它们是“同步”的。在将 feature 重新基于 master 后,历史记录将如下所示:

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

正如您所看到的,featureorigin/feature已经 分叉,它们的共同祖先是提交C。这是因为feature现在包含了从master获取的新提交F以及D'E'(读作“D prime”和“E prime”),它们是基于F的提交DE。尽管它们包含相同的更改,但Git认为它们不同,因为它们具有不同的提交ID。与此同时,origin/feature仍然引用DE
在这一点上,您已经 重写历史记录:通过重新设置它们来修改现有的提交,从而有效地创建了“新”的提交。
现在,如果您在feature上运行git pull,将会发生以下情况:
A - B - C - F (master)
         \   \
          \   D' - E'- M (feature)
           \         /
             D - E - (origin/feature)

git pull命令会执行git fetchgit merge操作,因此会创建合并提交M,它的父提交是E'E

相反,如果您运行git pull --rebase(也就是git fetch + git rebase),则Git会执行以下操作:

  1. feature移动到提交C(即featureorigin/feature的公共祖先)
  2. 应用origin/feature中的DE
  3. 应用FD'E'

但是,由于D'E'包含与DE相同的更改,因此Git会直接丢弃它们,从而导致历史记录如下所示:

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

注意观察提交记录中的提交 F,它先前是从分支 feature 可到达的,现在它被应用在 origin/feature 分支之上,产生了一个新的提交记录 F'。此时,git status 命令会提示您以下信息:

您的分支领先于 'origin/feature' 分支 1 个提交。

当然,这个提交就是 F'

3
谢谢。但是我不明白的是,在合并之前我对两个分支都进行了git pull --rebase操作,但最终还是出现了这种情况。为了让事情更清楚明白:我没有在主分支上进行合并,而是在开发分支上进行了合并,虽然这应该不会有任何变化(是的,我使用的命令是git rebase develop而不是git rebase master)。但是,为什么我仍然会遇到这种“分叉分支”的问题呢? - ptpdlc
16
如果你想要发布已经变基过的 feature 分支,唯一的方法就是使用 git push -f 命令进行 _强制推送_。然而,需要注意的是这将会发布一段被 重写的历史记录 (在我们的例子中,D'E' 的提交实际上是“新的”)。因此,如果有人在旧的提交 (DE) 上面创建了提交,他们在执行 git pull 后就会遇到相同的情况。所以,如果你可以安全地通知所有可能已经拉取了 origin/feature 分支的人,告诉他们你打算推送重写的提交,那么就可以这么做。否则,就不要对 feature 分支进行变基,而是将 master 分支合并到 feature 分支中。 - Enrico Campidoglio
5
@EnricoCampidoglio 我认为这个评论应该成为答案的一部分。我读了答案,想到"好的,现在我知道为什么......但是如何处理所有这些虚假提交的最佳方式是什么呢?" 我一直在使用 git push -f 以避免下次变基时出现无限合并冲突,但我没有考虑到有人正在该分支上工作,并且会有旧历史记录的副本。 - John Cramerus
1
Git 在将 feature 分支 rebase 到 master 分支之上时,会删除 F 分支,因为 F 分支包含相同的更改。最终的历史记录将如下所示:A-B-C-D-E - Enrico Campidoglio
1
@Seth 由于该图中没有其他DE提交,我将它们保持不变以保持简单。但是,是的,你是正确的,那些在技术上是_新提交_,所以它们应该被标记为D'E'。我更新了我的答案。 - Enrico Campidoglio
显示剩余7条评论

39
如果远程版本的masterfeature/branch分支都是最新的,那么只需重置本地的特性分支。
git checkout feature/branch
git fetch origin feature/branch
git reset --hard origin/feature/branch

那么如果您想从master分支引入更改,

git rebase origin/master

8
"从主分支引入更改"可能更清晰明了。 - noelicus

7

当您将功能分支(feature branch)在主分支(master)上进行了变基(rebase)时,您创建了一堆新的提交(commits)。然而,您的 origin/feature 分支仍然指向旧的提交。这是变基后的情况:

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

虽然提交A'包含与提交A相似的变更集,但它绝不是同一个提交。它包含不同的树,并且有不同的父提交。

现在,当您再次尝试拉取feature时,您要创建这个历史记录:

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

您正在合并两个分支,这些分支引入了非常相似但有所不同的更改。这肯定会产生大量冲突,而且完全没有意义。

您需要做的是使用 git push -f 通知上游仓库关于重新基础的情况。这将丢失旧历史记录,并用重写后的历史记录替换它

另一种选择就是避免在已经推送到任何其他仓库的分支上使用 git rebase 或者完全避免使用 git rebase这是更加干净的方法:它呈现出按照实际发生的历史,而不像 git rebase 那样造假。至少我是这么认为的。


2
因为“完全避免使用git rebase”而被踩了。git rebase是一种有用的工具,对于大型团队的有效开发运维尤其重要。仅仅因为你不理解如何正确使用某个工具,并不意味着你应该告诉其他人避免使用它。 - siliconrockstar
@siliconrockstar,你有注意到我只是提出“完全避免使用git rebase”作为一种替代方案吗?这是我更喜欢的一种选择,但仍然是一种选择。我知道有些情况下必须使用git rebase。我既用过rebase又用过merge,我知道为什么我更喜欢使用git merge。你认为我提倡更好的选择(在我看来)值得一个踩吗? - cmaster - reinstate monica
因为你对“更好的选择”有何看法是毫无价值的。根据使用情况,任何一种选择都可能更好或更差。对于如何解决非特定问题的笼统观点,我原则上会给予反对票。 - siliconrockstar

4
“have 27 and 2 different commits each”是在告诉您,现在您有27个来自“master”的新提交以及2个不在“origin/ ”中的新提交。
由于rebase大规模更改了“origin/ ”,因此它不再与“origin/ ”具有共同的基础。 因此,在rebase之后,您不想从“origin/ ”拉取更改,因为这会导致一系列问题。
如果您知道“origin/ ”中有需要在本地分支中使用的更改,则应在rebase之前pull这些更改。
如果您确定自上次推送以来没有人更改“origin/ ”(如果这是您自己的特性分支,则可以肯定),则可以使用“push --force”将它们再次同步。 然后,“origin/ ”将再次具有与您的本地分支相同的基础,并且该基础将包含所有最新的“master”更改。

3
谢谢,我正在尝试这个方法,但是缺少最后一步 push --force分支上。 - PabloRosales

1

这个错误是因为在调用git checkout feature/branch之后没有调用git fetch origin。为了避免将来出现这种错误,您可以按照以下顺序执行以下命令:

git checkout feature/branch
git fetch origin
git rebase master

0
在寻找解决方案时,以下方法对我有效。
git checkout feature/branch
git rebase main
git pull --rebase

在重新基于主分支之后(尽管我确保本地代码是最新的),状态显示我需要再次拉取功能分支上的提交。所以我只需确保运行git pull --rebase,它就像魔术般地解决了问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接