git - 我可以与它的前辈之一压缩 merge 提交吗?

标签 git git-merge git-rewrite-history

假设我从这样的历史开始:

A---B---C
 \
  \-D---E

然后,我 merge 了两个分支,解决了过程中的一些冲突:
A---B---C---F
 \         /
  \-D---E-/

但之后,我想重写历史,看起来像这样:
A---B---C---EF
 \         /
  \-D-----/

换句话说,我想从分支提交 E 移动更改 merge 提交 F .好像我已经在 E 中进行了更改同时通过 merge 手动解决冲突 D进入 C .

(我的实际原因是 E 只是一个 merge 准备提交,使预期的冲突更容易解决。它本身没有多大值(value),我不希望它污染历史。我不'不想将 E 压缩到它的前身中,因为它的变化在逻辑上是分开的。)

我试过:
git rebase -i --rebase-merges HEAD~10

但这给了我:
...
pick 1069500 ...
merge -C 44c0a69 ...

# s, squash <commit> = use commit, but meld into previous commit
# ...
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.

所以看起来提交可以是 merge 提交,或者 git rebase -i可以将其压缩为它的前任,但不能同时压缩。和 git rebasemake me reapply my manual conflict resolutions无论如何,这有点臭。

还有另一种方法吗?

最佳答案

TL; 博士

使用 git commit-tree使用 F 进行新的 merge 提交的快照和您选择的 parent ,然后强制您的分支名称指向那里:

newcommit=$(git commit-tree -p HEAD^ -p HEAD^2^ HEAD^{tree} -F /tmp/msg)

哪里/tmp/msg包含您要使用的提交消息。然后使用 git reset移动当前分支/提交到 $newcommit ,在检查这个提交看起来像你想要的样子之后(例如 git log --graph --oneline $newcommit)。

(这里假设一个类 Unix 的 shell,shell 变量设置为 var=value$(...) 以运行命令,依此类推。)



提交不存储更改。他们存储快照。

对于 merge 提交也是如此:它们的快照是快照。它们以每个文件的 merge 形式保存每个文件的完整副本。

所以假设你有:
A--B--C--F
 \      /
  D----E

(这是您的原始绘图,我只是将提交推到了我更喜欢的样式)并且希望拥有:
A--B--C--G
 \      /
  D----'

(我刚刚在这里使用了一个全新的字母 merge G,但这与您的 EF 相同)。 merge 提交的快照 G必须与 merge 提交的现有快照完全匹配 F . merge 提交的第一个父级 G必须提交 C — 与现有提交的第一个父级相同 F ——第二个父级必须提交 D , 跳过提交 E ,但最重要的是 G 的快照应该与您已经使用 merge 提交制作的快照完全相同 F .

现在,您可以轻松生成此图:
        ___
       /   \
A--B--C--F  G
 \      /  /
  D----E  /
   \_____/

从您已有的图表中,通过进行新的提交 G . git commit-tree命令正是这样做的。

您必须向 git commit-tree 提供三样东西:
  • 一组父哈希 ID,或解析为哈希 ID 的名称。您需要提交的 ID CD ,您可以使用 HEAD^1 拼写和 HEAD^2^1分别。每个1可以省略。这些是 -p论据。
  • 新提交的树哈希 ID。自 HEAD提交 F它的树就是你想要的,HEAD^{tree}指定。
  • 提交日志消息,带有 -m-F或来自标准输入。

  • 提交树程序写出新的提交对象并打印其哈希 ID,我们希望使用 shell 变量以人类可读的形式捕获它。

    完成后,我们只需要更新当前分支名称。由于此分支当前已 check out ,我们将使用 git reset :
    git reset $newcommit
    

    我们可以 git reset --soft避免更改当前索引,或 git reset --hard更改索引和工作树,但如果当前索引和工作树与现有 merge 提交中的快照匹配 F , merge 中的快照 G无论如何都是完全一样的,所以没有真正需要任何一个标志。默认--mixed reset 将重置索引,使工作树不受干扰。 (不过,如果你仔细地安排了一些东西,你可能想要 --soft 。)

    提交 F将继续存在,但没有名字找到它,你不会看到它——最终,一旦保存它的 reflog 过期,Git 的垃圾收集器将抛出提交 FE .

    关于git - 我可以与它的前辈之一压缩 merge 提交吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61906251/

    相关文章:

    git - 使用 git-revert 撤消推送 merge 的提交哈希?

    Git:如何将 "undo" merge

    git - 如何覆盖 git merge 中的 merge-base?

    git - 从 git repo 和提交历史中递归删除所有二进制文件

    git - 如何从 git 存储库中删除旧版本的媒体文件

    预推钩中的 git commit

    python - 如何编写 python 预推送来检查提交消息?

    git - 错误 : object file is empty . git/objects/../.. 是空的——致命的 : loose object . .. 已损坏

    svn - TFS 用户的 Git 或 Subversion

    git - 在 filter-branch --tree-filter 之后从 git repo 中删除 refs/original/heads/master?