我们之前使用 git stash
命令,将当时的修改进行了保存,保存的内容为: 新增了 a/b/c/hello.txt
文件,并添加了一行 Bye Bye.
,welcome.txt
文件新增了一行 Bye Bye.
实际上,恢复进度也是用这个命令。我们先看一下当前的分支状态和日志信息:
现在,我们处于 master 分支上,,查看保存的进度命令如下:git stash list
可使用 git stash pop
从最近保存的进度进行恢复 :
可以看到,之前保存的内容全都恢复了,列表中的该保存记录被销毁了。仔细查看一下输出:hello.txt
文件已经 add
了 ,等待 commit
,welcome.txt
文件修改了,还没被 add
, stash@{0}
这个进度记录被 drop
(删除)了。
再查看一下工作区的状态:
发现跟恢复进度时的输出是一致的。
现在,我们根据之前学过的各种命令,进行各种操作:
- 把当前暂存区的内容进行提交,即只提交
a/b/c/hello.txt
,不提交welcome.txt
:
可以看到hello.txt
文件被提交了,welcome.txt
文件并没有。
- 现在后悔了,想回到之前的状态,想要撤回这个提交:
可用命令 git reset --soft HEAD^
,注意要加上 --soft
,只是将分支提交记录回退到上一次提交。如果用默认的 --mixed
,会覆盖掉暂存区的。
可以看到,之前提交的内容回来了,提交记录也是之前的。
- 如果想把
welcome.txt
进行提交,而把hello.txt
撤出暂存区,可以这样操作:
这是 reset
的另一种用法,该用法不会改变引用,也不会改变工作区,而是用指定提交状态下(这里是 HEAD
)的文件替换掉暂存区中的文件。会回到 git add
命令之前的状态。
- 突然又不想提交
welcome.txt
文件,想从暂存区撤出(git reset
等价于git reset --mixed HEAD
) :
- 现在,想把工作区所有修改全部清除掉,包括
welcome.txt
的改动和目录a
以及下面的子目录和文件,可以使用git checkout .
命令:
现在工作区还有一个多余的目录 a ,git clean -nd
命令可以查看哪些文件和目录可以被删除,以免造成误删除:
发现 a 目录是可以被正常删除的,这时候再强制删除多余的目录和文件:
此时,工作区变得非常干净了。
最后,我们再详细讲解一下 git stash
的用法:
git stash
用于保存和恢复工作进度,这个命令非常有用。
命令:
git stash
作用:保存当前的工作进度,会分别对暂存区和工作区的状态进行保存。命令:
git stash list
作用:显示进度列表。很明显可以对工作进度进行多次保存操作,并且在恢复的时候可以进行选择。命令:
git stash pop [
]
作用:如果不使用任何参数,默认恢复最新保存的工作进度,并将恢复的工作进度从存储列表中移除。如果加上stash
参数(来自git stash list
显示的列表),则从指定stash
中恢复,恢复之后也将该进度从存储列表中移除。命令:
git stash [ save ] [ -k ] [
]
作用:通过 save 可以在保存进度的时候指定存储说明,格式如下:git stash save "this is a message"
,如果再加上-k
参数,则保存进度之后不会将暂存区重置,默认会将暂存区和工作区强制重置。命令:
git stash apply [
]
作用:跟git stash pop [
功能一样,但是恢复之后,该进度不会从存储列表中移除。] 命令:
git stash drop [
]
作用:删除存储记录,默认删除最新保存的记录,可指定记录进行删除。命令:
git stash clear
作用:清空所有的存储记录。命令:
git stash branch
作用:基于存储记录来创建分支。(分支功能后面再讲)
简单探秘一下 git stash
的机制,通过示例演示一下:
当前,我们没有存储任何进度,列表为空:
在 welcome.txt
文件中添加一行 Bye-Bye.
,并创建 hack-1.txt
文件(文件内添加一行 hello.
):
可以看到,暂存区中添加了新创建的 hack-1.txt
,修改过的welcome.txt
并没添加到暂存区,现在我们将当前的进度保存:
结果是:工作区恢复到了修改前的状态(实际使用了 git reset --hard HEAD
命令),文件 welcome.txt
的修改不见了,新增的文件 hack-1.txt
也不见了。
接下来,我们再做一个修改,新创建 hack-2.txt
文件,并添加一行文本为 fix.
,并尝试保存进度:
很遗憾,保存失败,说没有任何变更内容需要保存。可见,本地没有被版本控制系统跟踪的文件并不能保存进度,只能先 add
再保存:
现在有两个保存记录了,如下:
在保存进度时,最好提供说明,这样可以更好地通过进度列表找到保存的进度。每个进度的标识都是 stash@{
格式,像极了前面介绍的 reflog
的格式。实际上, git stash
命令就是用前面介绍的引用和引用变更日志 reflog
实现的:
可以看到,在 .git/refs/
和 .git/logs/refs
目录下,都存在 stash
文件。
跟分支引用一样, refs/stash
保存的就是 statsh list
中最新的提交ID ,还能看到该提交的相关记录:
简单总结一下:git stash
保存进度,实际上会将进度保存在引用 refs/stash
所指向的提交中。多次的进度保存,会指向最新的保存提交ID,而 refs/stash
引用的变化由 logs/refs/stash
记录下来。
那么,引用(refs/stash
) 是怎么同时保存暂存区进度和工作区中的进度呢?我们查看一下 refs/stash
的提交历史 【把 stash
看作分支对待】:
提交说明中的 WIP ,表示 Work In Progress
工作区进度,而 index on master ,包含 index 字眼
,表示暂存区进度。而且最新的提交是一个合并提交。
下面,我们来研究一下第一次的进度保存:
上面显示的三个提交对应着三棵不同的树。我们先把不同的状态区分出来,用 ‘原基线’ 代表进度保存时版本库的状态,即提交ID 4448fe8705
;用 '原暂存区' 代表进度保存时暂存区的状态,即提交ID 16ab29038
;用 '原工作区' 代表进度保存时工作区的状态,即提交ID d0f05c922
;
现在,开始对比各种差异:
^ 用法讲解: ^2 表示中的 2 表示是第几个父提交,比如上面的 stash@{1}
表示最新的提交 ID , stash@{1}^2
,表示其中的第二个父提交,也就是 16ab29038
,那么 stash@{1}^2^
就表示 16ab29038
的父提交,也就是 4448fe8705
- 原基线和原暂存区的差异比较:
刚好是第一次保存时,暂存区添加了新文件 hack-1.txt
并在里面添加了一行 hello.
;(不记得可以往前面的内容再看一下)
- 原暂存区和原工作区的差异比较:
刚好是第一次保存时,工作区的 welcome.txt
中添加了一行 Nice to meet you.
;(不记得可以往前面的内容再看一下)
- 原基线和原工作区的差异比较:
刚好是 1 和 2 的差异汇总 。
最后,我们用 stash@{1}
来恢复进度,再清空所有保存的进度:
清空之后,会发现 stash
相关的引用和 reflog
的 stash
文件都不见了,对不找对应的文件了: