如何在合并期间覆盖基础?

How to override base during merge?

问题

看图。我有一个 master 分支。在提交 A 时,我从中分支了 dev 分支。在 B 点,我将 devmaster 同步,创建了 M1。在 C 点,我们的团队从中分支了 release 个分支。将来,我需要将 dev 合并回 release

不幸的是,我不小心将提交 Dmaster 合并到我的 dev 分支,创建了 M2。现在我无法将 dev 合并到 release,因为它包含属于 master 的提交 C..D,不应转到 release

我的开发还没有结束,我现在不打算将 dev 合并到 release。但是,我想保持同步并将 release 合并到我的 dev 分支。我希望在我完成开发并将 dev 合并到 release.

之前,这种合并在未来还会发生几次

因此,在某些时候我需要从 dev 恢复 M2。我想尽早完成,因为来自 release 的提交可能会与 M2 中的更改发生冲突。请记住,其中一些更改在 release.

中不存在

因为我想尽早恢复 M2,所以我想在从 release 合并到 dev 之前完成。这就是问题真正开始的地方。感谢您阅读到这里:)

我可以在 dev 中还原 M2,这不是问题。但是,在那之后,当我将 release 合并到 dev 时,git 将合并基础计算为 C。虽然我希望合并基础为 B,但假装合并 M2 根本没有发生。由于这个不正确的基础,更改 B..C 实际上会被合并自动丢弃。 Git 认为,我手动删除了它们,因为这是他在 M2!

的还原提交中看到的

让我澄清一下。假设有人在 C 中创建了文件 foo.txt。此文件将添加到 devM2。一旦我恢复 M2,它将从 dev 中删除。当我将 release 合并到 dev 时,git 在 release 中看到 foo.txt,它在基本提交 C 中看到 foo.txt,但是它在 dev 中看不到 foo.txt。因此 git 认为我删除了 foo.txt。但是我没有做。

如果我指定B作为合并基础就没有问题了。在 git 中有没有办法做到这一点?

我的解决方案

因为我找不到覆盖合并基础的方法,所以我做了一些修改。我 post 在这里是因为我有另一个相关的问题。

见第二张图片。我从 E 开始了新的本地分支 tmp,在 M2 之前提交。我 cherry-picked 到此分支的所有更改来自 dev 除了 M2。我将 release 合并到 tmp 并提交结果 M3,只有 tmp 中的一个 parent(注意图像上的虚线)。

我在 dev 中恢复了 M2,提交 G。我创建了 "fake" 从 releasedev 的合并。此提交不包含任何更改,但有两个 parents:来自 devrelease。然后我 cherry-picked M3dev 并用我的空假合并压缩它。因此,我创建了 M4,具有正确的更改和正确的 parents.

问题是:我真的需要这个 "fake" 合并吗?也许有一种方法可以将 cherry-pick M3 转换为 dev 并使其具有两个 parent?

问题

我会在这里总结问题:

感谢您能够阅读本文!

更新

正如我在讨论后意识到的那样,我的解决方案(或针对所述问题的任何其他解决方案)有一​​个关键缺陷。正如我所说,在某些时候我会将 dev 合并到 release。正如我没有说过的那样,在那之后的某个时候我们会将 release 合并到 master。在这一点上,我们将面临完全相同的问题。此合并的基础将解析为 D,而不是 C。这将导致类似的问题,但规模要大得多。

因此,解决此问题的最佳方案是在 tmp 中继续开发,并使其成为新的 dev,有效地从历史记录中排除不良合并 M2

我认为您可以在分支 dev 中执行 interactive rebase 并从中删除 DM2

$ git checkout dev
$ git rebase -i <commit-sha-of-E>

一个新的window将会出现。现在只需删除 DM2 的提交行,然后保存并退出。

$ git rebase --continue           # finish your rebase.   

我很确定你的两个底线问题的简短答案是否定的:

  • 您绝对不能手动设置提交的基础,无论如何,这没有任何意义。 GIT 如何知道将 'us' 分支中的现有提交与其他分支中的提交相关联? (假设它在 C 之后的另一个分支上,只是为了让它变得困难)。
  • 使用 git reset 是有可能的,尽管这是一种变通方法。你git reset <wanted base>。当前提交和新基础之间的所有更改都未在工作区中提交,因此您可以根据需要进行新提交(尽管您可能会丢失一些您可能想要的树信息)。

在任何情况下,您都可以在 M1 周围放置 dev 分支以摆脱它:

git checkout dev
git rebase -i <commit prior to M1 or even to A>

-i 将允许删除合并提交。

编辑

您在 dev 中的解决方法的最终提交可能看起来像您想要的,但您必须记住在 git 中,如果下面有一行连接到您,那么这些提交就是您的一部分以及。您所做的是 精心 重新设置删除 M2,因此提交 C..D 不会在任何父项中被考虑。

如果你可以告诉git将dev合并到基于B的release,那么我认为以下是一个很好的估计会发生什么:

  1. GIT 看到 B..C 是共享的,就跳过它们。
  2. M1 和 M2 之间的添加合并到 dev
  3. 这里还有M2!所以将 C..D 合并到 dev。不管你从 B 开始,你都会到达这里!
  4. 然后继续剩下的。

如果层次结构中存在 M2,则无法绕过 C..D。但是,您可以通过还原或变基来跳过它,或者绕过您所做的疯狂分支。

简短的回答是 "no, you can't pick your own merge base",您的临时分支方法是正确的。那么现在让我们来回答第二个问题:

Is there a way to manually specify parents for a commit?

这里的答案是响亮的,但是您必须使用Git的"plumbing tools"来做到这一点,特别是git commit-tree

git commit 创建一个新提交时,它会执行以下操作(当然,在收集您的提交消息等之后):

  1. 将索引写入树:git write-tree。这会打印出新树的哈希 ID。
  2. 使用生成的树创建一个新提交:git commit-tree <em>args</em>(见下文) .这会打印出新提交的哈希 ID。
  3. 更新当前分支以指向新提交:git update-ref -m "commit: <em>subject</em>" HEAD <em>commit-hash</em>.

第 2 步是我们可以为提交选择 parent 的地方。 commit-tree 命令需要一个必需的参数,即树 ID(由步骤 1 生成),每个 parent ID 设置一个 -p 参数。每个 -p 都有一个提交 parent 说明符。这可以是原始哈希,或 gitrevisions 可接受的任何内容。事实上,树 ID 也可以这样写,如果你只是想创建一个 new commit 使用 same source tree 作为一些现有的提交。

因此,假设您有一个当前源代码树,在分支 X 下,指向提交 CX。您现在想要将 X 合并到分支 dest,但您希望合并的行为就好像 CX 的 parent 提交是 P,这样 git 将使用 git merge-base --all <em>P</em> dest 到找到合并基地。 (如果您已经知道所需的合并基础 B,您可以简单地选择 P = B。)

我们需要的是一个提交,其树与 CX 相同,但其(单个)parent 是 P .这个提交甚至不必在任何分支上,它只需要存在于存储库中。如果你想让它在一个分支上,使用配方中的 git branch

X=dev                                        # name of desired branch
P=<hash ID for commit P>
git show $P                                  # just to check
cat > /tmp/msg << END
temp commit of $X's tree from $(git rev-parse $X)

This is a special commit made with parent $P
just for doing a merge.
END
newcommit=$(git commit-tree -p $P $X^{tree}) < /tmp/msg
# git branch tmp $newcommit                  # save newcommit as new branch
git checkout dest
git merge $newcommit                         # or git merge tmp

(以上均为未经测试)

这里用$X^{tree},不用做很多复杂的cherry-picking。当然,如果您需要 省略 特定的提交来构建一个新的(不同的)树,您确实需要一个临时分支并做一些 cherry-picking.