情景:工作的时候,你正在项目中属于你的分支下开发一部分功能,但是还没有完善。Leader 突然叫你放下当前的工作,先修改项目中一个非常紧急的 BUG。修 BUG 嘛!家常便饭了。立即切回到上次发布版本(master 最近一次合并),新建一个分支去修复这个 BUG 。然而,在 checkout 到 master 的时候失败,提示你必须先 commit 当前分支的修改才能够 checkout 到其它分支。问题来了:当前分支的功能只是开发了一部分,不想那么快 commit。不 commit 的话,如果 reset 到前一次 commit 又浪费了之前所做的工作。怎么办?

Git 提供了一个存储机制,用户可以通过git stashgit stash save命令,将修改的跟踪文件与暂存改动存储到一个栈中。

1
git stash - Stash the changes in a dirty working directory away

注意,直接git stashgit stash save是不会将未跟踪文件暂存的。所以,当有未跟踪的文件的时候,要先add,或加 -u 参数

如,当前仓库中有文件test1.pytest2.py两个文件而且工作区都是清洁的,然后对test2.py进行修改,并且添加新文件test3.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git status
On branch lizs
nothing to commit, working tree clean
# change test2.py and add test3.py
$ echo " " >> test2.py
$ touch test3.py
$ git status -s
M test2.py
?? test3.py
# stashing the work
$ git stash
Saved working directory and index state WIP on lizs: 0df0b87 V1.0
$ git status -s
?? test3.py # Untracked files won't be stashed

那么,问题又来了。当我们 stash 修改的时候,发现还有文件没有被 stashed,想要将 stash 起来的修改恢复过来,重新 stash。要怎么做呢?

  • git stash list
    查看 stashed 的记录

    1
    2
    $ git stash list
    stash@{0}: WIP on lizs: 0df0b87 V1.0
  • git stash apply [<stash>]
    将存储栈的一条记录重新应用到当前分支,如果不指定<stash>,会默认恢复最近一次 stash 记录存储内容。git stash apply在这里即相当于git stash apply stash@{0}

    1
    2
    3
    4
    $ git stash apply
    $ git status -s
    M test2.py
    ?? test3.py

    打开 test2.py 文件可以看到其内容又回到了 stash 之前的状态了。我们再用 git stash list 查看存储栈的时候,发现刚刚的 stash 记录还在。存储的内容都恢复了,我觉得这条记录没意义了,想要删除了相应的 stash 记录,又要怎么做?

  • git stash drop [<stash>]
    删除存储栈的一条 stash 记录。如果不指定<stash>将会默认删除存储栈中最近一次的 stash 记录,git stash drop在这里即相当于git stash drop stash@{0}

    1
    2
    3
    $ git stash drop
    Dropped refs/stash@{0} (...)
    $ git stash list # 可以看到最近一次的 stash 记录已经被删除掉了
  • git stash pop [<stash>]
    恢复并删除一条 stash 记录的内容。相当于git stash applygit stash drop命令的结合。用法和git stash apply [<stash>]一样。

总结以上情境中操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# change test2.py and add test3.py in the clean working tree
$ echo "# hello world" >> test2.py
$ touch test3.py
$ git add .
$ git status -s
M test2.py
?? test3.py
$ git stash
Saved working directory and index state WIP on lizs: 0df0b87 V1.0
$ git stash list
stash@{0}: WIP on lizs: 0df0b87 V1.0
$ git status
On branch lizs
nothing to commit, working tree clean.
# checkout to master fix bug
$ git checkout master
# create a new branch to fix bug
$ git checkout -b issue-01
# go back wo branch lizs to continue you work after finishing the bug
$ git checkout lizs
# restore your coding
$ git stash pop

注意,恢复存储栈中的内容的时候可能会有冲突需要合并。

  • git stash --include-untrackedgit stash -u
    将修改的跟踪文件与暂存改动,还有未跟踪的文件存储起来,即相当于先 git addgit stash

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # change test2.py and add new file test3.py on a clean working tree
    $ echo " " >> test2.py
    $ touch test3.py
    $ git status -s
    M test2.py
    ?? test5.py
    # stashing the change and untracked file
    $ git stash -u
    Saved working directory and index state WIP on lizs: 0df0b87 V1.0
    $ git status
    On branch lizs
    nothing to commit, working tree clean.
  • git stash save --keep-index
    git stash -u相反,通过git add命令跟踪的文件不存储。注意,--keep-indexgit stash save的选项。即,git stash --keep-index是无效的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # change test2.py and add new file test3.py on a clean working tree
    $ echo " " >> test2.py
    $ touch test3.py
    $ git status -s
    M test2.py
    ?? test3.py
    $ git add -A
    $ git stash save --keep-index
    Saved working directory and index state WIP on lizs: 0df0b87 V1.0
    $ git status -s
    A test3.py
  • git stash --patch
    Git 不会直接存储修改过的内容,但是会交互式地提示哪些修改要存储。

  • git stash branch <branchname> [<stash>]
    将 stash 的一个记录内容恢复在一个新创建的分支 <branchname> 上。<branchname>,新分支的名称;<stash>,可选,不指定默认为最近一次 stash。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    # change test2.py and add new file test3.py on a clean working tree
    $ echo " " >> test2.py
    $ touch test3.py
    $ git status -s
    M test2.py
    ?? test3.py
    $ git stash list
    stash@{0}: WIP on lizs: 0df0b87 V1.0
    $ git status
    On branch lizs
    nothing to commit, working tree clean.
    # restore the a stash on a new branch
    $ git stash branch issue-01
    Switched to a new branch 'issue-01'
    On branch issue-01
    Changes not staged for commit:
    (use "git add <file>..." to update what will be committed)
    (use "git checkout -- <file>..." to discard changes in working directory)

    modified: test2.py

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

    test3.py

    no changes added to commit (use "git add" and/or "git commit -a")
    Dropped refs/stash@{0} (...)

    可以看到 git stash branch issue-01用分支 lizs 的一个 stash 记录的内容新建了一个 issue-01 分支,并且会自动删除该 stash 记录,当前位于 issue-01 分支下。那么 lizs 分支会怎么样呢?

    1
    2
    3
    4
    5
    $ git checkout lizs
    $ git status -s
    M test2.py
    ?? test3.py
    $ git stash --list # the stash has been dropped

    回到 lizs 分支之后,发现用来创建新分支的 stash 的内容居然也恢复在了 lizs 分支!WTF!那这个命令有什么卵用?