将变基在 PR 中包含旧功能分支

  • 本文关键字:包含旧 功能 分支 PR git
  • 更新时间 :
  • 英文 :


我为一个功能分支提交了 PR,但尚未获得批准。现在我必须处理一个新功能,它应该基于该功能分支。我想使用 re-base 开始使用来自其他功能分支(尚未批准/合并)的最新提交来开始处理新分支。在这种情况下,当我为新功能分支提交 PR 时,该 PR 是包含旧功能分支的提交,还是仅包含新功能分支的提交?

Git 本身完全不知道拉取请求(如果您使用的是 GitHub 或 Bitbucket,这是 GitHub 或 Bitbucket 的功能:请注意,GitLab 也有类似的东西,但它们被称为 MR,合并请求)。 所以git rebase不知道某些提交在某些PR 中;它实际上无法知道这一点。

因此,要正确回答这个问题,需要同时定义 PR 流程变基操作。 由于您尚未指定托管站点,因此无法定义 PR 部分,但我们可以概括一下:

  • 要在某个托管站点上发出拉取请求或合并请求,我们必须向该站点git push一组提交,然后使用该站点的功能生成 PR/MR。 PR/MR进入一些非Git辅助数据库,人们可以检查它并大惊小怪并做任何事情,但无论如何,都涉及一些提交,并且最初,至少,这些提交包括您发送的提交git push

  • git rebase都是关于复制提交的:我们有一些现有的提交集,我们喜欢这些提交的一些东西,但我们不喜欢这些提交的其他一些东西。 任何提交都不能更改:提交的实际名称是其哈希 ID,其哈希 ID 是通过对其内容进行哈希操作来确定的(特别是元数据,但元数据也取决于数据,所以我们得到了一个 Merkle-tree-ish 设置,其中最终哈希涵盖了所有内容)。

当我们使用git rebase

复制提交时,我们指示 Git 停止使用旧的提交,转而使用新的、复制的提交。这对其他 Git 存储库中的副本没有影响,因为它实际上不能:这些副本是它们的副本,并且像所有提交一样,无法更改。 我们也许能够指示其他 Git 存储库停止使用原始提交并开始使用其他提交。 是否、何时以及如何影响任何拉取请求取决于其他站点。

当我们使用git rebase时,我们必须告诉我们自己的 Git 软件两件事:

  • 它应该复制哪些提交? 哪些不应该复制? (这是一回事,有两面,就像一枚硬币的两面一样。

  • 副本应该去哪里?

Git 通过分支名称查找提交,对于git rebase,变基的最终结果反映在采用一个分支名称(至少目前只有一个)并使其找到新副本而不是我们复制原始副本。

因此,我们有一些分支名称可以找到一些提交:

...--G--H   <-- main

I--J   <-- feature1

K--L   <-- feature2 (HEAD)

在这里,我们在 Git 存储库中的"分支feature2上"。feature2这个名字专门用于查找提交L:该提交是当前分支的提示提交。 提交L找到提交K,它找到提交J,它找到提交I,它找到提交H,依此类推,以 Git 的通常方式向后

。同时,分支名称feature1找到提交J:提交J是分支feature1提示提交。main找到H的名称,所以H是分支main的提示提交。所有的提交都在分支feature2上,即使提交到包括Hmain上,并且通过和包括J提交在feature1

Git 有两种使用分支名称(或任何找到任何一个特定提交的内容)的方法:它可以只选择那个提交,也可以选择那个有历史记录的提交。 "根据历史记录选择"操作有点像洪水填充,只是它只朝一个方向,即向后。 对于只有一个父级的普通提交,这很简单;对于有两个或更多父级的合并提交,这时我们需要更复杂的东西。

无论如何,变基的一般形式是:1

git rebase --onto <target> <upstream>

upstream参数是"不复制什么"说明符。 Git 用历史记录选择它,用临时的"红色油漆"填充"提交图。 这样绘制的提交不会被复制。

然后通过HEAD(即当前分支名称)选择被复制的提交。阿拉伯数字Git 使用历史记录选择它,用临时的"绿色油漆"填充"提交图,但不覆盖任何红色。 因此,只有未明确取消选择的提交才会被复制。

然后,副本将遵循target参数指定的提交。

如果省略--ontotarget参数,则变基的目标是upstream参数指定的提交。 这适用于以下情况:

...--o--*--o--o--o   <-- mainline

A--B--C   <-- feature (HEAD)

我们刚刚跑git rebase mainline. Git 绘制所有o*提交为红色(暂时),然后将C-B-A绘制为绿色并停止*因为为红色,从而复制C-B-A

选择要复制的提交后,Git 确保将它们按正确的顺序排列。 Git 还会删除特定的提交:

  • 如果--fork-point处于活动状态,则通过分支点选择进行的提交将被丢弃。
  • 除非--preserve-merges(现已弃用)或--rebase-merges生效,否则将删除所有合并提交。
  • "Patch-ID 等效"提交被删除(Git 为此使用对称差异代码,因此它实际上使用的是upstream...HEAD而不是文档中声称的upstream..HEAD)。

其余的提交构成了要复制的列表(或者,对于交互式变基,在--autosquash修改这些命令之前,将其转换为pick命令)。

生成正确的列表后,Git 然后使用git switch --detachtarget到达副本所在的提交。 然后,它使用git cherry-pick或等效的3来复制每个提交。

(但是,变基有时会在这里使用一个快捷方式:如果要复制的提交下一次提交,Git 将快进该提交。 您可以使用git rebase --force或类似选项禁止显示此功能:请参阅文档。

一旦所有提交都被复制(或在允许和可能的情况下快进到位),Git 将采用原始分支名称,无论它是什么,并强制它指向上次复制的提交。 对于上面的主线和功能示例,结果如下:

A'-B'-C'  <-- feature (HEAD)
/
...--o--*--o--o--o   <-- mainline

A--B--C   [abandoned]

临时的红绿油漆内容会自动删除:事实上,它根本没有进入存储库,因为它只是在内存中,而 Git 正在运行对称差分操作以生成要复制的提交列表。 (它甚至不是"绘制",它只是某些数据结构中的一些位。 绘画的想法是帮助可视化效果。


1您可以再添加一个参数<branch>,但这仅表示 *运行git switch先到该分支,然后返回到常规窗体。 变基完成后,您就"在"给定的分支上,就好像您自己跑git switch一样。 所以我更愿意打折这个案子。

2如果在分离的 HEAD 模式下运行git rebase,Git 仍会以相同的方式选择提交。 它只是省略了最后的"移动一个分支名称"步骤。

3较旧的 Git 版本在这里默认使用git format-patchgit am;你必须强制他们使用樱桃采摘模式,除了交互式变基,这需要实际的樱桃采摘。


与拉取请求的交互

假设我们从以下方面开始:

...--G--H   <-- main

I--J   <-- feature1

K--L   <-- feature2 (HEAD)

并将feature1(提交I-J)和feature2(K-L)git push到GitHub存储库,然后使用GitHub CLI或Web界面创建两个PR。feature1PR 将要求某人将提交I-J添加到某个 GitHub 分支的某个分支。feature2PR 将要求某人将提交I-J-K-L添加到某个 GitHub 分支的某个分支。

无论是否有人这样做,我们现在都运行git switch feature2 && git rebase --onto main feature1,使用--onto操作来生成:

K'-L'  <-- feature2 (HEAD)
/
...--G--H   <-- main

I--J   <-- feature1

K--L   [abandoned]

GitHub 上的任何存储库中尚未发生任何更改。

如果我们现在将更新的feature2git push我们生成feature2PR 的 GitHub 分支,GitHub 将在要求我们将--force用于此git push操作后更新我们的分支PR。我们的新 PR 将包含提交K'-L'如果有人已经合并了旧的提交或以任何方式使用了它们,那么我们为时已晚。他们有我们的K-L承诺。 如果没有,他们仍然可以访问我们的K-L提交,但他们可以轻松访问的简单操作将使用我们的K'-L'提交。

如果我们不使用--onto,并且 copy 提交I-J-K-L--force或任何其他方式,以便我们得到:

I'-J'-K'-L'  <-- feature2 (HEAD)
/
...--G--H   <-- main

I--J   <-- feature1

K--L   [abandoned]

并将其git push --force到我们的 GitHub 分支以更新或feature2PR,我们现在已将新副本I'-J'作为该 PR 的一部分。 如果有人从字面上合并了我们的两个 PR,那么这些提交的两个副本都将出现在它们的合并结果中。但是GitHub有"接受这个PR"按钮,这些按钮实际上并不使用git merge,而是相当于git rebase --forcegit merge --squash。 在这些情况下,我们的提交(通过哈希 ID)都不会出现在它们的合并结果中。 我们的代码可能会也可能不会在他们的合并结果中结束。

结论

底线是,你必须知道变基做什么,并查看哪些提交在提交图中的位置。 但这只能告诉您有关存储库的信息。 当您git pushGitHub 分叉时,这会添加您拥有的任何新提交,并调整分叉中的分支名称以指向git push请求或命令的新提交(无强制 = 礼貌请求,使用--force--force-with-lease或类似 = 命令)。

在 GitHub 上,推送到具有开放 PR 的分支会导致另一个人(无论是谁)将新提交视为 PR 的一部分。 旧的仍然可以访问,但比新的更难找到。 在其他系统上,您可以得到他们给您的任何东西。

始终绘制图表(如果没有其他地方,请在您的脑海中绘制)。

假设主提交是 m1-m2。

您的特征 基于主节点的分支为 m1-m2-a1。

分支 A 的 PR 有一个提交。答1

如果在特征 A 上重定特征 B 的基数,则树为 m1-m2-a1-b1

分支 B 的 PR 将有 2 个提交a1,b1

一旦您的 PR1 完成变基,您的主节点将是M1-M2-A1。 分支 B 的 PR 最终将只有 1 个提交B1

最新更新