git revert
Git revert 用于撤回某次提交的内容,同时再产生一个新的提交(commit)。原理就是在一个新的提交中,对之前提交的内容相反的操作。
下面通过例子具体解释一下:
现有一个git项目,已经有3次提交,每次添加一个文件,具体提交步骤如下:
# 第一次提交
$ echo "first commit" > test.txt
$ git add test.txt
$ git commit -m "1 commit"
# 第二次提交
$ echo "second commit" > test2.txt
$ git add test2.txt
$ git commit -m "2 commit"
# 第三次提交
$ echo "third commit" > test3.txt
$ git add test3.txt
$ git commit -m "3 commit"
$ ls
test.txt test2.txt test3.txt
查看提交记录,使用命令git log --pretty=oneline --abbrev-commit
:
$ git log --pretty=oneline --abbrev-commit
* 224a71b - (HEAD -> master) 3 commit
* 54b25d8 - 2 commit
* b2b9b24 - 1 commit
可以看到有三次提交,每一次提交的内容都是添加一个文件。
现在我们做实验,使用git revert撤回第二次提交的内容,即54b25d8 - 2 commit
这次提交的内容,使用git revert 54b25d8
(第二次提交的hash值)
$ git revert 54b25d8
Removing test2.txt
[master e9fe99e] Revert "2 commit"
1 file changed, 1 deletion(-)
delete mode 100644 test2.txt
命令执行成功,我们查看一下 test2.txt文件是否存在
$ ls
test.txt test3.txt
test2.txt文件已经不见了,说明第二次提交的内容已经被撤回了
我们再查看一下git提交记录:
$ git log --pretty=oneline --abbrev-commit
* e9fe99e - (HEAD -> master) Revert "2 commit"
* 224a71b - 3 commit
* 54b25d8 - 2 commit
* b2b9b24 - 1 commit
发现多了1次提交记录,即:e9fe99e - (HEAD -> master) Revert "2 commit"
, 而这次提交的所作的事情就是撤销2 commit
所提交的内容。
这就是git reset的作用:撤回某次提交的内容,再产生一个新的提交(commit)
注意这句话的重点:
- 撤回提交的内容,不是撤回提交(commit),之前的提交的历史不会发生任何改变。
- 会产生一个新的提交。它是通过在新的提交中,将之前提交的内容反向操作了一遍,比如:之前提交内容是 添加 test2.txt,而新提交中的内容就变成了 删除 test2.txt
使用场景
在工作中,如果发现之前某次提交的内容是多余不需要的,需要撤销掉,这时就可以使用git revert
将这次提交的内容撤回掉。
其实git revert在使用过程中有很多局限性,比如在对相同的一个文件执行多次commit以后,再使用git revert撤回其中某次提交的内容将会出现冲突,这时就需要手动解决,这回带来一些麻烦。
实际演示一下这种情况:
在上面的git仓库中,继续添加3次提交记录,对同一个文件 test.txt依次增加一些内容:
$ echo "4 commit" >> test.txt
$ git add .
$ git commit -m "4 commit"
$ echo "5 commit" >> test.txt
$ git add .
$ git commit -m "5 commit"
$ echo "6 commit" >> test.txt
$ git add .
$ git commit -m "6 commit"
现在查看一下text.txt的内容,已经多了3行:
$ cat text.txt
first commit
4 commit
5 commit
6 commit
再查看一个git提交历史:
$ git log --pretty=oneline --abbrev-commit
* e005437 - (HEAD -> master) 6 commit
* c950839 - 5 commit
* b205bd9 - 4 commit
* e9fe99e - Revert "2 commit"
* 224a71b - 3 commit
* 54b25d8 - 2 commit
* b2b9b24 - 1 commit
这时git 提交历史中也多了三次提交,每一次提交的内容为test.txt增加一行,也就是说我们3次提交都是在修改同一个文件。
现在我在使用git revert撤回第5次提交的内容(第5次提交的hash值为c950839
),使用git revert c950839
$ git revert c950839
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
error: could not revert c950839... 5 commit
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add ' or 'git rm '
hint: and commit the result with 'git commit'
git revert 失败了,出现了冲突,原因是第6次提交也对这个文件进行了更改,导致git 无法判断具体要撤回的内容,所以需要手动去确定撤回的内容,
我们来手动撤回提交的内容,先使用 vim 打开 test.txt文件
$ vim test.txt
first commit
4 commit
<<<<<<< HEAD
5 commit
6 commit
=======
>>>>>>> parent of c950839... 5 commit
现在手动删除需要撤回的内容以及提示符
$ vim test.txt
first commit
4 commit
6 commit
保存修改后的文件,执行git add test.txt
添加修改的文件,再执行git revert --continue
完成本次git revert操作
$ git add test.txt
$ git revert --continue
[master 3fb7a20] Revert "5 commit"
1 file changed, 1 deletion(-)
查看,git提交历史,显示已经完成了本次操作
$ git log --pretty=oneline --abbrev-commit
3fb7a20 - (HEAD -> master) Revert "5 commit"
* e005437 - 6 commit
* c950839 - 5 commit
* b205bd9 - 4 commit
* e9fe99e - Revert "2 commit"
* 224a71b - 3 commit
* 54b25d8 - 2 commit
* b2b9b24 - 1 commit
整个git revert过程无疑比之前的例子麻烦了许多,需要手动去确定撤回的内容,没办法一步到位(这样的情况,在使用git revert撤回第4次操作时也会出现), 造成这样的原因就是多次提交的内容修改了同一个文件。而实际的git使用中多次提交修改同一个文件是常有的事情,所以git revert并不太好用。
下面介绍一下更好用的东西,git reset
git reset
git reset用于版本回退,能将当前提交(commit)回退到特定的历史版本(commit)。这是一个相当实用的功能,基本在提交内容中有错误需要更正时都会用上这个功能。
在使用git reset
有三个最常用的三个参数:
- --soft:
git reset --soft v1
:版本回退到v1,将v1版本之后修改的内容 存入暂存区 - --mixed:
git reset --mixed v1
:版本回退到v1,将v1版本之后修改的内容 存入工作区 - --hard:
git reset --hard v1
:版本回退到v1,v1版本之后修改的内容 全部清除
先大致解释一下工作区,暂存区的含义,以便更好的理解这3个参数的区别,如果知道的朋友可以直接跳过这部分:
什么是工作区和暂存区
当你在仓库中写完一段代码,删除,修改 或 创建 一个文件后,这时你修改的内容是就是存放在git工作区中,使用git status
查看,会发现修改的文件会显示成红色
举个例子,对之前的仓库里test3.txt
进行一下修改,追加一句话
$ echo "add something" >> test3.txt
之后使用git status
查看一下当前仓库的状态,会显示如下内容
$ git status
On branch master
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git restore ..." to discard changes in working directory)
modified: test3.txt
no changes added to commit (use "git add" and/or "git commit -a")
可以看到git 提示Changes not staged for commit
,也就是现在对test3.txt有修改,修改内容位于工作区,并用红色显示文件名,但是文件没有进行暂存。
我们使用git add test3.txt
将修改的文件添加到暂存区进行暂存
$ git add test3.txt
再次使用git status
查看一下当前仓库的状态
$ git status
On branch master
Changes to be committed:
(use "git restore --staged ..." to unstage)
modified: test3.txt
显示Changes to be committed
,也就是说文件test3.txt已经暂存,并用绿色显示,等待去提交到仓库。
现在,我们使用git commit
将修改提交到仓库
$ git commit -m "7 commit: add something"
提交完成,再次使用git status
查看一下当前仓库的状态
$ git status
On branch master
nothing to commit, working tree clean
git显示nothing to commit
,表明修改已经存储到仓库中,现在工作区和暂存区都是干净的。
通过上面的例子可以看到,工作区和暂存区只是存储修改内容的两个地方:当修改发生后,修改的内容会先存放在工作区中,然后通过git add
命令将修改添加到暂存区,最后通过git commit
提交到仓库。这就是git的存储修改内容的顺序:工作区 --> 暂存区 --> 仓库
而git reset的三个参数直接决定了,git reset v1
执行后,工作区和暂存区的状态
--soft, --mixed, --hard
-
git reset --soft v1
:版本回退到v1,将v1版本之后修改的内容 存入暂存区 -
git reset --mixed v1
:版本回退到v1,将v1版本之后修改的内容 存入工作区 -
git reset --hard v1
:版本回退到v1,v1版本之后修改的内容 全部清除
直接写明可能不够清楚,下面通过具体示例,来演示这三个参数之间的区别,这样就会更好的理解。
这次实例要做的内容是,在上面的git仓库中使用git reset 将版本回退到提交6 commit
(即:hash值为e005437
),通过分别带上这三个不同的参数,再查看执行后git 仓库的状态:
先git 查看当前的提交历史
$ git log --pretty=oneline --abbrev-commit
* cea4b44 - (HEAD -> master) 7 commit: add something
* 3fb7a20 - Revert "5 commit"
* e005437 - 6 commit # <-------这里
* c950839 - 5 commit
* b205bd9 - 4 commit
* e9fe99e - Revert "2 commit"
* 224a71b - 3 commit
* 54b25d8 - 2 commit
* b2b9b24 - 1 commit
-
例子一:使用--mixed 回退到提交版本6 commit
e005437
,运行命令git reset --mixed e005437
$ git reset --mixed e005437 Unstaged changes after reset: M test.txt M test3.txt $ git log --pretty=oneline --abbrev-commit * e005437 - (HEAD -> master) 6 commit * c950839 - 5 commit * b205bd9 - 4 commit * e9fe99e - Revert "2 commit" * 224a71b - 3 commit * 54b25d8 - 2 commit * b2b9b24 - 1 commit
已经退回到
6 commit
,用git status查看查看一下状态$ git status On branch master Changes not staged for commit: (use "git add
..." to update what will be committed) (use "git restore ..." to discard changes in working directory) modified: test.txt modified: test3.txt no changes added to commit (use "git add" and/or "git commit -a") Git status显示红色,说明在工作区中有对修改的内容没有暂存,而这些内容就是
6 commit
这次提交之后修改的内容。所以使用--mixed
回退到e005437
版本时,并没有清除掉这个提交版本6 commit
之后所修改的内容,只是将该版本6 commit
之后修改的内容放到了工作区
-
例子二:将恢复原样,使用--soft 回退到提交版本6 commit
e005437
, 运行命令git reset --soft e005437
$ git reset --soft e005437 $ git log --pretty=oneline --abbrev-commit * e005437 - (HEAD -> master) 6 commit * c950839 - 5 commit * b205bd9 - 4 commit * e9fe99e - Revert "2 commit" * 224a71b - 3 commit * 54b25d8 - 2 commit * b2b9b24 - 1 commit
回退成功,现在使用git status查看一下状态
$ git status On branch master Changes to be committed: (use "git restore --staged
..." to unstage) modified: test.txt modified: test3.txt git status显示为绿色,说明也有内容,不过这次内容不再工作区,而是在暂存区,这就说明 --soft也不会清除掉这个版本6 commit
e005437
之后修改的内容,而是把该版本6 commit
之后所修改的内容存放到了暂存区
-
例子三:将恢复原样,使用--hard 回退到提交版本6 commit
e005437
, 运行命令git reset --hard e005437
$ git reset --hard e005437 HEAD is now at e005437 6 commit $ git log --pretty=oneline --abbrev-commit * e005437 - (HEAD -> master) 6 commit * c950839 - 5 commit * b205bd9 - 4 commit * e9fe99e - Revert "2 commit" * 224a71b - 3 commit * 54b25d8 - 2 commit * b2b9b24 - 1 commit
执行完成,还是使用git status查看状态
$ git status On branch master nothing to commit, working tree clean
这次显示工作区和缓存区都没有内容,这就表示--hard 会清除掉该版本
6 commit
之后的提交的内容!这就意味着这个提交6 commit
之后的内容都会丢失掉,慎用!
通过实例总结得出,git reset 能够将git 回退到特定的提交(commit v1) ,并通过添加可选参数--xxx
决定是否保留v1之后修改的内容。 --hard
直接丢弃修改内容,--mixed
将修改内容放在工作区,--soft
将修改内容放在暂存区。
git reset常见的使用场景
在实际使用中,一般--hard 和--mixed使用较多,--soft用的较少。
之所以--soft用的比较少是因为它的功能和--mixed功能重合了,我完全可以先使用--mixed将之后修改的内容存入工作区,然后检查一遍是否有错误,再使用git add提交到暂存区然后提交。
下面介绍2个常见的使用场景
将某次提交从提交历史里清除,彻底删除次提交(--hard)!
比如,你今天开会被老板说了一顿,于是你心情不好,在代码里写了这么一句话my boss is a bitch!!!!!!
,来发泄心中的不爽。
$ vim test3.txt
third commit
add something
my boss is a bitch!!!!!! # add this
本来你是打算等会删除掉这句话,结果因为太忙给忘了,还一不小心把这句话保存到了仓库中
$ git add test3.txt
$ git commit -m "add some function"
$ git log --pretty=oneline --abbrev-commit
* c4d117c - (HEAD -> master) add some function
* cea4b44 - 7 commit: add something
* c950839 - 5 commit
* b205bd9 - 4 commit
* e9fe99e - Revert "2 commit"
* 224a71b - 3 commit
* 54b25d8 - 2 commit
* b2b9b24 - 1 commit
这下糟糕,当你想起来时内容已经保存到了仓库,你只能想办法撤销掉这次提交,这时就可以用上git reset,通过版本回退当前提交的上一次提交cea4b44 - 7 commit
来撤回上一次的提交,因为修改到内容完全不需要了,也就是撤回上次提交的所有数据,所以用 --hard
,即:git reset --hard cea4b44
,也可以写成git reset --hard HEAD^
在Git中,HEAD
表示当前版本,HEAD^
表示上一个版本,HEAD^^
就是上上一个版本,如果要回退到上10个版本是,可以写成HEAD~10
。所以撤回上一版本也可以写成git reset --hard HEAD^
$ git reset --hard head^
$ git log --pretty=oneline --abbrev-commit
* e005437 - (HEAD -> master) 6 commit
* c950839 - 5 commit
* b205bd9 - 4 commit
* e9fe99e - Revert "2 commit"
* 224a71b - 3 commit
* 54b25d8 - 2 commit
* b2b9b24 - 1 commit
大功告成,成功解决了一次职业危机!
如果已经推送到远端仓库,需要使用git push -f origin
将远端的提交内容给强行覆盖掉,但是慎用!要先确保远端没有其它人提交的情况下才能使用。
提交的内容有部分错误,需要更正(--mixed)
比如在提交的内容中犯了低级错误,把一个单词写错:func 写成 了 fuck,还提交到了仓库
$ vim test.go
package main
import "fmt"
fuck main() { // should be func
fmt.Println("hello world")
}
$ git add test.go
$ git commit -m "add: test.go"
$ git git log --pretty=oneline --abbrev-commit
* d53b465 - (HEAD -> master) add: test.go # <---新添加的commit
* cea4b44 - 7 commit: add something
* c950839 - 5 commit
* b205bd9 - 4 commit
* e9fe99e - Revert "2 commit"
* 224a71b - 3 commit
* 54b25d8 - 2 commit
* b2b9b24 - 1 commit
这种情况就可以通过git reset撤回此次提交,修改正确以后重新提交。因为需要保留修改的内容,所以使用 --mixed,即执行git reset --mixed head^
,将修改的内容撤回到工作区
$ git reset --mixed head^
$ git status
On branch master
Untracked files:
(use "git add ..." to include in what will be committed)
test.go
nothing added to commit but untracked files present (use "git add" to track)
内容已经撤回到工作区,修正错误 fuck 改为func
$ vim test.go
package main
import "fmt"
func main() { // 已经修正
fmt.Println("hello world")
}
修正错误后重新提交到仓库
$ git add test.go
$ git commit -m "add: test.go"
$ git git log --pretty=oneline --abbrev-commit
* d33a7cb - (HEAD -> master) add: test.go # <--- hash值已经变化
* cea4b44 - 7 commit: add something
* c950839 - 5 commit
* b205bd9 - 4 commit
* e9fe99e - Revert "2 commit"
* 224a71b - 3 commit
* 54b25d8 - 2 commit
* b2b9b24 - 1 commit
可以看到,提交的hash值已经由d53b465
变为d33a7cb
, 说明提交历史已经改变,d53b465
这次提交已经被提交d33a7cb
覆盖掉,最后使用 git show
命令查看最近一次提交的内容是否有误
$ git show
...
+package main
+
+import "fmt"
+
+func main() { // 已经修正
+ fmt.Println("hello world")
+}
(END)
可以看到,提交的内容已被修改,本次操作完成。
文章在最后终结一些git reset和git revert的区别
git reset 和git revert的区别
git revert和 reset都能撤回特定提交v1
的内容,但git revert是通过在新的提交里对提交v1
的内容进行反向操作来实现撤销,所以git revert不会改变提交历史;git reset 通过版本回退到特定提交v1-1
(v1的前一次提交),来直接撤销掉v1这次提交,从而撤回提交v1的内容,所以git reset会改变提交历史。
在工作中如何判断使用git reset 还是 git revert?
问题的重点在于你是否想保留之前的提交历史
- 如果觉得git提交历史就应该真实,不可更改,那就通过git revert命令,使用一个新的提交来中撤回错误提交的内容
- 如果觉得错误内容不应该保留在git提交历史中,就通过git reset撤回错误提交,然后更正后重新提交,从而覆盖掉之前的提交
如果本文对您有所帮助,希望您能点赞支持一下作者!