Git 如何用两个 parents 进行空提​​交

Git how to make an empty commit with two parents

我在为工作找到正确的 git 命令时遇到了一些问题。

我工作的地方目前正在切换源代码控制 (TFVC -> Git)。但是,TFVC 中的旧版本仍然需要不时修复错误。

我已经通过使用一些 PowerShell 脚本创建构建来处理这个问题,这些脚本确保我的“tfvc-merge”分支包含这些更改。

这就是我之后要做的事情。

我试着把它画出来。所以基本上,拉取请求中的空提交将在我的绘图中具有 1 和 2 的 parent。

我一直在四处寻找几个小时,但没有找到正确的方法。因此,如果有提示可以为我指明正确的方向,那就太好了。提前致谢。

TL;DR

我想你只是想要 git merge -s ours

描述

Cherry 选择一些现有提交进行新提交,不会 link 返回原始提交(新提交有一个 parent;这个 parent 是 HEAD 当你 运行 git cherry-pick 时的提交。你画的图显示提交 #2 有 two parents,就好像你的 cherry-pick 是一个合并。因此我不确定我是否正确理解了情况本身。

短语empty commit是Git本身使用的短语,但我发现它非常具有误导性:提交通常没有空的tree object(见Is git's semi-secret empty tree object reliable, and why is there not a symbolic name for it?);使用 git commit --allow-empty 进行的提交的空白是该提交与其 parent.

之间的 差异

有几种方法可以进行这样的提交,显然包括 git commit --allow-empty,但第一种方法不会进行 merge 提交。唯一进行合并提交的 user-facing 命令是 git merge。这可能会给您留下 "merging without merging" 的问题,如果该短语甚至意味着什么的话。有时会:git merge -s ours.

(请注意,由于合并提交有 两个 parent,使用短语 "empty commit" 来谈论此合并比它更没有意义在谈论 single-parent 提交时做了,因为对于合并提交的两个 parent 有 两个 差异。很可能如果一个是空的,其他不是。)

当您 运行 git merge 时,您可以提供一个 合并策略 名称。 Git 有四个内置策略,名称为 recursiveresolveoctopusours。人们在正常合并时遇到的正常情况是 recursiveresolve 策略是 -s recursive 的简化变体,它几乎是同一件事(两者仅在相对罕见的具有多个合并基础的合并情况下有所不同)。 octopus 策略是 Git 在与 比两个 parent 进行真正合并时使用的策略,我们不会在都在这里。剩下 -s ours。在我们开始之前,让我们花点时间回顾一下正常的 (recursive/resolve) 方法:

  1. 你检查了一些特定的 b运行ch: git checkout mainline.
  2. 你运行git merge sidebranch.

此时,Git 计算 合并基础 mainline 的尖端提交之间 - b运行 的提交ch 名称点,我将 L 称为 left/local/--ours — 以及 sidebranch 的提示提交,我将其称为 R [=183] =]/--theirs:

...--o--*--o--L   <-- mainline
         \
          o----R   <-- sidebranch

合并基础是两个 b运行ches 连接的点:在这里,它是提交 *。 Git 然后,本质上,计算两个 git diffs:

git diff --find-renames <hash-of-base> <hash-of-L>   # what we did
git diff --find-renames <hash-of-base> <hash-of-R>   # what they did

Git 然后尝试 组合 两组更改,使这些组合更改应用于合并基础 * 的内容,并且如果一切顺利,Git 进行新的 合并提交 M,第一个 L parent 和 R作为第二个 parent:

...--o--*--o--L--M   <-- mainline
         \      /
          o----R   <-- sidebranch

比较(diffing)L vs M 将显示此合并从 *-vs-R 中获得的更改 [=42] =]-vs-L。也就是说,我们接了他们的工作。比较 RM 将显示此合并从 *-vs-L 获得的更改,这些更改尚未在 *-vs-R.也就是说,我们还保留了我们的工作。

-s ours 策略

-s ours 的作用非常简单。 Git 不用理会所有 diff-ing,而是让树远离 L。 Git 以 L 作为第一个 parent 和 R 作为第二个 parent 进行新的合并提交,因此 graph 看起来完全一样:

...--o--*--o--L--M   <-- mainline
         \      /
          o----R   <-- sidebranch

但是现在如果我们运行git diff mainline^1 mainline比较LM,输出是。如果我们运行git diff sidebranch mainline来比较RM,它们可以完全不同。 Git 完全忽略了 commit R 的内容:它已经 M 使用了 L.[=93= 的内容]

如果你称一个空差异为 "empty commit",那么 LM 就是这样一个空提交。但是仍然有 RM 作为这个合并提交的一部分,所以正如我们上面提到的,短语 "empty commit" 在这里没有真正意义。

长篇大论

如果存在从旧 tvfc b运行ch 到新 b运行ches 的现有合并提交,整个事情会更有意义:

  ...--o--M--o--<arbitrarily complex work>--I   <-- integration
         /
...--o--*   <-- tvfc

随着时间的推移变成:

  ...--o--M--o--<arbitrarily complex work>--I   <-- integration
         /
...--o--*--o--<arbitrarily complex work>-----X   <-- tvfc

在这种情况下,添加到 tvfc 本身的任何和所有新提交都可以简单地合并到 integration,其中 git checkout integration; git merge tvfc 作为 integrationtvfc 现在提交 *。 Git 将比较 *X 以查看它们更改了什么,并将其与 *I 相结合以查看您更改了什么。合并这些更改后,Git 将进行新的合并提交 N,将来新的合并基础将是 X 本身:

  ...--o--M--o--<arbitrarily complex work>--I--N   <-- integration
         /                                    /
...--o--*--o--<arbitrarily complex work>-----X   <-- tvfc

但这似乎不是你在做什么。我想知道您现在是否只是 设置 这种情况。如果是这样,上面的 git merge -s ours 命令可能就是您想要的。