是否可以告诉 Github 我的分支已合并到上游 master 中?

Is it possible to tell Github that my branch was merged into upstream master?

我使用本地分支机构 feature 为 github 存储库创建了一个 PR(我没有写入权限)。后来我决定将它的最后一次提交分离成一个独立的 PR,所以我将 feature 一个提交移回:

git checkout feature
git branch feature2
git reset --hard @~
git push -f

第一个PR合并到上游,所以现在要创建第二个PR:

git checkout master
git pull upstream master
git push origin
git checkout feature2
git rebase master

很遗憾,原来git缺少feature被合并到master的信息。因此,它没有意识到 feature2master 的最近公共基非常接近:它只是 feature。相反,rebase 一直返回到 featuremaster 的公共基础,就好像它们从未合并过一样。结果,git rebase master 变得不必要地混乱。

为什么 Github 丢失了 feature 通过上游 PR 合并到 master 的信息?有什么方法可以向 Github 提供该信息吗?

最后,我不得不求助于:

git checkout master
git checkout -b feature2_new
git cherry-pick feature2

幸运的是我只需要处理一次提交。即使只有一次提交,我认为与真正的基础合并(如果 git 知道它)会比 cherry-pick 更好,因为 git 将能够使用它的知识自动解决更多冲突的历史记录。

请注意,如果我在本地将 feature 合并到 master 而不是执行 github PR,则不会丢失任何信息。当然,那我的master就不会和upstream repo同步了,就没有意义了。

你的问题的根本原因是当 feature 的拉取请求(功能 b运行ch 一次提交回滚)完成时,结果是 合并提交 进入master。这是一个图表,显示 masterfeature2feature 拉取请求到 master 完成后的样子:

master:   ... A -- B -- C -- M
                         \
feature:                  D
                           \
feature2:                   E

在这里,我们可以看到 feature b运行 在提交 C 时从 master 中删除,而 feature2 只是 feature 额外提交一次。合并提交 M 位于 master 的顶部,它代表 所有 feature 中完成的额外工作。请注意,这是一个 合并提交 ,因此与 feature2.

的历史无关

接下来,您 运行 masterfeature2 的以下变基:

git checkout feature2
git rebase master

在此变基之后,feature2 将如下所示:

feature2: ... A -- B -- C -- M -- D' -- E'

请注意合并提交仍然是历史的一部分。尽管从功能上讲它似乎没有必要,因为提交 D 包含进行合并提交所需的一切,但此提交仍然出现。

如果您想知道如何避免这种情况,一种选择是保持 master 的历史记录是线性的。缺陷是以合并提交结束的拉取请求。相反,如果您直接在 master 之上播放来自 feature 的提交,那么您就不会遇到这个问题。考虑以下命令:

git checkout feature
git rebase master

然后,将 masterfeature 进行快进合并:

git checkout master
git merge feature

这会使 masterfeature2 看起来像这样:

master:   ... A -- B -- C -- D
feature2: ... A -- B -- C -- D -- E

现在,如果您要将 feature2 合并到 master,Git 将简单地执行 E 提交,而不是返回到 feature2 的原始点=17=] 和 feature 分歧。

Github 现在支持 3 techniques 合并拉取请求:

  • Merge:创建合并提交(无快进)+ 从 PR 分支中获取所有原始提交
  • 压缩和合并:创建单个提交
  • 变基并合并:创建与 PR 分支一样多的提交,但它们变基到 master

只有常规 merge 保留了我的本地提交是合并到 master 中的 PR 的一部分的知识。如果用的话,我就不会遇到问题中描述的问题了。

其他两种技术失去了这种知识——我无法追溯地创建它(不修改上游主机)。这是为更简单的历史付出的代价。

直觉上,为了让 git 知道上游主提交 U 与我的本地提交 L 相关,需要有一个指向 [=11] 的箭头=] 到 L.

从概念上讲,有两种方法可以实现这一点。

首先,U 可以有两个父项:一个将它连接到 L,另一个将它连接到上游 master 上的所有先前提交。这正是 Github merge 技术所做的。

其次,如果 L 已经指向上游 master 上的所有先前提交,U 可以将 L 作为其唯一父级。 Github 可以通过其 merge 技术允许快进来支持这一点,但它选择不这样做。

如果 Github PR 与 squash 和 mergerebase 和 merge 合并,所有提交都创建在upstream master 只有一个 parent;它们和我的本地提交之间没有箭头。

编辑:

而且我现在相信我所问的历史遗失首先并不是什么大问题。 IIUC,如果 master 通过常规合并提交连接到 feature2,我会遇到 git cherry-pick 的冲突实际上与 git rebase 的冲突相同。如果我有超过 1 个提交拆分成一个独立的 PR,也可以选择 would handle that easily

通常是。但是 git 通过查看历史而不是更改来回答这个问题。如果你使用 Github 的 squash(即 nuke 历史),那么你就失去了利用这个历史的能力;即 git 检测该历史的一部分是否已经存在于上游的能力。

基于 Tim Biegeleisen 创建的图表:

master:   ... A -- B -- C -- M
                         \  /
feature:                  D
                           \
feature2:                   E

当你将 feature2 变基到 master 上时,你应该实际上看到这个历史:

master:   ... A -- B -- C -- M
                         \  /  \
feature:                  D     \
                                 \
feature2:                         E'

因为 rebase 永远不会重新创建目标中已经存在的提交。它会知道D已经在master的历史中了。

当您将 feature2 重新基于 master 时,您会看到逻辑上已经存在于 master 中的提交。这只会在以下情况下发生。

在合并之前,您重写了功能中的一些提交:

                 D'   feature
               / 
... A -- B -- C 
               \  
                D 
                 \
                  E  feature2

执行合并:

                 D'   feature
               /   \
... A -- B -- C --- M   master
               \  
                D 
                 \
                  E  feature2

然后尝试在 master 之上重新设置 feature2:

                 D'   feature
               /   \
... A -- B -- C --- M   master
                     \  
                      D'' 
                       \
                        E'  feature2

回答问题,

Is it possible to tell Github that my branch was merged into upstream master? [...] Why did Github lose the information that feature was merged into master through an upstream PR?

是的,当然可以记录合并。这很正常。

有人选择告诉git(和github)不要记录这个,所以效果出现在上游master分支上,却不知从哪里来。

您正在查看的是某人选择将主线历史与您提供的历史分开的结果。为什么他们选择这样做,你必须从他们那里找出答案。这是一个常见且广泛使用的选项,出于任何数量的人都会很乐意发表意见的原因。线性化历史 看起来 不错,但涉及权衡,无论哪种方式都有缺点,不同的人和不同的情况会以自己的方式倾斜天平。

无论如何,在获取结果后你现在得到了一个未记录的合并,只要你从不尝试合并后续工作仍然基于未记录的父.

怎么办?

您选择的选项,也可以更灵活地(它处理多次提交的 feature1..feature2 历史记录)完整拼写为

git rebase --onto master feature1 feature2

通常是最干净的:上游放弃了您的 feature1 历史记录,因此您放弃了它,将您的 feature2 基于他们现在拥有的内容。

如果出于某种原因您真的不想放弃自己存储库中的 feature1 历史记录——也许您拥有的不仅仅是基于旧的 feature1 提示的 feature2 并且变基将开始变得乏味—— - 您还可以添加合并的本地记录。

echo $(git rev-parse origin/master origin/master~ feature1) >>.git/info/grafts

这告诉本地 git 当前的 origin/master 提交将其记录的第一个父提交和本地 feature1 提交作为父提交。由于上游已经放弃了 feature1 提交而你还没有,所以 git 的所有机制现在在这里和上游都可以正常工作。

不同的项目对于 pull-request 历史应该基于什么有不同的规则,一些想要基于一些最新提示的一切,其他想要基于维护基础标签的一切,其他想要基于引入的提交的错误修复错误(我认为这应该更常见)。有些人不太在意,因为每个人都非常熟悉代码库,因此 rebase-as-desired 仍然是最简单的。

但这里的重要部分是推送前变基是您确保推送的内容完全正确的最后机会。这是一个很好的习惯,移植物在这个工作流程中工作得很好。