在Git中,如何『删除』commit?
本文主要参考 How can I delete a commit in Git? 以及文中的链接 翻译、整理而来。
在Git中,如何『删除』commit?
首先,要搞清楚你是哪种情况,因为『删除』commit 有3种方法,但是每种方法的适用情况不一样。
需要注意的是,这里的『删除』的意思是泛泛的,最终的效果像是『删除』了commit,而commit是否真的从提交的历史记录里删掉,则取决于采用的 git 命令。
顺便说一下,Git-Tower的教程还是不错的,在线教程免费,主题清晰,文章简练,有中英文两个版本,英文不错的,建议读读英文版。教程覆盖不到的地方,可以参考他们提供的FAQ。
注:本文中 『commit』和『提交』的概念一样,但是由于中文『提交』的动词含义较重,所以在『提交』后面有其他描述的,则使用『commit』来代替『提交』。
★ 情况1:恢复到某个旧版本
直接采用Git-Tower网站中的原图。这种情况是说,你想要将项目的版本恢复到C2版本,在效果上等同于将后面的C3和C4 commit『删除』了。在这种情况下,git reset
命令适合你。
$ git reset --hard 0ad5a7a6
- 1
此命令是将HEAD分支回退到指定的版本(0ad5a7a6),所有在这个版本(0ad5a7a6)之后的commit都删掉了。使用这个命令(带--hard
参数)要慎重,因为如果你的改动从没有提交到仓库中,那么你的改动将无法恢复了。--hard
参数是git reset
命令的唯一的危险的参数。如果你删除错了,本地未提交的数据就彻底丢失了。如果已经提交到本地仓库中,则可以参考这个链接来恢复数据。
$ git reset --soft 0ad5a7a6
- 1
使用--soft
参数,会把 0ad5a7a6 之后所有的commit的改动保留在工作目录(Working copy)中,留给你做进一步处理。
git reset
命令还是有些危险的,所以需要搞清楚git reset
的原理之后再使用。更多git reset的原理介绍,请参考 git官方文档 重置解密。
还有一种更安全的方式,可以保持HEAD分支不动,就是使用git checkout
命令将某个旧版本拉到一个新分支中。
$ git checkout -b old-project-state 0ad5a7a6
- 1
-b
参数用来创建一个新分支old-project-state。git checkout
切换到old-project-state分之后,就恢复到了旧版本。
注1:HEAD是指向当前所在的分支的最后一次提交,也是下一次提交的父节点。可以看做是当前分支的别名,例如HEAD指向master分支,则也可以用『HEAD分支』来指代master分支。
注2:『HEAD分支』在Git-Tower中的原文为『HEAD branch』。
★ 情况2:撤销某一个commit
如上图所示,撤销 C2 这个commit,但是保留C3、C4的改动。在这种情况下,可以使用git revert
命令。
git revert
命令并不会删除C2,而是创建一个新的commit,将C2中的改动再改回去。如下图所示。
在这个例子中,在revert之前是没有C4 commit的,假设C2的commit id为 0ad5a7a6,执行git revert 0ad5a7a6
则会创建C4 commit。
★ 情况3:从提交历史中删掉一个commit
如下图所示,从提交历史中删除C2 commit,而保留C3、C4的改动。
出现这种情况的时候,可能是因为我不小心提交了关键的、不应该公开的信息(例如密码、私钥),我需要从历史中把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
- 1
- 2
- 3
- 4
我想删除『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
- 1
- 2
- 3
- 4
删除commit a5f4a0d,就是把『pick a5f4a0d added cat-file』这一行删掉。如下,
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
- 1
- 2
保存并退出编辑器,git就把 commit a5f4a0d删掉了。
需要说明的是,如果只是删掉『a5f4a0d added cat-file』,不需要使用HEAD~3
,使用HEAD~1
就行,因为commit a5f4a0d是最后一个提交,是HEAD指向的提交。
- 扩展:
如果只是想修改历史记录里的邮箱、名字,则可以用git filter-branch
并配合--commit-filter
来使用。例如,修改邮箱和名字的命令,将所有原来邮箱是schacon@localhost
的commit,都把邮箱改为[email protected]
,名字改为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="[email protected]";
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-工具-重写历史