如何将Git仓库的前两个提交合并?

231

假设您有一个包含三个提交 A,B 和 C 的历史记录:

A-B-C

我想将两个提交记录 AB 合并成一个提交记录 AB

AB-C

我尝试过

git rebase -i A

这将打开我的编辑器,并显示以下内容:

pick e97a17b B
pick asd314f C

我将这个更改为

squash e97a17b B
pick asd314f C

然后 Git 1.6.0.4 会显示:

Cannot 'squash' without a previous commit

有没有解决方案,还是说这是不可能的?


参见:如何在Git中编辑根提交? - user456814
参见:在Git中压缩前两个提交? - user456814
9个回答

216

在Git 版本1.7.12及以上,可以使用git rebase -i --root命令。

在交互式rebase文件中,将第二行提交的B改为squash,而其他行保留为pick

pick f4202da A
squash bea708e B
pick a8c6abc C

这将把两个提交 AB 合并为一个提交 AB

来源于这个回答


126

你尝试过:

git rebase -i A

如果您继续使用 edit 而不是 squash,那么可以像这样开始:

edit e97a17b B
pick asd314f C

然后运行

git reset --soft HEAD^
git commit --amend
git rebase --continue

完成。


4
如果你想悄悄地修复一个 GitHub Gist,你需要在提交时添加“-m“initial””。;-) - Bruno Bronosky
1
git rebase --abort 可以重新开始并以正确的方式进行操作(不要在编辑器中压缩第一个提交)。 - oma

69

A是最初的提交,但现在你想让B成为初始提交。Git提交是整个树,而不是差异,即使它们通常被描述和查看为引入的差异。

即使在A和B之间以及B和C之间存在多个提交,此方法也适用。

# Go back to the last commit that we want
# to form the initial commit (detach HEAD)
git checkout <sha1_for_B>

# reset the branch pointer to the initial commit,
# but leaving the index and working tree intact.
git reset --soft <sha1_for_A>

# amend the initial tree using the tree from 'B'
git commit --amend

# temporarily tag this new initial commit
# (or you could remember the new commit sha1 manually)
git tag tmp

# go back to the original branch (assume master for this example)
git checkout master

# Replay all the commits after B onto the new initial commit
git rebase --onto tmp <sha1_for_B>

# remove the temporary tag
git tag -d tmp

1
当我执行 git rebase --onto tmp <sha1_for_B> 时,这将触发一个巨大的交互式变基。 - Alex
考虑到我有一个全新的仓库,只有两个提交(我想将它们合并为一个),这对我来说完美地解决了问题。谢谢@CB Bailey。 - RominRonin

14

如果你有数百或数千个提交,可以使用kostmo的答案

git rebase -i --root

由于rebase脚本必须处理大量提交,需要处理两次,一次用来生成交互式rebase编辑器列表(您可以选择每个提交要执行的操作),另一次用于实际执行提交的重新应用,因此它可能不切实际且速度很慢。

这里有一个替代方案,可以避免生成交互式rebase编辑器列表所需的时间成本,通过不使用交互式rebase来实现。这种方式与Charles Bailey的解决方案类似。您只需从第二个提交创建一个孤立分支,然后将所有后代提交重新基于该分支:

git checkout --orphan orphan <second-commit-sha>
git commit -m "Enter a commit message for the new root commit"
git rebase --onto orphan <second-commit-sha> master

文档


10

在交互式变基的情况下,你必须在 A 之前执行它,这样列表将会是:

pick A
pick B
pick C

变成:

pick A
squash B
pick C

如果A是初始提交,则需要在A之前有一个不同的初始提交。Git思考差异,它会处理(A和B)以及(B和C)之间的差异。因此,在您的示例中无法进行合并。


1

这里是否也重复回答会更好呢?我不确定。 - user456814

0

小组使用的Git命令: git rebase -i HEAD~[提交次数]

假设您有以下git提交历史记录:


pick 5152061 feat: 添加支持保存图像。 (A)
pick 39c5a04 Fix: 错误修复。 (B)
pick 839c6b3 fix: 冲突已解决。 (C)

现在您想将A和B压缩为AB,执行以下步骤:


pick 5152061 feat: 添加支持保存图像。 (A)
s 39c5a04 Fix: 错误修复。 (B)
pick 839c6b3 fix: 冲突已解决。 (C)

注意:对于压缩提交,我们可以使用squash或s。 最终结果将是:
pick 5152061 feat: 添加支持保存图像。 (AB)
pick 839c6b3 fix: 冲突已解决。 (C)


0
我也在想,如果结构是这样的话会怎么样。
X-Y------Z
 \-A-B-C/

我希望最终的结构是这样的。
X-Y-----Z
 \-AB-C/

在这种情况下,我如何将A和B结合起来?

根据目前的写法,你的回答不够清晰。请编辑以添加更多细节,帮助其他人理解这如何回答所提出的问题。你可以在帮助中心找到关于如何撰写好回答的更多信息。 - Community
根据目前的写法,你的回答不够清晰。请编辑以添加更多细节,帮助他人理解这如何回答所提出的问题。你可以在帮助中心找到关于如何撰写好回答的更多信息。 - undefined

-1

你需要进行一些命令行魔法。

git checkout -b a A
git checkout B <files>
git commit --amend
git checkout master
git rebase a

这样你就会得到一个含有AB和C commits的分支。


由于旧和新的初始提交没有共同的祖先,因此当git尝试将master的整个历史记录应用于a时,您可能会遇到一些不必要的冲突,即使它们有一个共同的树。使用--onto选项进行git rebase,您可以告诉git正确开始应用的位置。 - CB Bailey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接