【问题标题】:How to squash two commits which are actually before a merge commit?如何压缩实际上在合并提交之前的两个提交?
【发布时间】:2015-07-03 08:59:31
【问题描述】:

假设以下历史:

   X - Y - M - Z   <- feature
 /        /
A - B - C - D      <- master

我想重写历史以将 X 和 Y 修复为单个提交。所以我希望历史看起来像这样:

    X' -   M'- Z'  <- feature
 /        /
A - B - C - D      <- master

到目前为止,我所有的尝试都失败了。大多数情况下,rebase 期间都会发生冲突。也许 rebase 不是实现这一目标的正确方法?

我知道(不知道合并之前的有效情况并没有真正改变)将合并从 master 重新应用到 f​​eature (M) 会导致我在第一个地方解决的相同冲突。命令“rerere”可能是解决此问题的一个选项,但据了解,这只有在首先激活“rerere”时才有可能。

但在这种情况下,X - Y 确实与 X' 具有相同的变更集。为什么 git 不够聪明,无法重新应用 M?如果我只是在单个提交 X' 中压缩 X 和 Y,则原始已解决的更改(存储在 M 中)应该再次成为 M' 的正确内容。我如何告诉 git 只使用 M 的旧内容来构建 M' ?

【问题讨论】:

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


    【解决方案1】:
    echo `git rev-parse Y A` >.git/info/grafts
    git filter-branch -- --all
    rm .git/info/grafts
    

    filter-branch docs

    grafts 是 repo-local 祖先覆盖。 git 中任何看到祖先的东西都会看到嫁接的祖先,特别是重写提交的东西会看到它,因此将其烘焙到重写的提交中。

    过滤器是 shell 脚本片段,如果你想提供一个新的提交消息,你可以例如

    git filter-branch --msg-filter="
            [ \$commit = `git rev-parse Y` ] && cat <&3 || cat" -- --all 3<<EOD
    your new commit message subject
    
    your new commit message body here
    ...
    EOD
    

    【讨论】:

    • 这听起来很有希望!我的存储库非常大,因此如果为完整存储库运行过滤器分支将持续大约 2 小时。但是嫁接需要在历史上一个相当近的领域进行。那么是否可以仅对那些受影响的提交运行 filter-branch 命令(某一点的所有子提交,包括该部分历史记录中的分支)?
    • 是的。 --allgit rev-list 参数,您可以指定 rev-list 将采用的任何内容。你可以说--all --ancestry-path Y^! 来获取 Y 和所有可以追溯到它的祖先,但在 Y 的历史中什么都没有。另外,如果你有一个不错的 tmpfs 可以使用,请使用 filter-branch 的 -d 选项。
    • 正确的命令是“--ancestry-path --all ^Y”。 “^Y”和“Y^!”的区别非常重要。请参阅stackoverflow.com/questions/31265126 了解整个故事...非常感谢!
    • 如果Y 的父母中有其他孩子Y^! 也将包括它们,但替代方案^Y 根本不起作用,因为它将排除@987654332 @ 本身,使过滤器分支成为无操作。
    • 我怀疑是这样,我唯一确定的是git rev-list --all --ancestry-path ^Y | grep $(git rev-parse Y),它不会为我打印任何内容,并且当Y 是唯一的嫁接提交时,git filter-branch -- --all --ancestry-path ^Y 在我这样做时保持所有引用不变.
    【解决方案2】:
    git checkout feature
    git checkout HEAD~2
    git rebase -i HEAD~2
    

    Squash 最后两次提交。然后用git cherry-pickMZ重写历史。

    希望对您有所帮助。

    【讨论】:

    • 虽然这没有冲突,但它不能正确反映合并提交。合并提交“M”的樱桃选择将创建一个非合并提交 M'。
    • 我认为您必须将master 移回C,并且在压缩feature 的两个提交之后,您必须将它与master 合并。然后在featuremaster 上分别挑选ZD
    • 是的,但这会迫使我再次解决冲突。我正在寻找一种解决方案来重写长特性分支的历史,并在两者之间进行几次合并。所以我希望有一个干净、直接的解决方案;-)
    【解决方案3】:

    在我发现 hack 时回答我的问题。我希望 git 可以做得更好/更容易,所以我仍然在寻求更好的解决方案......

    技巧是在不提交的情况下再次开始合并。然后将原始合并(M)中的所有更改读入索引并完成合并。重置对于清除工作目录中的冲突文件是必要的。之后继续采摘Z ...

    git checkout -b featureRewritten A
    git cherry-pick X
    git cherry-pick -n Y
    git commit --amend           #this will actually fixup Y into X
    git merge -n C               #start the merge without commit
    git read-tree M              #throw away the staged merge results by
                                 #replacing them with the original solution
    git commit                   #finish commit. need to change the automsg?
    git reset --hard HEAD        #throw away any left overs
    git cherry-pick Z            #continue
    

    长期的艰苦工作...更好的解决方案?

    【讨论】:

      【解决方案4】:

      另一个解决方案(基于 jthill 的想法):

      git checkout Y
      git rebase -i A
      #i-rebase: reword X
      #i-rebase: fixup Y
      git replace Y HEAD
      git filter-branch -- --all --ancestry-path Y^!
      git replace -d Y
      

      首先结帐到分离的 Y(以 Y 结尾的未命名分支)。交互式变基将 Y 合并到 X 中并重新表述 X 的消息。结果是一个新的提交 N。HEAD 当前指向它。现在 Y(仍然是分支功能的一部分)被 N(HEAD)替换。使用过滤器分支永久化。去掉 Y 的替换,去掉 N。

      【讨论】:

        猜你喜欢
        • 2023-01-12
        • 2017-09-26
        • 2021-03-26
        • 2018-06-01
        • 2015-07-20
        • 1970-01-01
        • 2018-11-05
        • 2023-02-23
        • 2016-05-22
        相关资源
        最近更新 更多