git使用进阶——版本穿梭reset三种模式理解

Posted 涂宗勋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了git使用进阶——版本穿梭reset三种模式理解相关的知识,希望对你有一定的参考价值。

在日常工作中使用git,除了正常场景外,会有各种异常场景,比如误提交了代码需要回退就是其中一个常见的场景,这就需要用到reset
要理解reset相关的内容,首先需要理解git工作区相关概念,可以参考上一篇:
https://tuzongxun.blog.csdn.net/article/details/120315735
上一篇关于工作区的图也先拿过来用一下:

首先,reset翻译过来是重置,常用于回退操作,但是实际上它的作用不局限于回退,而是可以进行版本之间的切换。
reset操作有三种模式,分别是softmixedhard

soft

例如我有一个被git管理的文件·test.txt·,经过了三次提交,使用git log --pretty=oneline --abbrev-commit查看结果如下:

b6430be (HEAD -> master) update username
73c9b49 add username
b161811 init

可以看到当前最新的是b6430be,提交信息为update username
上边三次提交的具体内容如下:

  1. test.txt中写入一行git init,然后提交;
  2. test.txt中写入一行add username tzx,然后提交;
  3. test.txt中修改add username tzxadd username tzx123,然后提交。

假如这时候发现最后一次的提交内容不对,上一次的才对,需要回退到和上一次提交的内容一样,我目前知道的有两种常用做法,一种是直接修改代码为和上一个版本的一样,然后重新提交。
这样做了之后使用git log就会查到四个提交记录,但是第四次的内容和第二次的实际是一样的。
这种做的好处是,会直接留下修改记录,但是坏处也是会直接留下中间记录。
如果有的公司或者项目就是需要直接留下这种记录,那么就必须这样做,他们可能会在远程仓库设置不允许强制push
同时,也有公司或者项目不需要这种中间的记录,允许直接擦除,以使提交记录相对少一些,那么就可以使用reset进行回退。
例如,这里可以先执行git status看一下状态:

On branch master
nothing to commit, working tree clean

证明当前工作区、暂存区和本地版本库都是一致的。然后执行git reset --soft 73c9b49回退到指定版本(这里其实还可以有其他的写法,但是我觉得这种直接指定id的方式个人更习惯),之后再执行git log --pretty=oneline --abbrev-commit查看日志,结果如下:

73c9b49 (HEAD -> master) add username
b161811 init

可以看到,现在就只剩下了两次提交记录,然后再使用git status查看状态,结果如下:

On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   test.txt

理解这里的内容,就需要开头提到的上一篇的知识了。
这里的意思是,git当前状态为:test.txt文件有修改,并且已经修改已经添加到了暂存区,但是还没有提交。
根据提示可以看到,这种状态下可以进行两个操作,要么commit到本地版本库,要么restore到工作区。
那么这里先使用commit操作,例如git commit -m "reset",然后再查看日志:

ede0234 (HEAD -> master) reset
73c9b49 add username
b161811 init

然后再使用git status会看到现在工作区、暂存区和本地版本库又都是一致的了。同时,在上边几个reset之后的任意时间查看test.txt文件,会发现内容实际都是之前第三次提交后的内容。
那么这里重新执行git reset --soft 73c9b49进行回退,再查看日志,结果如下:

73c9b49 (HEAD -> master) add username
b161811 init

执行git status还是会看到之前一样的内容:

On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   test.txt

这一次进行第二种操作,执行git restore --staged test.txt,然后再执行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

很显然,根据上一篇的知识可以知道,这里的意思是当前文件的修改已经只存在于工作区了,也有两种操作可以选择,要么add到暂存区提交,要么放弃修改。
add到暂存区,这个就是之前的普通操作,这里就直接看一下第二个选择,执行操作之前,先确认一下文件内容,如下:

git init
add username tzx123

确认完之后,执行放弃修改的操作git restore test.txt,然后再使用git status会看到结果如下:

On branch master
nothing to commit, working tree clean

上边内容证明工作区、暂存区和本地版本库的内容又一致了。然后再看一下test.txt文件的内容,结果如下:

git init
add username tzx

显然,到这里才真正回到了之前想要的回退效果。总结一下步骤如下:

  1. git reset --soft 73c9b49回退到暂存区;
  2. git restore --staged test.txt回退到工作区;
  3. git restore test.txt回退工作区的内容,完成回退;

这里画一个简单的图来说明每个操作后文件内容的变化:

reflog和git log -g

上边说直接修改内容之后再提交会留下直接记录,使用reset没有直接记录,这里重点是直接两个字。
也就是说,没有直接记录,而不是没有记录。
这里的直接记录指的是使用git log查看到的记录,那么实际上使用git reflog或者git log -g还是可以查看到记录的,例如这里执行git reflog --abbrev-commit,结果就如下:

73c9b49 (HEAD -> master) HEAD@0: reset: moving to 73c9b49
ede0234 HEAD@1: commit: reset
73c9b49 (HEAD -> master) HEAD@2: reset: moving to 73c9b49
b6430be HEAD@3: reset: moving to HEAD
b6430be HEAD@4: commit: update username
73c9b49 (HEAD -> master) HEAD@5: commit: add username
b161811 HEAD@6: commit (initial): init

很显然,这里的提交记录、reset记录都是可以查到的。

hard

上边也说了,reset常用来回退,但是并不是只能回退。或者说,这里通常所说的回退其实只是指git log里看到的内容的回退。
那么借助于git reflog或者git log -g,实际还可以实现前进操作。
例如,在上边的基础上查看当前的log如下:

73c9b49 (HEAD -> master) add username
b161811 init

也就是当前显示有两次提交,最新的是73c9b49,但是根据git reflog我们实际可以看到在这个提交之后还有很多的操作,比如现在我们又想回到最初三次提交的那个样子,我这里就可以借助resethard模式直接跳到之前第三次提交的那个点,例如执行git reset --hard b6430be,然后再查看日志,结果如下:

b6430be (HEAD -> master) update username
73c9b49 add username
b161811 init

这里可以翻到最开始的地方进行比较,就能看到和第一次查看log时的结果一样了。
同时,如果现在执行git status会看到结果是这样:

On branch master
nothing to commit, working tree clean

也就是说当前也是工作区、暂存区、本地版本库状态都一致。那么这时候再查看一下test.txt文件内容,可以看到也成了最开始回退之前的样子:

git init
add username tzx123

为了进一步说明问题,这里再回到第二次的提交,执行git reset --hard 73c9b49,然后再查看日志会看到又只剩下了两次提交。
执行git status也会看到是干净的不需要提交的、三区一致的状态,查看文件内容也是成了下边这样:

git init
add username tzx

显然,和soft不一样的是,hard模式是一步到位,相当于把上边soft模式的三步都操作了。数据变化如图所示:

mixed

那么上边两种模式,都是必须要手动加对应模式的参数的,也就是说不是默认的模式,默认的是mixed,可以不指定。
当前文件内容如上,没有末尾的123,log结果如下:

73c9b49 (HEAD -> master) add username
b161811 init

为了保证三个示例看起来一致,这里先再执行git reset --hard b6430be跳到第三次提交的位置,然后使得查看日志结果如下:

b6430be (HEAD -> master) update username
73c9b49 add username
b161811 init

然后再执行使用mixed模式回到第二次提交的位置,使用git reset 73c9b49或者git reset --mixed 73c9b49后,再使用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

no changes added to commit (use "git add" and/or "git commit -a")

这时候如果查看文件内容,会看到还是第二次提交的内容,并没有回到第三次提交的有123结尾的。
根据对工作区相关概念的理解,可以知道上边的意思是文件test.txt有修改,这个修改目前存在于工作区,可以有两个操作可以选择,一个是add到暂存区,另一个是放弃当前工作区的修改。同时,add操作也可以直接使用commit -a直接提交。
那么add操作实际也相当于是修改了新内容后要进行的普通操作,而放弃工作区修改,实际就是和soft模式的第三步一样。
总结下操作步骤就是:

  1. git reset --mixed 73c9b49回退到工作区;
  2. git restore test.txt回退工作区的内容,完成回退;

那么mixed模式下每步操作的数据状态变化如下图:

总结

那么最后总结一下这三种模式的区别,大致如下:

  1. soft模式,会改变本地版本库的内容,保留暂存区和工作区之前的内容;
  2. mixed模式,会改变本地版本库和暂存区的内容,保留工作区之前的内容;
  3. hard模式,会把本地版本库、暂存区、工作区的内容全部改变。

以上是关于git使用进阶——版本穿梭reset三种模式理解的主要内容,如果未能解决你的问题,请参考以下文章

63.Git Reset 详解版本回滚的三种模式

git reset 的三种模式

Git reset的三种模式 傻傻分不清楚

Git reset的三种模式 傻傻分不清楚

Git reset的三种模式 傻傻分不清楚

Git reset的三种模式 傻傻分不清楚