如果我已经开始 rebase,如何将两个提交合并为一个?

1354

我想把两个提交合并成一个,所以我按照“使用rebase压缩提交”来自git ready的步骤进行操作。

我运行了

git rebase --interactive HEAD~2

在生成的编辑器中,我将pick更改为squash,然后保存并退出,但是变基失败,并显示以下错误消息:

无法“squash”没有先前提交的内容

现在,我的工作树已经达到了这种状态,我很难恢复。
使用命令git rebase --interactive HEAD~2失败,并出现以下消息:

交互式变基已经开始

git rebase --continue则失败,并显示以下消息:

无法“squash”没有先前提交的内容


25
我也遇到过这种情况。我的错误是因为git rebase -i命令列出的提交记录和git log命令相反;最新的提交记录在底部! - lmsurprenant
1
还有请查看:http://git-scm.com/book/zh/v2/Git-Tools-Rewriting-History - nha
我在尝试一些疯狂的东西之前,总是会创建一个备份分支。只希望在生活中也能这样运作 ;) - RayLoveless
你可以通过按下ESC键并输入:wq!来退出编辑器。 - Rafaël Moser
15个回答

1954

概述

错误信息为:

没有先前的提交,无法“压缩”

这意味着您可能尝试“向下压缩”。Git总是将较新的提交压缩到较旧的提交中或者根据交互式变基待办事项列表的视图“向上”压缩,即压缩到前一行上的提交。将待办事项列表的第一行命令更改为squash,将始终产生此错误,因为第一个提交没有可以压缩的内容。

解决方法

首先回到您开始的位置。

$ git rebase --abort

假设你的历史记录是:

$ git log --pretty=oneline
a931ac7c808e2471b22b5bd20f0cad046b1c5d0d c
b76d157d507e819d7511132bdb5a80dd421d854f b
df239176e1a2ffac927d8b496ea00d5488481db5 a

也就是说,a 是第一次提交,然后是 b,最后是 c。在提交 c 后,我们决定将 b 和 c 合并:

(注意:运行 git log 会将其输出传输到分页程序中,在大多数平台上默认使用的是 less。要退出分页程序并返回命令提示符,请按下 q 键。)

运行 git rebase --interactive HEAD~2 将会打开一个编辑器:

pick b76d157 b
pick a931ac7 c

# Rebase df23917..a931ac7 onto df23917
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

请注意,此待办事项列表与git log输出相比顺序相反。

将 b 的 pick 改为 squash 将导致你看到的错误,但是如果你将 c 合并到 b 中(较新的提交合并到较旧的提交或“向上压缩”),则需要更改待办事项列表。

pick   b76d157 b
squash a931ac7 c

保存并退出您的编辑器后,您将获得另一个编辑器,其内容为

# This is a combination of 2 commits.
# The first commit's message is:

b

# This is the 2nd commit message:

c

保存并退出时,编辑文件的内容将成为新合并提交的提交消息:

$ git log --pretty=oneline
18fd73d3ce748f2a58d1b566c03dd9dafe0b6b4f b and c
df239176e1a2ffac927d8b496ea00d5488481db5 a

关于重写历史的注意事项

交互式变基将重写历史。试图推送到包含旧历史记录的远程仓库会失败,因为它不是快进。

如果您变基的分支是自己工作的主题或特性分支,那就没什么大不了的。将其推送到另一个仓库需要使用--force选项,或者根据远程仓库的权限,您可以首先删除旧分支,然后再推送经过变基的版本。这些可能会破坏工作的命令范例超出了本回答的范围。

在与他人共同合作的分支上重新编辑已发布的历史记录,除非有非常好的理由(例如泄露密码或其他敏感细节),否则会让您的合作者承担额外的工作,是不良社交行为并会使其他开发人员感到烦恼。Git变基文档中的“从上游变基恢复”部分进行了解释,并强调了这一点。

对于其他人已经依赖的分支进行变基(或任何其他形式的重写),是个坏主意:任何下游使用这些分支的人都必须手动修复他们的历史记录。这一节从下游的角度解释了如何进行修复。但是,真正的解决方案应该是避免在第一时间内对上游进行变基。


如果我使用rebase来压缩一个commit,那么将会创建一个新的“合并”的commit,其中包含了两个变更集,但是哈希值不同。原始的commits也会被Git保留吗? - fabsenet
1
@fabsenet 是的和不是。原始提交仍然可以访问,但可能无法从任何引用中访问(取决于您历史记录的特定情况)。未引用的提交最终会通过垃圾收集过程清除。 - Greg Bacon
我只是在玩... 我执行了 git log hashoftheoldcommit,它可以工作,但我很好奇想看到一个包括所有这些不可达提交的 git log --graph - fabsenet
2
@Mithril git rebase --interactive HEAD~3,然后将c行移动到a行下面。将c行的pick更改为s,然后退出编辑器。 - funroll
@GregBacon 怎样“保存并退出”?我正在Android Studio终端执行这些命令,但只有一个关闭图标可以关闭窗口,如果你再次运行rebase,它会说请继续、停止、中止上一个会话。所以我认为关闭图标不是我想要的保存并退出方式。 - Dr.jacky
显示剩余8条评论

510

如果有多个提交,你可以使用 git rebase -i 命令将两个提交合并为一个。

如果只想合并最近的两个提交,可以使用下列命令将它们合并成一个:

git reset --soft "HEAD^"
git commit --amend

8
与变基相比有哪些不利之处?我发现其中一个使用起来要简单得多。 - Guillaume86
20
你不能随意加入 - 只能加入最后两次提交 - dr0i
57
你可以合并尽可能多的提交,只要它们是最新的X个提交,而不是在中间某处。只需运行git reset --soft HEAD~10,其中10是你想要合并的提交数量。 - fregante
11
如果您不想从HEAD开始数有多少次提交,您还可以使用 git reset --soft 47b5c5... 命令将代码重置到特定的提交点,其中 47b5c5... 是提交点的 SHA1 ID。请注意,这个命令不会改变原始提交点的内容。 - dguay
2
请添加说明可能需要使用 git push -f origin master 命令。 - Rishabh Agrahari
显示剩余6条评论

189

变基:你不需要它

对于大多数常见情况,有一种更简单的方法。

在大多数情况下:

实际上,如果你只想将几个最近的提交合并成一个,但不需要进行dropreword或其他变基操作,那么你可以简单地执行以下操作:

你只需执行以下操作:

git reset --soft "HEAD~n"
  • 假设~n是需要软撤销的提交数量(比如~1~2等)

然后,使用以下命令修改提交信息。

git commit --amend

这与一系列的压缩和一个挑选基本相同。

它适用于n次提交,而不仅仅是像上面回答中提到的两次提交。


3
如果您希望除了压缩提交之外进行其他清理,例如删除中间的一个提交或更改一行代码,那么这很不错。 - styfle
2
假设~n是要轻松取消提交的提交次数(即~1~2等)。 - Ray
1
如果我想合并的不是最后 n 个提交,而是中间的 n 个提交呢?我能轻松地做到这一点吗? - chumakoff
1
然后,如果您需要执行“压缩”工作,则需要运行 git rebase -i 命令。@chumakoff - pambda
YanTing_ThePanda,然后git rebase -i 是你需要压缩工作的方法,你能在回答中展示一个包含 n 个提交的示例吗?感谢你对问题的最佳回答。 - Саша Черных
6
将最近的n个提交合并为一个,首先使用命令git reset --soft @~m,其中m=n-1 - Łukasz Rajchel

59

首先,您应该检查您有多少个提交记录:

git log

有两种状态:

一种是仅有两个提交记录:

例如:

commit A
commit B

在这种情况下,你不能使用git rebase来完成操作,你需要按照以下步骤进行。

$ git reset --soft HEAD^1

$ git commit --amend

另一个原因是有两个以上的提交, 您想要合并提交C和D。

例如:

commit A
commit B
commit C
commit D

在这种情况下,你可以使用 git rebase。

git rebase -i B

然后使用 "squash" 命令即可。其余的步骤非常简单。如果您还不了解,请阅读http://zerodie.github.io/blog/2012/01/19/git-rebase-i/


如果您已经在进行rebase(并选择了“编辑”而不是“压缩”此提交),则reset --soft和commit --amend是唯一有效的方法。+1 - Jacek Lach
2
合并存储库中的第一个和仅有的两个提交,正是我的特殊情况 :-) - Chris Huang-Leaver
1
请添加说明可能需要使用 git push -f origin master 命令。 - Rishabh Agrahari

47
假设你已经在自己的主题分支上。如果你想将最后两个提交合并成一个,看起来像个英雄,那么请基于最后两个提交之前的提交(使用相对提交名称HEAD〜2)创建一个新分支。
git checkout -b temp_branch HEAD~2

然后将另一个分支压缩提交到这个新分支中:

git merge branch_with_two_commits --squash

这将会带来一些变化,但不会提交它们。所以只需提交它们,你就完成了。

git commit -m "my message"

现在,您可以将此新的主题分支合并回您的主分支。


6
对我来说,这是最有帮助的答案,因为它不需要手动变基,而是将整个分支的所有提交压缩成一个提交。非常好。 - Robert
谢谢!这就是我脑海中想要压缩提交的方式,让Git做到了! - Marjan Venema
令人惊叹的答案,比其他选项简单得多。 - user1310957
显然,这个答案不适用于需要合并ac并保留b的情况。 - Talha Ashraf
2
近期的 Git 版本有什么变化吗?当我在 Git 2.17 版本中尝试第一个命令(git checkout -b combine-last-two-commits "HEAD^2")时,我会收到一个错误:fatal: 'HEAD^2' 不是一个提交,也不能从它创建分支 'combine-last-two-commits' - mhucka
我使用 HEAD^2 时遇到了相同的错误 -- 我不得不使用具体的提交哈希值。 - Everett

27

你可以使用以下命令取消rebase:

git rebase --abort

当你再次运行交互式变基命令时,“合并”提交必须在列表中位于“挑选”提交之下。


21

$ git rebase --abort

如果你想撤销git rebase,请随时运行此代码。

$ git rebase -i HEAD~2

重新应用最近的两次提交。上述命令将打开一个代码编辑器。

  • [最新的提交将显示在底部] 将最后一次提交更改为squash(s)。由于squash将与前一个提交融合在一起。
  • 然后按esc键并输入:wq进行保存和关闭。

:wq之后,您将处于活动rebase模式中

注意:如果没有警告/错误消息,则会得到另一个编辑器。如果有错误或警告,不会显示另一个编辑器,如果看到错误或警告,则可以通过运行 $ git rebase --abort 来中止,否则只需继续运行$ git rebase --continue

您将看到2个提交消息。选择其中一个或编写自己的提交消息,保存并退出[:wq]

注意2:如果运行rebase命令,则可能需要强制推送更改到远程存储库

$ git push -f

$ git push -f origin master


1
注意2:git push -f origin/master是其他答案所遗漏的。+1 - Rishabh Agrahari

20

我经常使用git reset --mixed来回退到多个提交之前的基础版本,以便合并这些提交。然后我创建一个新的提交,这种方式可以让您的提交最新,并确保在推送到服务器后您的版本是HEAD。

commit ac72a4308ba70cc42aace47509a5e
Author: <me@me.com>
Date:   Tue Jun 11 10:23:07 2013 +0500

    Added algorithms for Cosine-similarity

commit 77df2a40e53136c7a2d58fd847372
Author: <me@me.com>
Date:   Tue Jun 11 13:02:14 2013 -0700

    Set stage for similar objects

commit 249cf9392da197573a17c8426c282
Author: Ralph <ralph@me.com>
Date:   Thu Jun 13 16:44:12 2013 -0700

    Fixed a bug in space world automation

如果我想把最近的两个提交合并为一个,首先我会使用:

git reset --mixed 249cf9392da197573a17c8426c282

"249cf9392da197573a17c8426c282" 是第三个版本,也是你合并前的基础版本,在此之后,我进行了新的提交:


"249cf9392da197573a17c8426c282" was the third version and your base version before merging. After that, I made a new commit:
git add .
git commit -m 'some commit message'

这就是全部,希望对每个人来说都有另一条路可以走。

供参考,来自 git reset --help

 --mixed
     Resets the index but not the working tree (i.e., the changed files are
     preserved but not marked for commit) and reports what has not been
     updated. This is the default action.

我还没有阅读'--mixed'的文档,但我相信其他人阅读了帖子并想知道同样的事情:使用--mixed有什么优势?将手册页面的片段包含在帖子中可能会改善它。 - funroll
@funroll 在回答之前,我并没有太多接触“混合”操作。根据我的经验,“混合”操作会将我传递的特定版本作为存储库HEAD版本,并且在该版本之后不会丢失任何内容,因此我们仍然可以处理这些更改。 - VinceStyling

6
如果您想要将多个提交合并成一个,可以使用交互式变基方法来实现。(感谢Mads教我这个!)
  • git rebase origin/develop -i
  • 然后只需在要合并的提交前面写上's',它们就会合并到主提交中。

提示:当处于git rebase-interactive模式(vim)时:

  1. 导航到您想要修改的提交行
  2. (ESC) ciw - (change inner word) 将更改光标下的整个单词。
  3. 输入您想要执行的操作,例如s代表合并
  4. (ESC) wq 写入退出,完成了.

enter image description here

然后执行git push -f


4

由于我几乎在所有情况下都使用git cherry-pick,在这里我也自然而然地这样做。

假设我已经检出了branchX,并且在它的最新位置有两个提交,我想要将它们的内容合并为一个提交,我可以执行以下操作:

git checkout HEAD^ // Checkout the privious commit
git cherry-pick --no-commit branchX // Cherry pick the content of the second commit
git commit --amend // Create a new commit with their combined content

如果我想要更新branchX(我猜这是这种方法的缺点),我还需要执行以下操作:

git checkout branchX
git reset --hard <the_new_commit>

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