Git 将此称为“壁球合并”,您可以通过运行git merge --squash 来获得它。与 Git 中的所有新提交一样,新提交将添加到当前分支中,因此在这种情况下,您将首先进入 master-branch,然后运行 git merge --squash feature-branch。
注意事项
对此有一些重要的事情需要了解。特别是,虽然与结果提交相关联的 tree(源或工作树)是通过 merging(动词:动作,“合并”)获得的,但它是不是合并提交(合并的形容词或名词形式)。
请记住,在 Git 中,提交有两个功能:
普通的git merge other 通过运行git merge-base --all HEAD other,1 找到一个合并基提交,然后:
结合两组差异:从基础到 HEAD 的差异,相当于“你改变了什么”,加上从基础到 other 的差异,相当于“他们改变了什么”。如果您更改了文件one.txt,而他们更改了文件two.txt,Git 会同时进行这两项更改,即您的one.txt 和他们的two.txt。如果您都更改了文件three.txt,Git 会尝试查看您的更改和它们的更改是否重叠,或者完全重复,或者完全独立。如果它们重叠,Git 会给你一个合并冲突。如果它们是独立的,Git 会同时进行这两项更改。如果它们是完全重复的,Git 会复制一份更改。生成的three.txt 是三路合并的输出。
创建一个新的提交,其历史说:“这个提交是一个合并提交,结合了第一个父级HEAD和第二个父级other的工作”。
git merge --squash other 仍然执行合并操作,包括任何所需的三向合并。然后 - 没有明显的原因2 - 省略了git commit 步骤并让你运行git commit,当你做运行git commit时,结果是不是合并提交:它的历史表明“这个提交是一个普通的提交,它的唯一父级是HEAD”。换句话说,它的提交历史就像是您自己对other 进行了所有更改,除了任何重复的更改。
这是你所要求的,但它也是一个陷阱。如果您稍后决定需要在 feature-branch 上进行更多开发并再次合并,Git 将不会有显示现有合并的历史记录,并且不会知道它可以使用以下命令启动新的合并过程上一次合并的结果。
这最终意味着,当您打算在 squash-merging 之后立即(或至少很快)删除功能分支时,squash 合并效果最好:
A---B----C <-- main
\
D--E--F--G <-- feature
(现在git checkout main && git merge --squash feature && git commit && git branch -D feature)
A---B----C--H <-- main
\
D--E--F--G [abandoned]
这样,您不能返回并添加更多的提交过去 G 使用 D-E-F-G 链稍后需要再次合并。如果您确实返回并进行此类提交,然后再次合并,则合并基础提交将是 A,而不是 G,并且 Git 更有可能错误合并,不小心复制了一些 D-E-F-G 更改。 3
1如果git merge-base --all 找到多个合并基,则行为取决于合并策略。如果它发现 no 合并基础,则旧版本 Git 和新版本 Git 中的行为不同:较新的 Git 会因拒绝合并不相关的历史记录而停止。
2常规合并也会停止,但只有在--no-commit 明确请求时才会停止。随后的git commit 将进行合并提交,即双亲提交。因此,git merge --squash 没有理由表现得好像您还指定了 --no-commit:如果您真的想要它停止,您可以运行 git merge --squash --no-commit。 (这很可能是最早的 Git 脚本的遗留物。)
3Git 的合并算法会努力仅获取每个更改的副本,但通常会成功仅获取更改的副本。也就是说,从 A 到 G 之后的任何内容的差异包括所有 D-E-F-G 更改,并且从 A 到 H 或之后的任何内容的差异也包括所有D-E-F-G 更改:但这就是“获取更改的副本”意味着要处理的内容。
问题在于,当你对这个算法施加太大压力时,它就会失败。通常,结果是一个巨大的合并冲突,您必须手动解决。