【问题标题】:Can I squash a merge commit with one of its predecessors?我可以用它的前辈之一压缩合并提交吗?
【发布时间】:2020-05-20 06:10:55
【问题描述】:

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

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

然后,我合并了两个分支,解决了过程中的一些冲突:

A---B---C---F
 \         /
  \-D---E-/

但之后,我想重写历史,使其看起来像这样:

A---B---C---EF
 \         /
  \-D-----/

换句话说,我想将更改从分支提交E 移动到合并提交F。好像我在手动解决将 D 合并到 C 的冲突时对 E 进行了更改。

(我的实际原因是E 只是一个合并准备提交,以使预期的冲突更容易解决。它本身没有多大价值,我不希望它污染历史. 我不想将 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.

所以看起来提交可以是合并提交, git rebase -i 可以将其压缩到其前身中,但不能两者兼而有之。无论如何,git rebase 会是 make me reapply my manual conflict resolutions,这有点臭。

还有其他方法吗?

【问题讨论】:

    标签: git git-merge git-rewrite-history


    【解决方案1】:

    TL;DR

    使用git commit-treeF 的快照和您选择的父级进行新的合并提交,然后强制您的分支名称指向那里:

    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,用var=value$(...) 设置 shell 变量来运行命令,等等。)

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

    合并提交也是如此:它们的快照是快照。他们拥有每个文件的完整副本每个文件的合并形式

    所以假设你有:

    A--B--C--F
     \      /
      D----E
    

    (这是您的原始绘图,我只是将提交稍微推向了我更喜欢的风格)并且想要:

    A--B--C--G
     \      /
      D----'
    

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

    现在,您可以轻松制作这个图:

            ___
           /   \
    A--B--C--F  G
     \      /  /
      D----E  /
       \_____/
    

    从你已有的图表中,通过新的提交Ggit commit-tree 命令正是这样做的。

    你必须向git commit-tree提供三样东西:

    • 一组父哈希 ID,或解析为哈希 ID 的名称。您需要提交的 ID CD,您可以分别使用 HEAD^1HEAD^2^1 拼写。每个1 都可以省略。这些是-p 参数。

    • 新提交的树哈希 ID。由于HEAD 是提交F 并且它的树是你想要的,HEAD^{tree} 指定它。

    • 提交日志消息,带有-m-F 或来自标准输入。

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

    完成后,我们只需要更新当前分支名称。由于该分支目前已签出,我们将使用git reset

    git reset $newcommit
    

    我们可以git reset --soft 来避免更改当前索引,或者git reset --hard 来更改索引和工作树,但是如果当前索引和工作树与现有合并提交中的快照匹配F,则快照在合并G 中无论如何都是完全相同的,所以实际上不需要任何一个标志。默认的--mixed reset 将重置索引,使工作树不受干扰。 (不过,如果你精心布置了一些东西,你可能想要--soft。)

    Commit F 将继续存在,但没有找到它的 name,您将看不到它——最终,一旦保留它的 reflog 过期,Git 的垃圾收集器将放弃提交 FE

    【讨论】:

      【解决方案2】:

      我会这样做:

      git checkout $( git commit-tree -p C -p D -m "some merge" F^{tree} )
      

      您最终将在修订版中分离 HEAD,就像您要求的一样(检查 git log 或 gitk,检查文件)....如果您喜欢它:

      git branch-f some-branch
      git checkout some-branch
      

      【讨论】:

        猜你喜欢
        • 2017-09-26
        • 2010-12-16
        • 1970-01-01
        • 2023-01-12
        • 2021-09-18
        • 1970-01-01
        • 1970-01-01
        • 2016-08-28
        • 2018-06-01
        相关资源
        最近更新 更多