scfhao 的个人博客

面向新手的Git教程:git 世界的储物空间

git

你看过修真小说吗?里面有一种空间类的法器/法宝,可能是口袋的形态,也可能是戒指形态,可以用意念把物品放入其中,也可以用意念从中取出物品。

如面向新手的 Git 教程:像玩游戏一样用 Git中描述,Git 中的提交就像游戏世界的剧情存档;Git 中的不同分支就像是同时存在的平行世界;而我们今天要讲的 Git 贮藏,就是 Git 中的储物空间……

工作区域与案板

Git 中的工作区域可以理解为项目目录中除了.git文件夹的内容。

说到工作区域,我就想到了案板(厨房里的那种)。对于备菜的人来说,案板就是他的工作区域。你可以在案板上切菜,你也可以在案板上和面,虽然你同时在同一块案板上切菜和和面,但是我极不建议你这么做,原因很简单,案板上放着的菜会影响你和面,这样很容易让你在和面的时候不小心把切好的菜也和进面里。

作为山西人,我有必要解释一下:“和”是一个多音字。在这里读和(huo)面,是做面条前把面和水混合在一起的步骤。

那什么是好的建议呢?就是保持案板干净。作为一名优秀的厨师,我的案板应该比我的脸面还干净!有人可能会来杠,“你不能同时在一块案板上和面和切菜只能说明你很菜,菜就多练!”,好,我不会对你做任何反驳!毕竟我现在连别人说我不知道 git-bash,我都不反驳了。

既然都是工作区域,这样的建议也适用于 Git 的工作区域。Git 中工作区域的干净则代表着当前的工作区域中没有尚未提交的修改。虽然 Git 不要求切换分支前工作区一定干净;但如果本地修改与目标分支冲突,Git 会拒绝切换。所以如果在切换分支这类操作前能保持工作区域干净,无疑可以为你避免很多不必要的麻烦。就像保持案板干净,虽然不会碍着别人什么事,但是可以为你自己避免一些麻烦。

再多说一句,保持案板干净,并不是要把案板供起来不用,而是每结束一项工作,就把案板清理干净。比如先在案板上切大葱,切完后就把切好的葱花放到一个碗里备用,这时案板就是干净状态;然后拿出洗好的西红柿,在案板上切西红柿,切完西红柿后再把切好的西红柿收到一个碗里备用,这时案板又到干净状态了。

储物空间

想象一个场景,当你正在切葱花时,厨师长突然对你说:先别切葱了,马上切几个西红柿。这个时候案板上放着切了一半的葱,在开始切西红柿前怎么处理这些葱?当你正在开发一项新功能时,领导要求你立马去修复线上的紧急 bug,你不得不停下手头的工作切换到别的分支去修复这个 bug。可是工作区域中修改了一半的代码要怎么处理?

如果你是那个厨师,你可以把切了一半的葱先临时放到一个碗里,这样案板就干净了。接着在案板上切西红柿,等切完西红柿后,把切好的西红柿交付给厨师长,这时案板也是干净的。然后取出没切完的葱,继续在案板上切葱,切完后把所有切好的葱放入之前放葱的那个碗里。

可以把改了一半的代码也临时放入一个储物空间里吗?答案是:可以的!Git 为我们准备的这个储物空间就叫stash(贮藏)。这个储物空间内部的结构是“栈”,后进先出。我们可以调用git stash push命令把当前工作区域中的修改存入储物空间,这时工作区域就干净了,然后切换到其他分支去修复紧急 bug,修复完成后回到之前的分支执行git stash pop,就可以从储物空间中取出上次处理了一半的修改放到工作区域中了。

stash是本地临时栈,不会随git push上传,也不适合长期保存重要工作。

作为一名优秀的程序员,我的工作区域也应该比我的脸面还干净!

总结

今天要讲的就到这里,Git 贮藏对于程序员的实用程度就像储物空间对于修真者的实用程度。实际上,Git 贮藏比修真界的储物空间还要强大,如果有兴趣,可以参考后面的附录。

附录 - 子命令介绍

创建一个贮藏

最简捷的命令就是git stash,等同于git stash push。

我们也可以给贮藏起个名字,便于后续知道这个贮藏做了什么:

git stash push -m "贮藏了什么"

还可以指定贮藏哪些文件:

git stash push a.txt b.txt

默认情况下,贮藏只处理已跟踪文件,含修改未暂存、修改已暂存及修改暂存后又修改等情况。

已跟踪的文件就是之前使用git add命令纳入 Git 管理的文件,新建的文件在未执行git add前为未跟踪状态。

如果你希望贮藏之后工作区仍保留已暂存的修改(只把未暂存的修改“收走”),可以使用--keep-index选项:

git stash --keep-index

如果希望把新创建的未跟踪的文件也贮藏起来,可以使用--include-untracked或-u选项。

git stash -u

但对于被忽略规则匹配的文件,即便使用--include-untracked或-u选项也不会被贮藏,如果你想贮藏这种文件,可以使用--all或-a选项。

如果同一个文件中有的修改想要贮藏,有的修改不想贮藏,可以使用--patch选项交互式地创建贮藏。

查看现有贮藏

使用git stash list查看贮藏栈:

$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log

请注意这个命令输出部分中的stash@{n},这是贮藏的标识符。后续要应用指定的贮藏、删除指定的贮藏,都需要使用这个标识符。花括号中的数字表示该贮藏在栈中的位置,越晚创建的贮藏数字越小,比如最新创建的贮藏就是stash@{0}。

使用git stash show查看某个贮藏改了哪些文件:

git stash show stash@{0}
# 下面是输出
 abc.txt | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

加上-p可以查看具体的修改内容(diff):

git stash show -p stash@{0}
# 下面是输出
 diff --git a/abc.txt b/abc.txt
 --- a/abc.txt
 +++ b/abc.txt
 @@ -1 +1,2 @@
 -old line
 +new line
 +another line

删除贮藏

删除指定的某个贮藏:

git stash drop stash@{n}

删除全部贮藏:

git stash clear

git stash clear通常不会询问确认,执行前最好先用git stash list确认

应用某个贮藏

git stash apply stash@{n}

或者

git stash apply

相当于应用最新的贮藏,也就是:

git stash apply stash@{0}

默认情况下,apply和pop不会恢复原来的暂存区状态,所有修改都会回到未暂存状态,可以使用--index选项:

git stash apply --index

应用并删除最新的贮藏

git stash pop

默认应用最新贮藏,并在成功应用后删除它;如果应用时发生冲突,贮藏通常会被保留,不会自动删除。另外pop命令也支持--index选项。

从贮藏创建分支

git stash branch <new branchname> [stash@{n}]可以从一个贮藏创建一个新分支,并同时应用并删除该贮藏。

这个功能也很实用。有时我们可能在本地工作区修改了多个功能,这时我们如果想为不同的修改分别创建分支,就可以先把属于不同功能的修改放入不同的贮藏中,然后再从贮藏创建出不同的分支。