一、基础
回滚是一个版本管理系统最重要的功能之一。
由于Git的设计,导致Git的回滚在新手面前十分难理解。
本文从结果出发,总结了对于使用者来说最常用的操作,在不了解Git的深层原理时,可以快速上手使用。
二、说明
实验环境:Windows 10,Git版本2.21.0
为了方便演示,编者自己只做了一个简单的Git项目,项目中只有一个README.md文件。
第一次提交内容为v1,第二次为v2,第三次为v3,现在的提交历史及仓库状况如下图所示。
一图胜千言。
我们把仓库(Repository)、暂存区(Index)和工作目录(Working Directory) 同时画进图中,方便后续理解。
大概就是这个样子:我们在master分支上提交了三次,所以仓库中有三次记录,暂存区和最新的提交一样,工作目录我们没有修改什么,和仓库一样。
深入理解这张图片需要理解如下内容:
- 分支的本质是一系列提交的链接,分支(图中master)一般情况下指向最新的提交(链末尾),我们通过引用指向提交的指针来操作分支。
- HEAD也是一个指向提交的指针,表示当前所在的分支,一般情况下指向当前分支。
- Git存储文档的方式见【Git常用】之深入理解Git存储文档的方式。
三、常用操作总结
3.1 回顾add与commit
将工作目录中的README修改为v4,本次提交想输入的提交信息为“forth commit”,现在如下:
运行$ git add README.md
,相信你一定可以理解下图。
运行$ git commit -m 'forth commit'
,就变成了如下的样子。
3.2 取消add
这里说“取消add”,可以理解为(git的做法)将Repository的最新提交覆盖到Index中。
按照git bash中的提示,使用命令$ git reset HEAD <file>
可以达到此目的。
我们回到3.1中运行$ git commit
,提交v4之前的这一步。
运行$ git reset HEAD README.md
,运行结果如图
想要理解这个命令做了什么,请看3.4。
3.3 取消修改
很简单,就是我修改的都不想要了,是整个工作目录回退到一个干净的状态,和仓库的代码一致。
我们回到3.1中$ git add
,添加v4之前这一步。
运行$ git checkout -- README.md
,运行结果如图
3.4 取消一次提交
以上展示的都没有操作git提交的历史,都是在完好的提交历史基础上进行三个区域的操作。
假设v4版本的提交不想要了,又如何操作呢?
让我们回到这一步。
经历过3.2,我们思考,是不是把v3提交从Repository中复制出来,复制给Index和Working Directory不就搞定了?
Git确实也是这么做的。
使用$ git reset --hard HEAD^
,执行结果:
解析:
- 第一步,将HEAD和master向目标提交移动,
HEAD^
表示HEAD所在的提交的父提交 - 第二步,将目标提交复制到Index。
- 第三部,将Index复制到Working Directory。
-- hard
参数在这里起了作用,表示需要修改Working Directory
有意思的是,我们可以通过指定参数,让git reset
命令并不都执行这三步。
$ git reset --soft HEAD^
只会执行第一步,这样的结果看起来如下。
$ git reset --mixed HEAD^
只会执行到第二步,这样的结果看起来如下。
等等,这个命令的作用,和之前3.2提到的$ git reset HEAD <file>
有异曲同工之妙。
是的,没错,他们是一个命令。原因在于:
$ git reset
默认携带--mixed
参数$ git reset HEAD <file>
指定的提交是HEAD,即master最新的提交,所以在3.2的条件下,git将v3的内容复制出来给Index
这就是git的做法。
还有一个区别是3.2的命令指定了一个文件,这个很好理解,就是该文件单独处理。
总结一下,关于$ git reset
- 第一步,移动HEAD和分支指针到目标提交。
git reset --soft [branch]
在此停止。 - 第二步,将目标提交复制到Index。
git reset <--mixed> [branch]
在此停止。 - 第三部,将Index复制到Working Directory。
git reset --hard [branch]
在此停止。
想要修改某些文件,则可以使用git reset <--mixed> [file]
,--soft
和--hard
都不可以加文件
但是这会跳过第一步!
我们来尝试一下,仓库如下图所示状态。
执行$ git reset --mixed HEAD^ README.md
,结果如下。
可以看到,HEAD和master没有移动,但是Index变成了v3的内容,即执行了第二步,将HEAD的父提交v3复制到Index,并停止。
3.5 checkout
现在来理解checkout。
$ git checkout [branch]
和$ git reset --hard [branch]
一样,都可以操作三个区域。不同的是:
- checkout只移动HEAD,不改变master(举例来说)。
- checkout不会像
--hard
一样强制修改Working Directory,他会检查甚至自动合并。
运行$ git checkout 2b0d18b
结果如图。
这时git处于“detached HEAD”状态。
如果使用$ git checkout -- <file>
,则一样会跳过“第一步“和”第二步“,使用HEAD中文件覆盖工作区域。
reset命令并没有删除提交,v4还在那里,我们可以使用SHA-1值去索引到它。
四、总结
三个区域的文件迁移关系如下图所示。
注:
- 这里的迁移关系只是示意图,并没有表示出命令完整的作用。reset有三个步骤,请理解以后再来看此图。
- 关于
$ git checkout -- <file>
的作用,编者亲自做了实验,发现并没有修改Index区,和《Git Pro》中的说法不一致,欢迎大家留言讨论。