我该如何从一个分支中删除一个提交?

4268

我该如何从我的分支历史记录中删除一个提交?我应该使用 git reset --hard HEAD 吗?


67
我认为这篇文章不是Git undo last commit相同,因为它询问如何从一个分支中删除任何提交。我还认为没有一个答案实际回答了这个问题。它们都是倒退到最后提交的状态,而不是cherry-pickdelete一次性提交,即使这个提交发生在很久以前。 - Chris
23
@Chris,答案“git rebase -i HEAD~10”确实回答了问题,因为它允许您任意选择要删除的提交。Git会逐个应用您指定范围内的提交,忽略您从日志中删除的提交。我今天使用这个命令来摆脱我的存储库中第二和第三个最近的提交,同时保留顶部的提交。我同意其他答案都不令人满意。 - MST
@MST 是的,我应该说,接受的答案中没有任何选项解决了这个问题,但你是完全正确的 - 那个命令似乎有效。 - Chris
3
我认为 git reset --soft HEAD~1 正是你所需要的。 在这种情况下,你将撤销提交并保存你的工作。reset --hard 将完全删除提交。 - sergpank
幸运的是,我可以只写一个空提交信息,这样提交就不会被提交了:),所以我不需要删除它。简而言之:我偶然运行了命令git commit -a,认为它是git commit --amend的缩写(实际上不是),并且在一个新的空提交消息中。我可以使用空消息取消编辑器(在vim中使用:wq),git不会创建提交,而是返回“由于空提交消息而中止提交。” - questionto42
显示剩余2条评论
38个回答

5412

注意:git reset --hard将删除您的工作目录更改。在运行此命令之前,请务必存储任何要保留的本地更改

假设您正在该提交上,那么此命令将清除它...

git reset --hard HEAD~1

HEAD~1 表示 head 前一次提交。

或者,您可以查看 git log 的输出,找到要备份的提交的提交 ID,然后执行以下操作:

git reset --hard <sha1-commit-id>

如果您已经推送了它,您需要进行强制推送以摆脱它...
git push origin HEAD --force

然而,如果其他人可能已经拉取了它,那么最好新建一个分支。因为当他们拉取时,它将合并到他们的工作中,然后你会再次推送。

如果你已经推送了,使用git revert可能更好,创建一个“镜像”提交来撤销更改。但是,两个提交都将在日志中。


提示: 如果您想要摆脱"正在进行中",git reset --hard HEAD非常好用。它可以将您重置回最近的提交,擦除您工作树和索引中的所有更改。


最后,如果你需要找到一个你“删除”的提交,通常可以在git reflog中找到,除非你已经进行了垃圾回收操作。

72
使用 HEAD~1 或者 HEAD^ 进行回滚操作是不推荐的,如果你已经提交了代码,应该使用 git revert 命令进行撤销。 - Jakub Narębski
17
很明显,你也可以使用HEAD~n来“回退”到从头部开始的第n个提交。也许从这个角度来看,你可以把... --hard HEAD理解为HEAD~0=>删除正在进行中的工作。 - nuala
14
依我之见,Git rebase 几乎总是更好的删除提交方式(就像 Greg Hewgill 的回答所描述的那样)——最重要的原因是 rebase 实际上包括一个大警告,提示你真正删除了东西。请注意,这里不要改变原来的意思,并尽可能使翻译通俗易懂。 - Noah Sussman
21
这并不会从提交树中删除更改。原帖的要求是删除一个已经存在的提交。如果你使用 reset --hard,并检查 log --oneline --all,这些提交仍然会留在树中。那么我们该如何从树中删除这些提交呢?谢谢。 - Igbanam
27
使用 reset --soft 命令可以删除本地提交,而不会撤销正在进行中的工作! - user972946
显示剩余16条评论

900
如果您还没有把提交推送到任何地方,您可以使用git rebase -i 来删除该提交。首先,确定该提交大致位于什么位置,然后执行以下步骤:
git rebase -i HEAD~N
~N的意思是将最近的N次提交合并(N必须为数字,例如HEAD~10)。然后,您可以编辑Git呈现给您的文件以删除有问题的提交。在保存该文件后,Git将重写所有后续提交,就好像您删除的提交不存在一样。
Git Book中有一篇关于rebase的文章,其中包含图片和示例。
但请注意,如果更改了已经推送到其他地方的内容,则需要采用另一种方法,除非您计划进行强制推送。

2
注意:如果您在最后一批提交中有任何--no-ff合并,rebase将破坏它们:(这在此页面的-p下提到。问题是,如果您用-p替换-i,您将不再获得弹出窗口,选择“编辑此提交,sqush那一个”等等的选项。有人知道解决方案吗? - Bukov
4
如果你已经推送了它呢?(只是我在使用远程仓库) - Costa Michailidis
8
@Costa,你可以使用push -f命令来强制推送并将远程分支替换为本地分支。如果只是你自己的远程仓库,那就没有问题。但如果在此期间有其他人拉取了代码,那么可能会出现问题。请注意。 - Greg Hewgill
3
我添加并提交了一个对于 GitHub 来说过大的数据文件(是的,它可能本来就不应该在源代码仓库中;咳咳)。当我尝试推送时,GitHub 拒绝了因为文件太大的原因。我只想撤销这个提交,同时保存一些其他不相关的后续提交。git rebase -i HEAD~5 命令完全符合我的需求,它在本地仓库中完全删除了这个提交!谢谢! - aldo
4
使用 rebase -i 命令时,与被删除的提交相关的更改不会被保留。 - Greg Hewgill
显示剩余5条评论

712

另一个可能性是我个人最喜欢的命令之一:

git rebase -i <commit>~1

这将在交互模式下从您想要删除的提交点之前开始rebase,-i。编辑器将启动列出此后所有的提交记录。删除包含您想要消除的提交的行并保存文件。Rebase会执行其余的工作,仅删除该提交,并将其他所有提交重新播放回日志中。


5
谢谢,顺便说一句,如果你遇到任何问题(比如空提交),你可以使用 git rebase --continue - realgt
18
更简单的做法:git rebase -i HEAD~1(该命令用于交互式地合并最近的一次提交到当前分支,HEAD~1代表上一个提交) - mmell
3
哇哇哇,git rebase -i HEAD~1 真的让代码仓库整洁了许多!虽然不太清楚具体做了什么,但整个仓库看起来干净多了。有点让人吃惊。 - Charles Wood
12
值得注意的是,提交并没有被彻底删除,只是从列表中移除了。如果出现问题,可以使用 reflog 恢复提交记录。 - Zaz
10
删除一行和删除/丢弃物品是否相同? - Leo
显示剩余8条评论

465

我追加这个答案,因为我不明白为什么任何尝试提交工作的人都想因为Git的某些错误而删除所有的工作!

如果你想保留你的工作,只需“撤消”那个提交命令(在推送到存储库之前捕获):

git reset --soft HEAD~1

不要使用 --hard 标记,除非你想摧毁上次提交以来的工作进程。


8
以下是一个例子:你在开发服务器上完成了一小部分工作并提交了。然后发现该服务器没有出站HTTPS访问权限,因此无法将提交推送到其他地方。最简单的方法就是假装它从未发生过,并从本地机器重新制作补丁。 - Steve Bennett
2
@KarthikBose,即使在执行git reset --hard HEAD~1之后,您仍然可以通过reflog找到以前的最新提交(直到过期为止)。请参见此处:http://gitready.com/intermediate/2009/02/09/reflog-your-safety-net.html。 - codeling
5
谢谢。这个回答应该排名更高或者包括在被接受的答案中。删除提交并不等同于还原提交。 - Alsciende
2
@RandolphCarter:但是你仍然会失去任何未提交的更改。 - naught101
9
@Rob,一个例子是当你无意中提交了包含绝不应该在源代码控制中出现的秘密信息(例如密码)的文件。这个本地提交必须被删除,而不仅仅是撤消,以便它永远不会被推送到服务器上。 - Bob Meyers
显示剩余4条评论

256

6
-p, --preserve-merges 重新创建合并提交,而不是通过重播提交来扁平化历史记录。合并冲突的解决或手动修改合并提交将不会被保留。 - raittes
19
它说,“用你想要摆脱的参考替换SHA”,但是这行中有两个SHA。我做了什么。 git rebase -p --onto 5ca8832c120^ 5ca8832c120 但是没有任何变化。我是否应该两次使用相同的SHA?如果不是,则要删除提交的SHA是哪一个,另一个SHA应该是什么? - Rubicksman
3
具体来说,这种方法会移除整个提交记录,包括文件(在我的情况下不是所需的)。 - goofology
2
@Rubicksman 在 Windows(cmd 窗口)中必须转义(^^)- https://dev59.com/rHI-5IYBdhLWcg3wO1vl - goofology
6
-p [DEPRECATED: use --rebase-merges instead] - syedelec
显示剩余4条评论

132

假设我们想从仓库中删除提交2和4。(数字越高表示提交时间越新;0是最旧的提交,4是最新的提交)

commit 0 : b3d92c5
commit 1 : 2c6a45b
commit 2 : <any_hash>
commit 3 : 77b9b82
commit 4 : <any_hash>

注意:您需要拥有该存储库的管理员权限,因为您正在使用--hard-f

  • git checkout b3d92c5 切换到上一个可用提交。
  • git checkout -b repair 创建一个新分支进行工作。
  • git cherry-pick 77b9b82 运行第三次提交。
  • git cherry-pick 2c6a45b 运行第一次提交。
  • git checkout master 切换到主分支。
  • git reset --hard b3d92c5 将主分支重置为上一个可用提交。
  • git merge repair 将我们的新分支合并到主分支。
  • git push -f origin master 推送主分支到远程存储库。

3
最后一步应该是 git push -f origin master,没有选项 --hard - vivex
7
我猜测commit 0commit 1旧。您能告诉我为什么首先要通过cherry-pick运行commit 3,然后再运行commit 1吗?在检出b3d92cd (commit 0)之后,我希望首先进行commit 1cherry-pick,然后是commit 3。谢谢。 - Jarek C
1
@JarekC 我认为最上面的提交是最新的提交,除非我看错了... - Jeff Huijsmans
3
这基本上是正确的,但有一点错误。我刚刚按照这个过程操作并进行了一些微调。澄清一下:commit 0 是最新的提交,commit 4 是最早的。从检出不想要的提交之前的 commit 5 开始。然后 cherry-pick commit 3,再 cherry-pick commit 1,接着按照同样的方式处理 commit 0。然后,检出 master 并将其重置为 commit 5 并按照剩余步骤进行操作。 - Joshua Swain
1
根据我对这个答案的理解,提交4是最新的,提交0是最老的。其中一个原因是 - 他在最后将主分支与修复分支合并,以便主分支的最终树看起来就像提交2和提交4不存在一样。到目前为止还好吧?现在,在执行这个合并之前,他将主分支重置为提交0,然后将来自修复分支的两个良好提交合并。如果提交4是最老的,则他不会将主分支重置为b3d92c5,而是应该重置为77b9b82。 - utkarsh-k
显示剩余4条评论

75
git rebase -i HEAD~2

这里的'2'是您想要变基的提交数。

'git rebase -i HEAD`

如果您想使所有提交基于新的基底。

那么您就可以选择以下其中一个选项:

p, pick = 使用此次提交

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
x, exec = run command (the rest of the line) using shell
d, drop = remove commit

这些行可以重新排序;它们从上到下执行。如果你在这里删除一行,那个提交将会丢失。然而,如果你删除所有东西,rebase 将会中止。请注意,空提交已被注释掉。

您可以使用选项 "d" 或删除包含您提交的行来简单地删除该提交。


在最新的git版本中,不再有选项d。您只需要从rebase中删除带有commits的行即可删除它们。 - Oleg Abrazhaev

64

#[快速回答]

你有很多选择,例如:

  • 选择1:

     git rebase -i <YourCommitId>~1
    

    将YourCommitId替换为您想要恢复到的提交编号。

  • 备选方案2:

     git reset --hard YourCommitId
     git push <origin> <branch> --force
    

    将YourCommitId更改为您要还原到的提交编号。

    我不建议使用此选项,因为您可能会丢失正在进行中的工作。

  • 备选方案3:

     git reset --soft HEAD~1
    

    您可以保留您的工作,仅撤消提交。


1
git reset --soft HEAD~1 对我来说完美地解决了问题。确保我不会丢失该提交所做的更改。感谢您清晰的回答。 - Wilan

59

强制修改历史记录

如果你不仅仅想删除最后一次提交,而是要删除最近n次提交中的特定提交,请使用命令:git rebase -i HEAD~<向后回退几个提交>,例如,如果你想查看最近的5次提交,则使用git rebase -i HEAD~5

然后在文本编辑器中把每个要删除的提交旁边的单词pick更改为drop。保存并退出编辑器。大功告成!

逐步修改历史记录

尝试使用命令git revert <commit hash>Revert将创建一个新的提交,以撤消指定的提交。


drop 关键字未定义。要删除提交,请删除整行。 - Shayan Salehian
2
对我来说,drop 被定义为关键词,但执行 drop 并没有从历史记录中移除提交。然而,在交互式 rebase 中删除该行可以实现。 - Adam Parkin
我按照多个地方的建议删除了该行代码,但并没有起到任何作用。将单词“pick”替换为“drop”确实删除了提交。注意:指令在文本编辑器内容底部的注释部分中。使用“git version 2.24.3 (Apple Git-128)”。 - leanne

59

如果您没有发布更改,要删除最新的提交,可以执行以下操作

$ git reset --hard HEAD^

请注意,这也会删除所有未提交的更改;请慎重使用。

如果您已经发布待删除的提交,请使用git revert

$ git revert HEAD

那没起作用。当我执行 git log 命令时,所有的记录都还在,无论我做什么都只会增加更多的提交。我想清理一下历史记录。 - Costa Michailidis
@Costa:哪个版本没有起作用(即你使用了哪个版本),你是如何进行git log的? - Jakub Narębski
我已经尝试了这个问答中几乎所有的方法(最近我尝试了git revert HEAD)。我的git日志:tree = log --all --graph --format=format:'%C(bold blue)%h%C(reset) %C(dim black)%s%C(reset)%C(bold red)%d%C(reset) %C(green)by %an, %ar%C(reset)' - Costa Michailidis
@Costa:请注意,使用第一个版本(使用git reset --hard HEAD^)只会从当前分支中“删除”提交(实际上是移动分支指针),如果该提交还在其他分支上,则仍将存在。使用git reset“删除”提交只是将分支指针向后移动,使提交悬空并可供修剪……如果没有被其他引用(其他分支、远程跟踪分支或标签)引用。或者您使用了git revert HEAD - Jakub Narębski
1
我只想删除提交记录(就像它们从未存在过一样)。我进行了一些奇怪的编码冒险,有几个新的提交记录,但最终都是垃圾。我该如何从我的git日志中擦除它们? - Costa Michailidis

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