我为一个功能分支提交了 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
上,即使提交到包括H
在main
上,并且通过和包括J
提交在feature1
。
Git 有两种使用分支名称(或任何找到任何一个特定提交的内容)的方法:它可以只选择那个提交,也可以选择那个有历史记录的提交。 "根据历史记录选择"操作有点像洪水填充,只是它只朝一个方向,即向后。 对于只有一个父级的普通提交,这很简单;对于有两个或更多父级的合并提交,这时我们需要更复杂的东西。
无论如何,变基的一般形式是:1
git rebase --onto <target> <upstream>
upstream
参数是"不复制什么"说明符。 Git 用历史记录选择它,用临时的"红色油漆"填充"提交图。 这样绘制的提交不会被复制。
然后通过HEAD
(即当前分支名称)选择被复制的提交。阿拉伯数字Git 使用历史记录选择它,用临时的"绿色油漆"填充"提交图,但不覆盖任何红色。 因此,只有未明确取消选择的提交才会被复制。
然后,副本将遵循target
参数指定的提交。
如果省略--onto
和target
参数,则变基的目标是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-patch
和git am
;你必须强制他们使用樱桃采摘模式,除了交互式变基,这需要实际的樱桃采摘。
与拉取请求的交互
假设我们从以下方面开始:
...--G--H <-- main
I--J <-- feature1
K--L <-- feature2 (HEAD)
并将feature1
(提交I-J
)和feature2
(K-L
)git push
到GitHub存储库,然后使用GitHub CLI或Web界面创建两个PR。feature1
PR 将要求某人将提交I-J
添加到某个 GitHub 分支的某个分支。feature2
PR 将要求某人将提交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 上的任何存储库中尚未发生任何更改。
如果我们现在将更新的feature2
git push
到我们生成feature2
PR 的 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 分支以更新或feature2
PR,我们现在已将新副本I'-J'
作为该 PR 的一部分。 如果有人从字面上合并了我们的两个 PR,那么这些提交的两个副本都将出现在它们的合并结果中。但是GitHub有"接受这个PR"按钮,这些按钮实际上并不使用git merge
,而是相当于git rebase --force
或git merge --squash
。 在这些情况下,我们的提交(通过哈希 ID)都不会出现在它们的合并结果中。 我们的代码可能会也可能不会在他们的合并结果中结束。
结论
底线是,你必须知道变基做什么,并查看哪些提交在提交图中的位置。 但这只能告诉您有关存储库的信息。 当您git push
GitHub 分叉时,这会添加您拥有的任何新提交,并调整分叉中的分支名称以指向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。