How to rebase branch against master after parent is squashed and committed?

Dun*_*nes 6 git git-merge squash git-squash

I use an optimistic work-flow in Gitlab, which assumes the majority of my merge requests will be accepted without change. The flow looks like this:

  1. Submit a merge request for branch cool-feature-A
  2. Create a new branch, based on cool-feature-A, called cool-feature-B. Begin developing on this branch.
  3. 一位同事批准了我的cool-feature-A.
  4. cool-feature-B反对master(这是无痛的)并继续开发。

如果同事在第 3 步进行壁球合并,就会出现问题。这会重写cool-feature-B. 当我到达第 4 步时,我面前是一个合并痛苦的世界。

我怎样才能避免这种情况发生?

tor*_*rek 17

本质上,必须告诉 Git: 我想cool-feature-B针对进行变基master,但我想复制与您通常在此处计算的提交不同的一组提交。 最简单的方法是使用--onto. 也就是说,通常你会跑,正如你所说:

git checkout cool-feature-B
git rebase master
Run Code Online (Sandbox Code Playgroud)

但你需要做:

git rebase --onto master cool-feature-A
Run Code Online (Sandbox Code Playgroud)

删除自己的 branch-name / label 之前cool-feature-A

即使他们使用普通合并,您也可以随时执行此操作。 这样做不会有什么坏处,除了您必须键入更多内容并记住(无论您喜欢什么),稍后您将需要 this --onto,它需要正确的名称或哈希 ID。

(如果您可以获得当前cool-feature-A指向 的 reflog的提交的哈希 ID cool-feature-B,您可以使用该--fork-point功能让 Git 稍后自动为您计算,前提是相关的 reflog 条目尚未过期。但总的来说,这可能比手动执行此操作更难。另外,它具有整个“提供”部分。)

为什么会这样

让我们像往常一样从图形开始。最初,您在自己的存储库中有此设置:

...--A   <-- master, origin/master
      \
       B--C   <-- cool-feature-A
           \
            D   <-- cool-feature-B
Run Code Online (Sandbox Code Playgroud)

运行后git push origin cool-feature-A cool-feature-B,您将拥有:

...--A   <-- master, origin/master
      \
       B--C   <-- cool-feature-A, origin/cool-feature-A
           \
            D   <-- cool-feature-B, origin/cool-feature-B
Run Code Online (Sandbox Code Playgroud)

请注意,我们在这里所做的只是添加了两个origin/名称(两个远程跟踪名称):在他们的存储库中,在 at 上origin,他们获取了 commits B, C, Dand 他们将它们的 cool-feature-Acool-feature-B名称分别设置为记住提交CD,因此您自己的 Git 添加了您的 origin/变体这两个名字中。

如果他们(无论“他们”是谁——控制存储库的人origin)进行快进合并,他们就会将他们的master 向上滑动以指向 commit C。(请注意,GitHub Web 界面没有进行快进合并的按钮。 我没有使用过 GitLab 的 Web 界面;它可能以各种方式不同。)如果他们强制进行真正的合并——这就是 GitHub 网页“立即合并”clicky 按钮默认执行;再说一次,我不知道 GitLab 在这里做了什么——他们会做一个新的合并提交E

...--A------E
      \    /
       B--C
           \
            D
Run Code Online (Sandbox Code Playgroud)

(这里我故意去掉了所有的名字,因为他们的名字和你的不太匹配)。他们大概会删除(甚至可能从未真正创建)他们的cool-feature-A名字。无论哪种方式,您都可以使用自己的 Git 快进您的 master名称,同时更新您的origin/*名称:

...--A------E   <-- master, origin/master
      \    /
       B--C   <-- cool-feature-A [, origin/cool-feature-A if it exists]
           \
            D   <-- cool-feature-B, origin/cool-feature-B
Run Code Online (Sandbox Code Playgroud)

或者:

...--A
      \
       B--C   <-- cool-feature-A, master, origin/master [, origin/cool-feature-A]
           \
            D   <-- cool-feature-B, origin/cool-feature-B
Run Code Online (Sandbox Code Playgroud)

无论你cool-feature-A现在是否删除你的名字——为了方便以后的绘图,假设你删除了——如果你运行:

git checkout cool-feature-B
git rebase master
Run Code Online (Sandbox Code Playgroud)

Git 现在将枚举从D- D、 then C、 thenB等可访问的提交列表,并减去可从master以下内容访问的提交列表:(E如果存在),则A(如果E存在)和C(无论是否E存在),然后B, 等等。减法的结果就是 commit D

你的 Git 现在复制这个列表中的提交,以便新的副本出现在 : 的提示之后,master即在E他们进行真正的合并之后,或者在C他们进行快进合并之后。所以 Git 要么复制D到一个新的提交D',在E

              D'   <-- cool-feature-B
             /
...--A------E   <-- master, origin/master
      \    /
       B--C
           \
            D   <-- origin/cool-feature-B
Run Code Online (Sandbox Code Playgroud)

或者它D独自离开,因为它已经出现了C(所以没有什么新东西可以画)。

当他们使用 GitLab 的任何等效项是 GitHub 的“挤压和合并”或“变基和合并”点击按钮时,就会出现棘手的部分。我将跳过“rebase 和合并”案例(这通常不会导致问题,因为 Git 的 rebase 也会检查补丁 ID)并直接进入硬案例,即“挤压和合并”。正如您正确指出的那样,这会产生一个新的、不同的、单一的提交。当您将新提交带入您自己的存储库时 - 例如,之后git fetch- 您拥有:

...--A--X   <-- master, origin/master
      \
       B--C   <-- cool-feature-A [, origin/cool-feature-A]
           \
            D   <-- cool-feature-B, origin/cool-feature-B
Run Code Online (Sandbox Code Playgroud)

whereX是进行新提交的结果,其快照将匹配 merge E(如果他们要进行 merge E),但其(单个)父项是现有 commit A。所以历史——通过向后工作枚举的提交列表——来自X只是X, then A, then 任何提交到之前A

如果您git rebase master在 on 上定期运行cool-feature-B,Git:

  1. 枚举可从D以下位置访问的提交:D, C, B, A, ...;
  2. 枚举可从X: X, A, ...;访问的提交
  3. 从步骤 1 中的集合中减去步骤 2 中的集合,留下D, C, B;
  4. 复制这些提交(以未向后化的顺序),以便它们在X.

请注意,第 2 步和第 3 步都使用该词master来查找提交:对于第 2 步,要复制的提交是那些无法master. 对于步骤 3,放置副本的位置的尖端之后master

但是如果你运行:

git rebase --onto master cool-feature-A
Run Code Online (Sandbox Code Playgroud)

你让 Git在步骤 2 和 3 中使用不同的项目:

  • 要复制的提交列表,从第 2 步开始,来自cool-feature-A..cool-feature-B:subtract C-B-A-...from D-C-B-A-...。那只剩下 commit 了D
  • 第 3 步中放置副本的位置来自--onto master: put them after X

所以现在 Git 只复制DD',然后git rebase将名称拉到cool-feature-B指向D'

          D'  <-- cool-feature-B
         /
...--A--X   <-- master, origin/master
      \
       B--C   <-- cool-feature-A [, origin/cool-feature-A]
           \
            D   <-- origin/cool-feature-B
Run Code Online (Sandbox Code Playgroud)

这就是你想要的。

如果他们——控制 GitLab 存储库的人——使用了真正的合并或快进而不是真正的合并,这一切仍然有效:你会让你的 GitD向后枚举,但是C从列表中删除-on-backwards,只留下D复制;然后 Git 会复制,D以便在E(真正的合并情况)或C(快进情况)之后出现。在快进的情况下,“复制 D 使其到达它所在的位置”,它会巧妙地根本不费心去复制,而将所有内容留在原处。

  • 哇。感谢您花时间写这篇文章。 (2认同)