前情提要:Git应用详解第三讲:本地分支的重要操做git
git
做为一款版本控制工具,其最核心的功能就是版本回退,没有之一。熟悉git
版本回退的操做可以让你真真正正地放开手脚去开发,不用当心翼翼,怕一不当心删除了不应删除的文件。本节除了介绍版本回退的内容以外,还会介绍stash
的使用。vim
在git
中永远有后悔药可吃,老是能够回到版本库的某一个时刻,这就叫作版本回退;bash
如上图所示:当前master
分支指针指向D
,经过版本回退可使master
指向C
、B
或A
。进行版本回退的命令大致上有三种:reset
、revert
和checkout
。下面就来一一讲解:app
git reset
reset
命令能够添加不少参数,经常使用的有--mixed
、--soft
和--hard
三种。下图为一次完整提交的四个阶段:编辑器
三个参数大致上的区别为:工具
--mixed
:为默认值,等同于git reset
。做用为:将文件回退到工做区,此时会保留工做区中的文件,但会丢弃暂存区中的文件;--soft
:做用为:将文件回退到暂存区,此时会保留工做区和暂存区中的文件;--hard
:做用为:将文件回退到修改前,此时会丢弃工做区和暂存区中的文件;下面就来详细地讲解它们的使用方法:post
首先在master
分支进行四次提交,每次提交在test.txt
中添加一行文本信息:测试
--mixed
该参数为默认值,做用为:将文件回退到工做区中:以下图所示,将test.txt
文件回退一次提交:spa
能够看到第四次提交对test.txt
的修改操做被回退到了工做区当中,而且保留了工做区中第四次提交对test.txt
所作的修改,因此工做区中的test.txt
文件内容与回退前一致。3d
--soft
该参数的做用为:将文件回退到暂存区中:以下图所示,将test.txt
文件回退一次提交:
能够看到第四次提交对test.txt
的修改操做被回退到了暂存区当中,而且保留了工做区和暂存区中第四次提交对test.txt
所作的修改,因此,工做区中的文件内容与回退前一致。
--hard
该参数的做用为:将文件回退到修改前:以下图所示,将test.txt
文件回退一次提交:
能够看到test.txt
直接回到了进行第四次提交前,此时删除了工做区和暂存区中第四次提交对test.txt
所作的修改。因此,工做区变得干净了,test.txt
文件内容回退到刚完成第三次提交时。
为了方便演示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
的思想为:经过建立一个新提交来取消不要的提交。因此,提交数会增长。
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
)操做:
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^^
该指令的做用为重作所在分支的倒数第三次提交,会发生冲突,须要手动合并,完成重作操做:
git revert HEAD~n
该指令的做用为重作所在分支的倒数第n+1
次提交,会发生冲突,须要手动合并,完成重作操做。过程与上述一致,这里就再也不赘述了。
**总结:**经常使用
git revert commit_id
这种方式。
revert
操做思路很简单,再次经过revert
操做取消上一次的revert
操做(即所谓"负负得正")。
操做前,dev
分支上的提交记录和test.txt
文件内容以下:
经过:git revert --no-edit f4a95
重作提交dev2
(--no-edit
表示不修改新增提交的注释):
重作后,多了一次提交,而且test.txt
文件中删除了dev2
这一行内容。此时,能够经过:
git revert --no-edit 582d127
复制代码
重作上一次重作操做,以此达到取消上一次重作操做的目的:
如上图所示,虽然多出了一次提交,可是test.txt
文件中被删除的dev2
内容被恢复了,这样就撤销了revert
操做。
git checkout
git checkout commit_id
使用checkout
能够进行版本回退,如直接使用:
git checkout cb214
复制代码
回退到提交3rd
,此时会出现以下提示:
注意到,切换后HEAD
指向的再也不是master
分支,而是cb214...
即第三次提交,查看历史提交记录:
可看到只有3
次提交,什么意思呢?以下图所示:
经过git checkout
让HEAD
指针指向了第3
次提交,能够将它想象为一个新的分支。可是却没有实际建立分支,即此时head
指向的由提交1~3
组成的commit
对象链条处于游离状态;
接着,在HEAD
还指向游离的提交节点3
的基础上对文件作出新的修改:
master
分支,会出现下列错误提示显示:若是没有保存就从游离的提交上切换到master
分支,这一修改就会被checkout
命令覆盖。咱们能够在切换前进行一次提交操做:
此时的状态为:
Commit
对象链中进行了一次提交以后,再次经过:git checkout master
切换到master
分支:提示大意为:若是没有任何分支指向刚才在游离的Commit
对象链中进行的提交,那么该提交就会被忽略。此时的状态以下图所示:
若是想要建立一个分支保存(指向)这条游离的Commit
对象链,如今就是很好的时机。根据上述提示的命令:
git branch mycommit c4d5cc3
复制代码
建立指向commit_id
为c4d5cc3
的提交(即上述的提交节点5
)的分支mycommit
:
由此游离的commit
对象链得以被新分支所指向,并获得了保存,此时的状态以下图所示:
总结:
经过
checkout
进行版本回退会形成游离的提交对象链,须要额外建立一个分支进行保存;所以,使用
checkout
进行版本回退的思路为,先切换到想要回退的提交版本,再删除进行版本回退的分支dev
。最后,建立一个新的dev
分支指向游离的提交对象链,完成分支dev
的版本回退,简称"偷天换日";只要有分支指向,提交就不会被丢弃。
revert
与reset
的选择因为checkout
会形成游离的提交对象链,因此,通常不使用checkout
而是使用reset
和revert
进行版本回退:
revert
经过建立一个新提交的方式来撤销某次操做,该操做以前和以后的提交记录都会被保留,而且会将该撤销操做做为最新的提交;
reset
是经过改变HEAD
和分支指针指向的方式,进行版本回退,该操做以后的提交记录不会被保留,而且不会建立新的提交;
在我的开发上,建议使用reset
;可是在团队开发中建议使用revert
,特别是公共的分支(好比master
),这样可以完整保留提交历史,方便回溯。
版本回退主要有三大方式:reset
、revert
和checkout
,各方式的比较以下:
方法 | 效果 | 向前回退 | 向后回退 | 同步修改HEAD 与分支指向 |
---|---|---|---|---|
git reset --hard HEAD^ |
往前回退1 次提交 |
能 | 否 | 是 |
git reset --hard HEAD^^ |
往前回退2 次提交 |
能 | 否 | 是 |
git reset --hard HEAD~n |
往前回退n 次提交 |
能 | 否 | 是 |
git reset --hard <commit_id> |
回退到指定commit id 的提交 |
能 | 能 | 是 |
git revert HEAD |
重作最新一次提交 | 能 | 否 | 是 |
git revert HEAD^ |
重作倒数第二次提交 | 能 | 否 | 是 |
git revert HEAD^^ |
重作倒数第三次提交 | 能 | 否 | 是 |
git revert HEAD~n |
重作倒数第n+1 次提交 |
能 | 否 | 是 |
git revert commit_id |
重作指定commit_id 的提交 |
能 | 能 | 是 |
git checkout commit_id |
回退到指定commit id 的提交 |
能 | 能 | 否 |
从上表可知,只有下列三种方式能够自由地向前向后回退:
git reset --hard commit_id
git revert commit_id
git checkout commit_id
复制代码
可是,使用checkout
进行回退会出现游离的提交,须要建立一个新分支进行保存,因此不经常使用。
git stash
git stash
的做用git stash
指令的做用为:对没有提交到版本库的,位于工做区或暂存区中游离的修改进行保存,在须要时可进行恢复。具体应用场景以下:
在master
分支进行两次提交:1st
和2nd
,随后建立并切换到dev
分支。在dev
分支上进行一次提交(dev1
),此时两分支的状态为:
随后在dev
分支上给文件test.txt
添加一行dev2
,可是不提交到暂存区,直接切换到master
分支,会出现以下错误:
图中显示的错误大意为:在dev
分支上的修改会被checkout
操做覆盖。下面咱们来看看,将dev
分支上的这一修改操做添加到暂存区后,再切换分支,是否还会出现一样的问题:
可见仍是会出现该错误,这初步验证了位于工做区和暂存区中的修改都会被checkout
操做覆盖的结论。缘由以下图所示:
虽然在dev
分支上修改了文件,可是没有将这一修改操做进行提交。这样就不会产生提交节点,就如上图所示,修改dev2
是游离的,在切换分支的时候会被丢弃。
这种状况在平常开发中很常见,当在develop
分支上开发新功能的时候,master
分支出现紧急状况须要切换回去进行修复。可是,当前分支的新功能还没开发彻底,贸然切换分支,原来开发的内容就会因被覆盖而丢失,怎么办呢?
有人可能会说进行一次commit
不就能够了吗?确实能够。可是,这样不符合提交的代码就是正确代码的原则。更好的解决方法为使用git stash
,以下图所示:
可见git stash
能够将当前dev
分支上,位于在工做区或暂存区中的修改,在未提交的状况下进行了保存;而且将分支回退到修改前的状态,保存事后,就能够很顺畅地切换回master
分支了。
图中的
WIP
(working in progress
)表示的是正在进行的工做;
当咱们在master
分支上完成了工做,再次切换回dev
分支时,查看test.txt
文件:
发现切换分支前所作的修改dev2
消失了,这是为何呢?
其实,上面经过git stash
将dev
分支上工做区或暂存区中的修改,提交到了stash
区域进行保存,并将dev
分支回退到修改前的状态。以下图所示:
切换到master
分支时test
分支上的修改依旧会被覆盖。因此,再次回到dev
分支时须要从stash
区域中恢复切换分支前保存的修改;
怎样恢复经过git stash
保存到stash
中的修改呢?能够经过:
git stash list
复制代码
查看该分支上被stash
保存的修改:
继续给test.txt
文件添加内容:dev3
,并经过如下指令保存修改的同时添加注释:
git stash save '注释'
复制代码
stash
中存储修改的备注信息;test
分支上进行了两次修改,可是使用git stash
保存修改后,文件test.txt
并无实际被修改;stash
存储的修改方法有不少,主要有如下三种:
git stash pop
如图所示,经过上述命令将stash
中存储的最新一次修改恢复了。相信你已经发现了,stash
与栈很是相似:先保存的修改,排在最后,序号最大;后保存的修改,排在最前,序号最小;
恢复了最新一次修改后,再次查看stash
:
能够看到存储的修改只剩下一条了,由此可推断出git stash pop
做用为:
stash
中存储的最新一次修改;stash
中删除;git stash apply
如上图所示,使用该指令时发生了合并冲突。这是由于,stash
中保存的每一次修改表明的都是一个版本。
如上图所示,在test
分支上,进行第一次修改后,经过git stash
将该修改做为修改0
保存到stash
中,此时分支中的文件并无发生改变;
进行第二次修改后,经过git stash
将修改做为修改1
保存到stash
中,分支中的文件依旧没有发生改变;此时的stash
中至关于保存着同一分支上两个修改后的版本;
此时经过**git stash pop
取出修改0
,与test
分支进行合并;再经过git stash pop
**取出修改1
,再次与test
分支进行合并,两个版本合并天然会产生冲突。
手动解决冲突后,要进行一次提交才算完成了手动合并;随后查看stash
:
修改0
仍然存在,说明**git stash apply
**的做用为取出stash
中最新(前面)的修改并与分支进行合并。可是,stash
中存储的该修改并不会被删除;
git stash apply stash@{n}
这是最经常使用的方法,做用为从stash
中恢复特定的修改,而且不删除stash
中的该修改。
将test.txt
的两次修改经过git stash
存储到stash
中,以下图所示:
经过**git stash apply stash@{1}
**恢复stash
中存储的修改1
:
如上图所示,成功地恢复了stash
中的修改1
,而且stash
中的修改1
并无被删除;
总结:
git stash pop
:恢复并删除stash
中存储的最新修改;git stash apply
:恢复但不删除stash
中存储的最新修改;git stash apply stash@{0}
:恢复但不删除stash
中存储的特定提交;以上就是这一节的所有内容了,相信看到这里的你已经可以熟练地使用
Git
进行版本回退了。下一节将会介绍大名鼎鼎的Github
与Git
的图形化操做界面。期待与你再次相见!