如何在git历史记录中删除特定的版本?

247

假设你的git历史记录如下:

1 2 3 4 5

其中1-5是单独的修订版本。您需要删除第三个版本,同时保留1、2、4和5。如何实现?

如果要删除的版本后有数百个修订版本,是否有一种高效的方法可以完成此操作?


1
这个问题定义不清晰。它没有明确说明作者想要1-2-(3+4)-5还是1-2-4-5。 - RandyTek
23
八年后的现在,我无法准确地告诉你当时我想解决的问题。但是在 Git 中,做某件事情总有很多方法,不同的人也会喜欢各自的答案,所以我猜这种含糊并没有给太多人带来麻烦。 - 1800 INFORMATION
1
git rebase --onto 2 3 HEAD 的意思是将提交从3到HEAD(HEAD是可选的,或者在这种情况下为5)重新应用到2上。 - neaumusic
9个回答

150

根据这条评论(我已经确认这是真的),rado的回答非常接近但会使git处于分离的HEAD状态。相反,删除HEAD并使用以下命令从当前分支中移除<commit-id>

git rebase --onto <commit-id>^ <commit-id>

1
如果您能告诉我如何仅基于提交ID从历史记录中删除该提交ID,那么您将成为我的英雄。 - kayleeFrye_onDeck
3
请问您能否在答案中包含魔术命令的解释?即每个参数代表什么意思? - alexandroid
2
由于某些原因,当我运行这个程序时,什么也没有发生。但是,将 ^ 更改为 ~1 后,它就可以工作了。 - Antimony
虽然有点晚了,但我想补充一下:如果遇到合并冲突,请中止变基操作,然后在末尾重新运行它,并加上 --strategy-option theirs - LastStar007
另外,如果您需要从远程存储库中删除提交(例如,如果您意外推送了敏感信息),请使用 git pull --strategy-option oursgit push - LastStar007
显示剩余2条评论

126

以下是一种非交互式地删除特定<commit-id>的方法,只需知道您想要删除的<commit-id>:

git rebase --onto <commit-id>^ <commit-id> HEAD

3
我也试过了,对我也有效。顺便问一下,“^” 运算符是什么意思?它表示指定提交之后的下一个提交吗? - hopia
3
@hopia的意思是指特定提交记录的(第一个)父提交。请参阅“git help revisions”了解更多信息。 - Emil Styrke
12
请参考 Kareem 在他的答案中的建议,省略 HEAD 以避免出现分离头指针的情况。 - mklement0
1
比起要算出需要搜索多少个版本,这样做容易得多。 - Dana Woodman
1
这太棒了,但如果我想删除历史记录中的第一个提交怎么办?(这就是我来这里的原因 :P) - Sterling Camden
显示剩余2条评论

82

如之前所述,git-rebase(1) 是你的好朋友。假设这些提交在你的 master 分支中,你可以执行以下操作:

git rebase --onto master~3 master~2 master
抱歉,我只能使用英文回答您的问题。
1---2---3---4---5  master

之后:

1---2---4'---5' master

来自git-rebase(1):

使用rebase也可以删除一系列提交。如果我们有以下情况:

E---F---G---H---I---J  topicA

然后执行该命令

git rebase --onto topicA~5 topicA~3 topicA

这将导致删除提交 F 和 G:

E---H'---I'---J'  topicA

如果F和G存在某些缺陷,或者不应该是topicA的一部分,则此方法非常有用。 请注意,--onto参数和参数可以是任何有效的提交标识符。


3
这应该是 --onto master~3 master~1 吗? - mxk
3
如果您只想删除最后一次提交,可以使用 --onto master~1 master。 - MikeHoss
1
我想继续进行这个解决方案,但是我遇到了“合并冲突”错误。我使用了https://dev59.com/s3A85IYBdhLWcg3wHvo0中提到的场景,但无法删除该示例中的第二个提交。 - maan81

80
要将修订版3和4合并为单个版本,您可以使用git rebase。如果您想要删除修订版3中的更改,则需要在交互式rebase模式下使用编辑命令。如果要将更改合并为单个修订版,请使用压缩(squash)命令。
我已经成功使用了这种压缩技术,但以前从未需要删除修订版。在“拆分提交”下的git-rebase文档应该足以让您了解如何弄清楚它。(或者其他人可能知道)。
git文档中可以看到:
请从您想要保留的最旧提交开始:
git rebase -i <after-this-commit>
一个编辑器将打开当前分支中所有提交(忽略合并提交),这些提交在给定提交之后。您可以随心所欲地重新排序此列表中的提交,并可以删除它们。这个列表看起来有点像这样:
pick deadbee 这个提交的单行说明
pick fa1afe1 下一个提交的单行说明
...
单行说明仅供您参考;git-rebase不会查看它们,而是查看提交名称(例如本例中的“deadbee”和“fa1afe1”),因此请勿删除或编辑名称。
通过将命令“pick”替换为命令“edit”,您可以告诉git-rebase在应用该提交后停止,以便您可以编辑文件和/或提交消息,修改提交并继续重新进行基础。
如果要将两个或多个提交合并为一个,请将第二个和随后的提交的命令“pick”替换为“squash”。如果提交具有不同的作者,则将压缩提交归因于第一个提交的作者。

47
问题定义得很清楚,但是这个答案不够清晰。作者没有说明确切的解决方案。 - Aleksandr Levchuk
1
这是一个错误的线索。SPLITTING COMMITS部分不是正确的部分。你想要阅读手册中更高的部分 - 参见@Rares Vernica的答案。 - Aleksandr Levchuk
1
@AleksandrLevchuk 这个问题没有明确定义:从问题陈述的方式来看,不清楚是否要保留第3个变更集。如果更改将被丢弃,我同意其他答案提供了一种更简单的方法。然而,如果更改将被保留,那么这将是一个纯粹的表面操作。两种方法都以危险的方式重写历史,如果其他人可能基于有缺陷的历史进行工作,则不应进行表面清理,并且通过git revert执行更改删除会更好。 - Theodore Murdock
1
大家都在抱怨,但这对我有帮助。哈哈 - giovannipds

23
如果您只想撤消第三个版本所做的更改,您可以使用 git revert。
Git revert 只是创建一个新版本,其中包含撤消您正在还原的版本中所有更改的变化。
这意味着您保留了关于不需要的提交和撤消这些更改的提交的信息。
如果有人在此期间从您的存储库中拉取,则此方法可能会更加友好,因为还原基本上只是一个标准提交。

4
很抱歉,这对我来说不是一个好的解决方案,因为有人意外地向库提交了100MB的垃圾文件,导致库的大小急剧增加,使得网络界面变得迟缓。 - Stephen Smith

19
到目前为止,所有答案都没有解决尾随的问题:

如果要删除的版本之后有数百个版本,是否有一种高效的方法?

下面是步骤,但是为了参考,让我们假设以下历史记录:

[master] -> [hundreds-of-commits-including-merges] -> [C] -> [R] -> [B]

C: 在被删除的提交(commit)之后的提交(commit)

R: 要被删除的提交(commit)

B: 在被删除的提交(commit)之前的提交(commit)

由于“数百个修订版本”限制,我假设以下先决条件:

  1. 存在一些令人尴尬的提交(commit),您希望它从未存在过
  2. 没有后续提交(commit)实际上依赖于那个尴尬的提交(commit)(撤销时没有冲突)
  3. 您不在意将被列为数百个介入提交(commit)的“提交者”(“作者”将被保留)
  4. 您从未共享过存储库
    • 或者您确实有足够的影响力来说服所有曾经克隆包含该提交(commit)历史记录的人使用您的新历史记录
    • 而且您不关心 重写历史记录
这是一组非常严格的限制条件,但在这种情况下有一个有趣的答案可以实现。
以下是步骤:
1. `git branch base B` 2. `git branch remove-me R` 3. `git branch save` 4. `git rebase --preserve-merges --onto base remove-me`
如果没有真正的冲突,那么这个过程应该可以顺利进行。如果有冲突,您可以解决它们并使用 `rebase --continue` 继续,或者决定忍受尴尬并使用 `rebase --abort` 放弃此次操作。
现在,您应该在不再包含提交 R 的 `master` 分支上了。`save` 分支指向之前的位置,以防您想要协调处理。
你可以自行决定如何安排其他人转移到你的新历史记录。你需要熟悉 stash、reset --hard 和 cherry-pick 命令。同时,你可以删除 base、remove-me 和 save 分支。保留 HTML 标签。

我怎样可以点赞150000次? - Gena Moroz
我成功地从一个分支的历史记录中删除了三个连续的提交,其中有人提交了一堆150MB的文件。 - Marcos

3

我也遇到了类似的情况。使用下面的命令使用交互式变基,在选择时删除第三个提交。

git rebase -i remote/branch

2

以下是我遇到的情况以及如何解决它的过程。

[branch-a]

[Hundreds of commits] -> [R] -> [I]

这里的R是我需要删除的提交,I是在R之后的单个提交

我创建了一个还原提交并将它们压缩在一起。

git revert [commit id of R]
git rebase -i HEAD~3

在交互式变基期间,将最后2个提交压缩成一个。

0

rado 和 kareem 的答案对我没有任何帮助(只显示消息“当前分支已经是最新的。”)。可能是因为在 Windows 控制台中 '^' 符号不起作用。 但是,根据 this 的评论,将 '^' 替换为 '~1' 可以解决问题。

git rebase --onto <commit-id>^ <commit-id>

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