前言
前情提要:Git应用详解第三讲:本地分支的重要操作
git作为一款版本控制工具,其最核心的功能就是版本回退,没有之一。熟悉git版本回退的操作能够让你真真正正地放开手脚去开发,不用小心翼翼,怕一不小心删除了不该删除的文件。本节除了介绍版本回退的内容之外,还会介绍stash的使用。
一、版本回退
在git中永远有后悔药可吃,总是可以回到版本库的某一个时刻,这就叫做版本回退;
如上图所示:当前master分支指针指向D,通过版本回退可以使master指向C、B或A。进行版本回退的命令大体上有三种:reset、revert和checkout。下面就来一一讲解:
Ⅰ.git reset
1.参数
reset命令可以添加很多参数,常用的有--mixed、--soft和--hard三种。下图为一次完整提交的四个阶段:
三个参数大体上的区别为:
-
--mixed:为默认值,等同于git reset。作用为:将文件回退到工作区,此时会保留工作区中的文件,但会丢弃暂存区中的文件; -
--soft:作用为:将文件回退到暂存区,此时会保留工作区和暂存区中的文件; -
--hard:作用为:将文件回退到修改前,此时会丢弃工作区和暂存区中的文件;
下面就来详细地讲解它们的使用方法:
首先在master分支进行四次提交,每次提交在test.txt中添加一行文本信息:
--mixed
该参数为默认值,作用为:将文件回退到工作区中:如下图所示,将test.txt文件回退一次提交:
可以看到第四次提交对test.txt的修改操作被回退到了工作区当中,并且保留了工作区中第四次提交对test.txt所做的修改,所以工作区中的test.txt文件内容与回退前一致。
--soft
该参数的作用为:将文件回退到暂存区中:如下图所示,将test.txt文件回退一次提交:
可以看到第四次提交对test.txt的修改操作被回退到了暂存区当中,并且保留了工作区和暂存区中第四次提交对test.txt所做的修改,所以,工作区中的文件内容与回退前一致。
--hard
该参数的作用为:将文件回退到修改前:如下图所示,将test.txt文件回退一次提交:
可以看到test.txt直接回到了进行第四次提交前,此时删除了工作区和暂存区中第四次提交对test.txt所做的修改。所以,工作区变得干净了,test.txt文件内容回退到刚完成第三次提交时。
2.写法
为了方便演示reset的各种使用方法,下面的指令都采用--hard参数。
git reset --hard HEAD^
该命令的作用为回退一次提交:
回退后的状态为:
可以看到,该方法会同时改变了HEAD和master指针的指向;
git reset --hard HEAD^^
该命令的作用为回退两次提交:
回退后的状态为:
同样,使用--hard参数回退,工作区是干净的;可以看到,该方法也会同时改变HEAD和master指针的指向;
git reset --hard HEAD~n
该命令的作用为回退n次提交:
可以看到使用了--hard参数,回退结果符合预期,并且该方法也会同步修改HEAD和分支master指针的指向。
注意:该方式只能向前回退,不能向后回退。
上述命令中的
HEAD可以更换为分支名,比如master:git reset --hard master~n该命令表示将
master分支回退n次提交。由于HEAD始终指向当前分支,所以使用分支名和使用HEAD效果是一样的。
git reset --hard commit_id
该指令的作用为回退到指定的commit id的提交版本;由于commit id是不会重复的,一般只需要写前几(6)位就可以识别出来。通过commit id的回退方式既可以向前回退,也可以向后回退。如下所示,从1st commit往后回退到4th commit,其中4th commit的commit id = bdb373...。
为了熟悉该指令,我们分两种方式进行回退:使用--hard参数与使用默认参数。
-
使用
--hard参数从图中可以看出:通过第四次提交的
commit_id: bdb373顺利地从第一次提交向后回退到了第四次提交,并且工作区干净。该方法也同时修改了HEAD和分支master的指向,具体过程为: -
使用默认参数
可以看到切换回了
4th commit,但是工作区的test.txt文件并没有变化;这是因为,在4th -> 1st的过程中,需要在工作区中删除test.txt文件中的2nd line、3rd line、4th line。通过默认参数--mixed,将4th commit对文件的修改回退到了工作区当中,如下图所示:这个过程丢弃了暂存区中对文件的删除操作,但是保留了工作区中对文件的删除操作。所以,工作区中的
test.txt文件仍然处于删除了三行内容的状态。此时只需要将修改操作从阶段
1移动到修改前的阶段0,即可将文件恢复到修改前的状态,并清空工作区。可以采用git restore test.txt实现:
Ⅱ.git revert
revert是回滚,重做的意思。不同于reset直接通过改变分支指向来进行版本回退,并且不产生新的提交;revert是通过额外创建一次提交,来取消分支上指定的某次提交的方式,来实现版本回退的。如下图所示,假如想要重做提交B,重做前与重做后的状态为:
所谓重做提交B,指的是在新建的提交B'中取消提交B中所做的一切操作。也就是说revert的思想为:通过创建一个新提交来取消不要的提交。所以,提交数会增加。
1.参数
git同样为revert提供了许多参数,常用的有以下三种。为了演示它们的作用,首先需要设置对应的测试环境:在dev分支上进行四次提交,每次提交都为test.txt添加一行内容:
-e
-e参数是--edit的缩写,为revert指令的默认参数,即git revert -e等同于git revert。该参数的作用为在重做过程中,新建一次提交的同时编辑提交信息。比如通过以下命令重做上述的dev2提交:
git revert f4a95
执行该指令后会创建一次新的提交来取消提交dev2所做的一切操作,并且会进入vim编辑器,编辑新提交的提交注释:
如下图所示,提交dev2为文件test.txt添加的dev2文本被取消了,并且dev分支上多了一次提交:
--no-edit
该参数的作用为不编辑由于revert重做,所新增提交的注释信息。如下图所示,通过:
git revert --no-edit f4a95b
重做提交dev2的过程中,并不会进入vim编辑器编辑新增提交的注释信息,而是采用默认的注释信息:Revert "dev2":
-n
-n参数是--no-commit的简写形式,作用为对revert重做某次提交时所产生的修改,不进行提交,也就是不会新增一次提交;
如下图所示,这是revert指令通过新建提交B'来取消提交B的过程,分为0~4个阶段。不添加-n参数时,revert指令会产生一次额外提交B',此时处于下图中的第3阶段。而使用-n参数时,虽然revert指令也会通过新建提交B'来重做提交B。但是,此时还处于生成提交B'的过程,还没有完全生成提交B',也就是处于下图中的第2阶段。
这种做法的好处是,允许我们干涉revert重做过程,手动进行提交。如下图所示,通过:
git revert -n f4a95
重做提交dev2的过程中,手动暂停了重做过程。虽然提交dev2对test.txt所做的修改已被撤销,但是这一重做操作还未进行提交:
这样我们既可以修改重做过程中不满意的地方,也可以随意添加注释。修改完后,通过手动提交的方式,完成重做(REVERTING)操作:
2.写法
revert指令也有多种写法,下面介绍主要的几种。为了方便演示,下列指令都采用默认参数-e手动编辑每次新增提交的注释信息。
git revert commit_id
这是最常用的写法,通过commit_id精准地选择想要重做的提交。分两种情况:
-
情况一:重做最新一次提交,不会发生冲突。
例如:通过以下指令,重做
dev分支上最新的一次提交dev2:git revert f4a95b首先进入
vim编辑器编辑新增提交的注释信息:随后完成重做操作,如下图所示;可见提交
dev2给test.txt添加的dev2内容被删除了,并且多出一次提交,说明重做成功: -
情况二:重做非最新一次提交,会发生冲突。
例如:通过以下指令,重做
dev分支上的第三次提交dev1:git revert dbde45会出现合并冲突:
使用
git mergetool指令,通过vim编辑器的工具vimdiff显示冲突文件test.txt:回车进入
vim编辑器界面,解决冲突:解决冲突之后,手动进行一次提交,完成
revert过程: -
为什么会出现冲突?
通过上面的例子不难看出,
revert操作生成的新提交其实是通过两次提交合并而成的。如下图所示:-
首先,将被重做的提交
dev1的前一次提交2nd复制一份,即图中的2nd'; -
然后,将它与当前分支的最新提交
dev2进行合并,由此生成revert操作新增的提交;
知道了
revert操作新增的提交的由来后,就不难解释为什么会出现合并冲突了,如下图所示:合并的两次提交中,文件
test.txt的内容不一样。git不知道以哪个版本为准,自然会导致自动合并失败,需要手动合并。 -
首先,将被重做的提交
git revert HEAD
该指令的作用为重做所在分支的最新一次提交,并且不会发生冲突:
git revert HEAD^
该指令的作用为重做所在分支的倒数第二次提交,会发生冲突,需要手动合并,完成重做操作:
git revert HEAD^^
该指令的作用为重做所在分支的倒数第三次提交,会发生冲突,需要手动合并,完成重做操作:












































