git reset 和 git revert使用详解

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)

注意这句话的重点:

  1. 撤回提交的内容,不是撤回提交(commit),之前的提交的历史不会发生任何改变。
  2. 会产生一个新的提交。它是通过在新的提交中,将之前提交的内容反向操作了一遍,比如:之前提交内容是 添加 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 <paths>' or 'git rm <paths>'
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 <file>..." to update what will be committed)
  (use "git restore <file>..." 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 <file>..." 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
  1. 例子一:使用--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 <file>..." to update what will be committed)
      (use "git restore <file>..." 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之后修改的内容放到了工作区

  1. 例子二:将恢复原样,使用--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 <file>..." to unstage)
     modified:   test.txt
     modified:   test3.txt
    

    git status显示为绿色,说明也有内容,不过这次内容不再工作区,而是在暂存区,这就说明 --soft也不会清除掉这个版本6 commit e005437之后修改的内容,而是把该版本6 commit之后所修改的内容存放到了暂存区

  1. 例子三:将恢复原样,使用--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 <file>..." 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撤回错误提交,然后更正后重新提交,从而覆盖掉之前的提交

如果本文对您有所帮助,希望您能点赞支持一下作者!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270