手把手带你玩git之 git stash

背景

git相对svn有许多好的设计,其中一个就是git stash功能。许多教程在介绍git stash的使用场景时,经常举例是:当你开发着新功能时,写到一半时(既没办法提交,又舍不得撤销),突然报告了一个BUG,你必须立马FIX这个BUG。

如何理解这个应用场景?

基础知识

首先得对git有基础知识,如下图所示:

git三个区和状态变迁

git 是分3个区的,分别是:

  • 工作区(working dir): 简单说就是我们看得见摸得着的目录和文件。
  • 索引区(index/stage): 又叫 暂存区,是被git管理(暂存)了,但尚未提交的。
  • 版本区(repository): 就是我们常说的版本,仓库。

状态变迁是这样的:

  • Untracked:当我们新建一个文件a.txt,并且编辑内容时,这个a.txt仅仅处于工作区,状态是untracked或叫unstaged,这个状态是不被git管理的,只被OS的文件系统管理。
  • Staged: 我们执行 git add a.txt后,文件才进入索引区,状态是staged,开始被git管理。
  • Committed: 再执行git commit a.txt -m 'add a.txt',状态是committed,纳入仓库。

可见git跟svn不同,git的提交,是要经历两个阶段的,它不会直接从工作区跳跃到版本区,中间隔着索引区/暂存区

git stash 内涵

有了上述分区概念后,我们用个示意图来表达下 git stash 具体做了什么:

git stash 的内涵

重点关注左半部分用橙色标注的部分,注意编号1、2、…、n和n+1。
这些步骤只有两个操作:

  • 一个是git stash,表示把索引区的内容转存到stash栈里面,同时工作区跟索引区保持一致(实际上工作区中的untracked的内容依然存在,不会被清除)。
  • 另一个是git stash pop,表示把转存到stash的弹回索引区。

实验实战

构建实验场景

笔者在做代码教程的时候,常希望利用git,把代码演变过程记录下来。比如我写了代码a.txt,演示了功能点a,并打tag为demo-a;接着写了代码b.txt,演示了功能点b,并打tag为demo-b。以便读者可以git checkout demo-agit checkout demo-b来重现笔者当时的代码场景,而不用一上来就看到太多无关的代码。

但是,笔者时常一不小心就写完了a.txt和b.txt,但是却迟迟没有提交,现在笔者想分开提交,先提交a.txt,再提交b.txt。同时,每次提交后,我都必须跑一边单元测试,以便验证正确,如果不正确应该回退这个提交,调整代码后,再提交。

问题来了:在已经提交a.txt,尚未提交b.txt的时候,为了排除b.txt是否对a.txt单元测试有干扰,必须把b.txt从工作区删了,但之后提交b.txt的时候,又得拿回来。

如何用 git stash 解决?

实验操作过程

  • 编写文件 a.txt和b.txt
➜  GitTutorial echo "aaa" > a.txt
➜  GitTutorial echo "bbb" > b.txt
➜  GitTutorial ls
a.txt b.txt
➜  GitTutorial
  • 借助git管理
➜  GitTutorial git init
Initialized empty Git repository in workspace/GitTutorial/.git/
  • 提交 a.txt
➜  GitTutorial git:(master) ✗ git add a.txt
➜  GitTutorial git:(master) ✗ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   a.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    b.txt

➜  GitTutorial git:(master) ✗ git commit a.txt -m 'add a.txt'
[master (root-commit) 9264345] add a.txt
 1 file changed, 1 insertion(+)
 create mode 100644 a.txt
➜  GitTutorial git:(master) ✗ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    b.txt

nothing added to commit but untracked files present (use "git add" to track)
➜  GitTutorial git:(master) ✗

上述代码,提交a.txt,需要两个步骤,git add a.txtgit commit a.txt -m 'add a.txt',每次操作后,用git status命令,验证下a.txt经历了从untracked状态到staged (to be committed)状态,再到纳入版本库的变迁过程。

a.txt提交完后,当前情况是:a.txt已经纳入版本仓库,b.txt依然是untracked状态。

用Eclipse Git 可视化看下各区的情况:(Project -> Team -> Commit ... -> Open Git Staging view)

Git可视化各区情况
  • 给a.txt提交打tag,标记为demo-a
➜  GitTutorial git:(master) ✗ git tag demo-a
➜  GitTutorial git:(master) ✗ git tag
demo-a
➜  GitTutorial git:(master) ✗
  • 暂时“删除”b.txt

为了对a.txt进行单元测试,排除b.txt对它可能产生的干扰,需要暂时“删除”b.txt,让工作区只剩下a.txt。执行 git stash,结果:

➜  GitTutorial git:(master) ✗ git stash
No local changes to save
➜  GitTutorial git:(master) ✗

提示的是

No local changes to save

OMG 怎么回事?

因为b.txt是untracked状态,并没进入索引区,git stash是把索引区转存起来。所以我们需要:

  • 把工作区所有内容先纳入索引区;
  • 然后把索引区转存到stash里面。
➜  GitTutorial git:(master) ✗ git add *
把b.txt纳入索引区

执行git stash

➜  GitTutorial git:(master) ✗ git stash
Saved working directory and index state WIP on master: 9264345 add a.txt
HEAD is now at 9264345 add a.txt
➜  GitTutorial git:(master) ls
a.txt

此时工作区只有 a.txt了,b.txt暂时消失了。可以跑只有a.txt的单测了。
可以用命令 git stash list查看stash栈的情况。

  • 恢复b.txt到工作区
➜  GitTutorial git:(master) git stash pop
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   b.txt

Dropped refs/stash@{0} (a6325932f00d22c978617102d9fe6b10b9605e79)
➜  GitTutorial git:(master) ✗ ls
a.txt b.txt
➜  GitTutorial git:(master) ✗

执行 git stash pop 后,b.txt回到工作目录了。

  • 提交b.txt,并打标签demo-b
➜  GitTutorial git:(master) ✗ git commit * -m 'add b.txt'
[master ec78d66] add b.txt
 1 file changed, 1 insertion(+)
 create mode 100644 b.txt
➜  GitTutorial git:(master) git tag demo-b
➜  GitTutorial git:(master) git tag
demo-a
demo-b
➜  GitTutorial git:(master) git log
  • 读者如果想看demo-a怎么办?

执行 git checkout demo-a

➜  GitTutorial git:(master) ls
a.txt b.txt
➜  GitTutorial git:(master) git checkout demo-a
Note: checking out 'demo-a'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 9264345... add a.txt
➜  GitTutorial git:(9264345) ls
a.txt
➜  GitTutorial git:(9264345)

注意:git报了一个警告,叫detached HEAD
这是什么意思呢? 请听下回讲解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1.git的安装 1.1 在Windows上安装Git msysgit是Windows版的Git,从https:/...
    落魂灬阅读 12,730评论 4 54
  • Git是分布式版本控制系统,那么它就没有中央服务器的,每个人的电脑就是一个完整的版本库,这样,工作的时候就不 需要...
    Royno7阅读 469评论 2 3
  • 我今天和爸爸 妈妈还有姐姐去学校的操场上朗读了, 我今天读的是孝弟三百千的第一章。今天下午还学了舞蹈 我感觉今天...
    杨皓宇yang阅读 186评论 0 0
  • 我俯视天地以钟声里的虔诚
    2020号阅读 467评论 20 32
  • 工具不重要,重要的是***。 我猜很多人像我一样经常看到这样的说法,可对于这种简单的说法,我一直不是很满意。刚刚有...
    Rico_阅读 948评论 8 4