在Git中,如何『删除』commit?

本文主要参考 How can I delete a commit in Git? 以及文中的链接 翻译、整理而来。

在Git中,如何『删除』commit?
首先,要搞清楚你是哪种情况,因为『删除』commit 有3种方法,但是每种方法的适用情况不一样。
需要注意的是,这里的『删除』的意思是泛泛的,最终的效果像是『删除』了commit,而commit是否真的从提交的历史记录里删掉,则取决于采用的 git 命令。

顺便说一下,Git-Tower的教程还是不错的,在线教程免费,主题清晰,文章简练,有中英文两个版本,英文不错的,建议读读英文版。教程覆盖不到的地方,可以参考他们提供的FAQ。

注:本文中 『commit』和『提交』的概念一样,但是由于中文『提交』的动词含义较重,所以在『提交』后面有其他描述的,则使用『commit』来代替『提交』。

★ 情况1:恢复到某个旧版本

POD removing 无法删除 无法删除common_POD removing 无法删除


直接采用Git-Tower网站中的原图。这种情况是说,你想要将项目的版本恢复到C2版本,在效果上等同于将后面的C3和C4 commit『删除』了。在这种情况下,git reset命令适合你。

$ git reset --hard 0ad5a7a6

此命令是将HEAD分支回退到指定的版本(0ad5a7a6),所有在这个版本(0ad5a7a6)之后的commit都删掉了。使用这个命令(带--hard参数)要慎重,因为如果你的改动从没有提交到仓库中,那么你的改动将无法恢复了。--hard参数是git reset命令的唯一的危险的参数。如果你删除错了,本地未提交的数据就彻底丢失了。如果已经提交到本地仓库中,则可以参考这个链接来恢复数据。

$ git reset --soft 0ad5a7a6

使用--soft参数,会把 0ad5a7a6 之后所有的commit的改动保留在工作目录(Working copy)中,留给你做进一步处理。

git reset命令还是有些危险的,所以需要搞清楚git reset的原理之后再使用。更多git reset的原理介绍,请参考 git官方文档 重置解密。

还有一种更安全的方式,可以保持HEAD分支不动,就是使用git checkout命令将某个旧版本拉到一个新分支中。

$ git checkout -b old-project-state 0ad5a7a6

-b参数用来创建一个新分支old-project-state。git checkout切换到old-project-state分之后,就恢复到了旧版本。

注1:HEAD是指向当前所在的分支的最后一次提交,也是下一次提交的父节点。可以看做是当前分支的别名,例如HEAD指向master分支,则也可以用『HEAD分支』来指代master分支。
注2:『HEAD分支』在Git-Tower中的原文为『HEAD branch』。

★ 情况2:撤销某一个commit

POD removing 无法删除 无法删除common_重置_02

如上图所示,撤销 C2 这个commit,但是保留C3、C4的改动。在这种情况下,可以使用git revert命令。
git revert命令并不会删除C2,而是创建一个新的commit,将C2中的改动再改回去。如下图所示。

在这个例子中,在revert之前是没有C4 commit的,假设C2的commit id为 0ad5a7a6,执行git revert 0ad5a7a6则会创建C4 commit。

POD removing 无法删除 无法删除common_重置_03

★ 情况3:从提交历史中删掉一个commit

如下图所示,从提交历史中删除C2 commit,而保留C3、C4的改动。

POD removing 无法删除 无法删除common_旧版_04


出现这种情况的时候,可能是因为我不小心提交了关键的、不应该公开的信息(例如密码、私钥),我需要从历史中把commit删掉。此时,就需要用到最强大也最危险的工具git rebase -i

例如,我有3次提交,使用git log命令显示如下:

$ git log --pretty=format:"%h %s" HEAD~3..HEAD
a5f4a0d added cat-file
310154e updated README formatting and added blame
f7f3f6d changed my name a bit

我想删除『a5f4a0d added cat-file』这个提交。

执行git rebase -i HEAD~3命令,结果如下:

$ git rebase -i HEAD~3
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file

删除commit a5f4a0d,就是把『pick a5f4a0d added cat-file』这一行删掉。如下,

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame

保存并退出编辑器,git就把 commit a5f4a0d删掉了。

需要说明的是,如果只是删掉『a5f4a0d added cat-file』,不需要使用HEAD~3,使用HEAD~1就行,因为commit a5f4a0d是最后一个提交,是HEAD指向的提交。

  • 扩展:

如果只是想修改历史记录里的邮箱、名字,则可以用git filter-branch并配合--commit-filter来使用。例如,修改邮箱和名字的命令,将所有原来邮箱是schacon@localhost的commit,都把邮箱改为schacon@example.com,名字改为Scott Chacon。邮箱不是schacon@localhost的commit则邮箱保持不变。但是,所有受影响的commit的SHA-1校验都会变化。命令如下:

$ git filter-branch --commit-filter '
        if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
        then
                GIT_AUTHOR_NAME="Scott Chacon";
                GIT_AUTHOR_EMAIL="schacon@example.com";
                git commit-tree "$@";
        else
                git commit-tree "$@";
        fi' HEAD

★ 参考

  • How can I delete a commit in Git?
  • How can I restore a previous version of my project?
  • How can I undo an older commit?
  • 关于HEAD的说明:https://git-scm.com/book/zh/v2/Git-分支-分支简介
  • 撤销操作:https://www.git-tower.com/learn/git/ebook/cn/command-line/advanced-topics/undoing-things
  • 重置解密:https://git-scm.com/book/zh/v2/Git-工具-重置揭密
  • 重写历史:https://git-scm.com/book/zh/v2/Git-工具-重写历史