在保留对 master 的引用的同时,使用 git 将 master 中的一系列小合并压缩到我的分支中?

Squashing a sequence of small merges from master into my branch with git while keeping reference to master?

我有一个非常复杂的合并要做。部分问题在于我让太多时间过去了,所以合并到我的分支中的更改量是巨大的。

为了让事情更简单,我选择了 git merge origin/master~20git merge origin/master~17git merge origin/master~15 等,这样我就可以零碎地解决冲突,而不必一次性全部拿走

问题是这会导致我想删除的日志历史记录污染。合并所有这些提交的最佳方法是什么,同时仍然保持结果提交指向我的分支和主分支?

我通常使用 git reset --soft 压缩,但这不会留下对 master 分支的引用。我也尝试 git rebase -i --preserve-merges 但我收到 "Refusing to squash a merge" 错误消息。

我应该如何进行?

让我这样描述你的情况:你有你想要的合并结果——源代码树——但没有你想要的历史想要导致这个结果。

作为 VonC has put it elsewhere,并且您自己尝试过,git reset --soft 通常 的答案。您进行软重置,然后进行新的提交。如果此时您可以进行 merge 提交,它仍然是答案。

无需 git rerere 即可通过三种简单的方法完成此操作。一种是作弊,其中一种方法是 "documented cheating",因此可能是正确的方法。 (我更喜欢中间的方法,因为它又短又甜,但显然是在作弊,所以有一天它可能会失效。)

方法 1:笨重,但使用所有普通工具(无作弊)

请注意,此处的命令序列假设您位于存储库的顶层(特别是下面的 git rmgit checkout 步骤引用 . 表示 "everything").我还使用 $startpoint 作为您想要合并的提交,并使用 $other 来引用另一个 b运行ch-name 或提交 ID(您想要 git merge).

  1. 保存最终结果的ID(我们想要,但是标准工具最容易只引用提交,这也有效很好):

    $ git tag temp-save-result
    

    (或使用剪切和粘贴或 reflog 来保存它;为了简单起见,我在这里显示一个标签)。

  2. 重置。这也可能是 --hard,而不是 --soft:

    $ git reset --hard $startpoint
    
  3. 运行 合并,将因冲突而失败。忽略冲突并 删除整个索引和工作树。 我们不希望有冲突的合并,或者到目前为止的任何临时结果,因为我们有 正确的 其他地方的结果。

    $ git merge $other
    $ git rm -r .
    

    (如果您有一些自定义合并工具留下了粪便,您可能还想在这里将它们从工作树中清除,尽管它们不会影响任何重要的事情:它们只会让您的工作变得混乱工作树)。

  4. 提取步骤1中保存的工作树,并提交结果:

    $ git checkout temp-save-result -- .
    $ git commit
    

此提交结束合并,其 tree 来自您在步骤 1 中保存的树。您现在可以删除标签:

$ git tag -d temp-save-result

方法二:作弊

git commit 进行新提交时,如果 .git/MERGE_HEAD 存在,它会进行合并提交。 MERGE_HEAD 文件包含第二次提交的 ID,即正在提交的 otherremote--theirs合并。

因此,我们只需像往常一样进行软重置,然后添加合并 ID,然后提交。 (注意:我最近没有尝试过这个,Git 可能也想要 .git/MERGE_MSG。准备好需要调整作弊,或者继续使用方法 3。)

$ git reset --soft $startpoint
$ git rev-parse $other > .git/MERGE_HEAD
$ git commit

第一个命令是我们通常的 git reset --soft 步骤,第二个命令 Git 表示我们正在解决冲突合并(并且索引已全部解决,所以我们必须完成执行该步骤),git commit 现在提交合并。

方法 3:使用管道命令 ("documented cheating")

创建实际提交对象的命令——git commit 无疑曾经只是一个 shell 脚本,运行 它接近尾声——是 git commit-tree。它需要:

  • 包含所需树的树对象
  • 一条提交消息(它将从标准输入中读取一条,但您可能应该使用 -m-F
  • 新提交的父项的父项 ID

并将新的提交对象写入存储库,并打印出对象的哈希 ID。

我们已经这棵树了!因为它是当前提交,所以它的 ID 是 HEAD^{tree}(使用 gitrevisions syntax)。我们也有两个父 ID。我们只需要消息:

$ tmp=$(git commit-tree -p $startpoint -p $other -m 'merge msg' HEAD^{tree})

一旦我们有了提交 ID,我们只需要 git reset --hard 我们当前的 b运行ch 指向它:

$ git reset --hard $tmp

(当然,您可以在第二个命令中使用 $(...) 而不是 $tmp 将两者组合成一个大命令,尽管这假设您的 git commit-tree 命令会起作用:它是为了个人舒适,最好执行两个步骤。如果您更喜欢,可以通过剪切和粘贴哈希 ID 来省略 $tmp 变量和相应的 shell $(...) 语法。而且,您可以输入一个俗气的临时提交消息,然后在重置后使用 git commit --amend 对其进行编辑:--amend 选项也适用于合并提交。)