git stash帮你在切换分支前暂存不想提交的修改

前言

偶然间发现这个命令,正好解决了最近遇到的问题,使用 Git 管理代码时有这样一种场景,你正在分支 branch2 上开发新功能,突然刚刚提交测试的 branch1 分支上报了严重的BUG,需要尽快修改,这时候就需要切换到 branch1 分支上去修复BUG,但是你刚刚在分支 branch2 修改的文件还没有提交,接下来该怎么办?

如果本地的修改正好到达一个比较完整的阶段,可以直接提交,然后切换分支改BUG,那再好不过了。可是发生这种情况的时候往往是函数写了一半,或者功能大致写完但是还没来得及测试,这样的代码你敢提交吗?我感觉最好还是不要提交吧,那么如果这时候切换分支会有什么后果呢?一般会遇到两种情况:第一种是你在 branch2 分支上所做的修改与 branch1 上做过的修改不冲突,这时切换分支会将本地修改带到 branch1 分支,如果冲突了就是第二种情况,git checkout branch1 命令会被拒绝,当然你可以添加 -f 参数强行切换分支是能成功切换的,代价就是你会丢掉本地的所有修改。

git stash

上面提到的切换分支时遇到的两种情况一般都不是我们想要的,之前说过“在 Git 中没有真正的方法来做任何事情,这就是它的妙处!”,但是关于切换分支有这样一个建议,那就是在切换分支时尽量保证你的工作区和暂存区是干净的,而 git stash 命令就是用来做这件事的。

当我们遇到这种状况,本地的修改我不能提交,不想带到新切换的分支,更不想直接丢掉,只想把他们暂存到一个地方,等我切换完分支修改好BUG,再切换回来迎接他们。使用 SVN 想保存本地修改可以使用 patch,而使用 Git 想要解决这种情况更加方便,那就是利用 git stash 命令。

使用方法

这个命令的使用方法非常简单,最常用的 git stash pushgit stash pop 就能应付大部分情况了,其中 push 这个单词还可以省略,使用起来可以说是相当方便了,接下来尝试一下具体用法。

本地有修改时切换分支的两种情况

之前提到过这两种情况,一种是将当前分支修改带到要切换的分支,另一种是切换会导致冲突,本次切换操作被拒绝,下面具体操作一下。

将当前分支修改带到要切换的分支

首先以 dev 分支为基础新建 feature 分支

1
2
3
4
5
6
7
8
9
10
albert@homepc MINGW64 /d/gitstart (dev)
$ git status
On branch dev
Your branch is up to date with 'origin/dev'.

nothing to commit, working tree clean

albert@homepc MINGW64 /d/gitstart (dev)
$ git checkout -b feature
Switched to a new branch 'feature'

feature 分支上修改文件,再切换回 dev 分支,可以正常切换,git status 查看状态,发现修改的文件被带到了 dev 分支上

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
29
30
albert@homepc MINGW64 /d/gitstart (feature)
$ echo "test checkout">>README.md

albert@homepc MINGW64 /d/gitstart (feature)
$ git status
On branch feature
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md

no changes added to commit (use "git add" and/or "git commit -a")

albert@homepc MINGW64 /d/gitstart (feature)
$ git checkout dev
Switched to branch 'dev'
M README.md
Your branch is up to date with 'origin/dev'.

albert@homepc MINGW64 /d/gitstart (dev)
$ git status
On branch dev
Your branch is up to date with 'origin/dev'.

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md

no changes added to commit (use "git add" and/or "git commit -a")

这里要注意一点,在切换到 dev 分支的时候,有一行 M README.md的内容,表示这个文件在切换过来的时候就是修改的。

如果你想要的效果就是这样,就可以直接提交了,比如修改了代码发现分支弄错了,可以这样带着修改的内容切换分支,假设就是这种情况,我们直接在 dev 分支提交修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
albert@homepc MINGW64 /d/gitstart (dev)
$ git add README.md

albert@homepc MINGW64 /d/gitstart (dev)
$ git commit -m"add comments"
[dev 5f4181e] add comments
1 file changed, 1 insertion(+)
git
albert@homepc MINGW64 /d/gitstart (dev)
$ git status
On branch dev
Your branch is ahead of 'origin/dev' by 1 commit.
(use "git push" to publish your local commits)

nothing to commit, working tree clean

切换分支操作被拒绝

上面一种情况,在 feature 分支的修改被带到 dev 分支提交,我们在此基础上切换回 feature 分支看一下:

1
2
3
4
5
6
7
8
albert@homepc MINGW64 /d/gitstart (dev)
$ git checkout feature
Switched to branch 'feature'

albert@homepc MINGW64 /d/gitstart (feature)
$ git status
On branch feature
nothing to commit, working tree clean

发现此时 feature 分支上没有任何修改了,我们再改一次,然后切换到 dev 分支上试试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
albert@homepc MINGW64 /d/gitstart (feature)
$ echo "second try">>README.md

albert@homepc MINGW64 /d/gitstart (feature)
$ git status
On branch feature
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md

no changes added to commit (use "git add" and/or "git commit -a")

albert@homepc MINGW64 /d/gitstart (feature)
$ git checkout dev
error: Your local changes to the following files would be overwritten by checkout:
README.md
Please commit your changes or stash them before you switch branches.
Aborting

看到了吧,切换分支的操作被拒绝了,原因是这次切换可能导致本地的修改被覆盖,你可以在切换分支前尝试 commit 你的修改或者 stash 你的修改,等等,这里出现了 stash 这个单词,其实之前我都没注意到,不是修改好提交了就是直接加 -f 参数放弃了所做的修改,没想到还有这样神奇 stash 命令帮我渡过难关。

stash 一般操作

接下来展示一下 git stash 最常用的操作,也就是标题中提到的——在切换分支前暂存不想提交的修改,继续在上面的环境下操作,现在 feature
分支上修改了 README.md 文件,切换到 dev 分支时因为可能产生冲突而被拒绝,我们先来看一下文件状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
albert@homepc MINGW64 /d/gitstart (feature)
$ git status
On branch feature
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md

no changes added to commit (use "git add" and/or "git commit -a")

albert@homepc MINGW64 /d/gitstart (feature)
$ git diff
warning: LF will be replaced by CRLF in README.md.
The file will have its original line endings in your working directory
diff --git a/README.md b/README.md
index 76a124a..4c2bfb8 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
learn git branch command
m2
+second try

存储临时修改

对比显示我们增加了一行,然后执行 git stash 命令,再查看一下文件状态:

1
2
3
4
5
6
7
8
9
10
albert@homepc MINGW64 /d/gitstart (feature)
$ git stash
warning: LF will be replaced by CRLF in README.md.
The file will have its original line endings in your working directory
Saved working directory and index state WIP on feature: 6ae97d0 Revert "modify README 1"

albert@homepc MINGW64 /d/gitstart (feature)
$ git status
On branch feature
nothing to commit, working tree clean

执行完 git stash 之后我们发现,刚才的修改不见了,本地状态提示为 nothing to commit, working tree clean,这时我们再来切换分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
albert@homepc MINGW64 /d/gitstart (feature)
$ git checkout dev
Switched to branch 'dev'
Your branch is ahead of 'origin/dev' by 1 commit.
(use "git push" to publish your local commits)

albert@homepc MINGW64 /d/gitstart (dev)
$ git status
On branch dev
Your branch is ahead of 'origin/dev' by 1 commit.
(use "git push" to publish your local commits)

nothing to commit, working tree clean

这次切换就没有被拒绝,成功的切换到了 dev 分支,你可以在 dev 分支上进行想要的操作,全部操作完成后再切换回 feature 分支,我们这里就不操作了,直接切回 feature 分支查看一下状态:

1
2
3
4
5
6
7
8
albert@homepc MINGW64 /d/gitstart (dev)
$ git checkout feature
Switched to branch 'feature'

albert@homepc MINGW64 /d/gitstart (feature)
$ git status
On branch feature
nothing to commit, working tree clean

还原临时修改

我们看到工作区很干净,这时如果想还原刚才在 feature 分支的修改,可以使用 git stash pop 命令,我们执行一下然后查看状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
albert@homepc MINGW64 /d/gitstart (feature)
$ git stash pop
On branch feature
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md

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

albert@homepc MINGW64 /d/gitstart (feature)
$ git status
On branch feature
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md

no changes added to commit (use "git add" and/or "git commit -a")

刚刚被临时暂存的修改又恢复了,我们可以在 feature 分支上继续愉快地开发了。

stash 进阶操作

作为这么神奇的命令,stash 肯定不止这么一点点用法,接下来再列举几个较为常用的参数组合:

查看临时存储的所有条目 git stash list

当你使用几次 git stash 命令之后就会发现,这个命令有点像建立还原点,所以暂存命令不止可以用一次,当使用多次暂存命令之后就会形成一个暂存列表,这时可以使用 git stash list 命令查看所有的暂存操作,执行命令后大概就是下面的样子:

1
2
3
4
albert@homepc MINGW64 /d/gitstart (feature)
$ git stash list
stash@{0}: WIP on feature: 84dfd79 add test file
stash@{1}: WIP on feature: 6ae97d0 Revert "modify README 1"

这个列表的构成很像一个栈,stash@{0} 是栈顶元素,stash@{1} 是栈顶下面的一个元素,当使用 git stash 命令时会把新的临时存储信息压入栈顶,原来的信息向栈底移动,当使用 git stash pop 命令的时候又会把栈顶的元素弹出,恢复到工作区和暂存区。

临时存储未追踪的新文件 git stash -u

在开发过程中新添加的文件不属于任何一个分支,在不冲突的文件情况下也可以在切换分支的时候带到新的分支,默认在使用 git stash 命令的时候不会把这些文件临时存起来,如果想要存起来加上 -u 参数就可以了,执行之后你会发现这个新加的文件在工作区中消失了。

临时存储被忽略的文件 git stash -a

被忽略的文件在默认情况下也不会被 git stash 命令存储,想要临时存储这部分文件只要使用 -a 参数就可以了,这样不仅会把忽略的文件临时存储,连未追踪的文件也存储了起来。

stash 操作的标号

前面的 git stash list 命令也提到了,使用 git stash 命令的结果会形成一个栈形式的列表,其中 stash@{n} 就是每次临时存储对应的标号,针对于这些标号的操作也有很多,如果不加这些标号默认使用 stash@{0} ,也就是栈顶元素。

查看临时修改的具体内容 git stash show stash@{0}

这个查询过程和查询提交日志的形式有点像,主要展示了某次临时存储时改了哪些内容:

1
2
3
4
5
6
7
8
9
albert@homepc MINGW64 /d/gitstart (feature)
$ git stash show stash@{0}
test.txt | 1 +
1 file changed, 1 insertion(+)

albert@homepc MINGW64 /d/gitstart (feature)
$ git stash show stash@{1}
README.md | 1 +
1 file changed, 1 insertion(+)

恢复指定标号的临时修改 git stash apply stash@{0}

在恢复临时存储的修改时不仅可以使用 git stash pop 命令来恢复栈顶那一次修改,也可以按照标号恢复指定的某次修改,测试如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
albert@homepc MINGW64 /d/gitstart (feature)
$ git stash apply stash@{1}
On branch feature
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md

no changes added to commit (use "git add" and/or "git commit -a")

albert@homepc MINGW64 /d/gitstart (feature)
$ git stash list
stash@{0}: WIP on feature: 84dfd79 add test file
stash@{1}: WIP on feature: 6ae97d0 Revert "modify README 1"

albert@homepc MINGW64 /d/gitstart (feature)
$ git stash apply stash@{1}
error: Your local changes to the following files would be overwritten by merge:
README.md
Please commit your changes or stash them before you merge.
Aborting

这个命令在执行后,指定标号的修改会被恢复到工作区和暂存区,但是临时存储的列表不会被删除,这时可以尝试再次恢复相同标号的修改到工作区,你会发现本次操作因为修改了相同的文件而被拒绝。

刪除指定标号的临时存储的修改 git stash drop stash@{0}

可以在临时存储列表中删除指定标号的一些修改,可以测试一下看看效果:

1
2
3
4
5
6
7
8
9
10
11
12
albert@homepc MINGW64 /d/gitstart (feature)
$ git stash list
stash@{0}: WIP on feature: 84dfd79 add test file
stash@{1}: WIP on feature: 6ae97d0 Revert "modify README 1"

albert@homepc MINGW64 /d/gitstart (feature)
$ git stash drop stash@{1}
Dropped stash@{1} (8408e56305fabcd82c1d05db18e177c89c47c5ac)

albert@homepc MINGW64 /d/gitstart (feature)
$ git stash list
stash@{0}: WIP on feature: 84dfd79 add test file

利用临时存储的修改内容新建分支 git stash branch <branchname> [<stash>]

一般这种情况就是使用过多次 git stash push 命令,而本地分支还修改了其他内容,直接恢复之前的修改不太合适,所以利用这个命令新建一个分支,分支的内容以指定的存储标号 <stash> 对应的提交 commit-id 为基础,然后应用 <stash> 的修改,实际上就是新建了一个对应 <stash> 的分支,继续之前未完成的工作,<stash> 默认为 stash@{0},测试如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
albert@homepc MINGW64 /d/gitstart (feature)
$ git stash branch feature1
Switched to a new branch 'feature1'
M README.md
On branch feature1
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
modified: test.txt

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

albert@homepc MINGW64 /d/gitstart (feature1)
$ git stash list

这个操作会消耗掉对应 <stash> 标号的临时存储的内容,将这些内容从存储列表中移除。

stash 的注意事项

这个命令不仅可以在同一分支上存储和还原,也可用于不同分支之间,这时就可以有一个应用,当我们发现在错误的分支上开发了代码,可以先 git stash push 将这些修改临时存储起来, 然后切换到正确的分支,再执行 git stash pop 命令将刚才的修改引用到现在的分支上。

git stash push 命令默认是存储工作区和暂存区的修改内容的,但是 git stash pop 命令在还原是默认将所有的修改还原到工作区,如果想还原到对应的暂存区,需要加额外的参数,像这样 git stash pop --index

总结

  • 这个命令挺有用的,在合作开发的时候经常碰到临时问题需要处理,切换分支暂存一下很方便
  • 感觉这个命令其实和 commit 也很像的,在操作过程中你会发现,它也有自己的 hash-id,但是不会放到 commit 列表中
  • 这个命令参数也有好多个,不过记住常用的就可以面对大多数情况了,简单列举下
  • git stash push 会将当前本地的修改临时保存起来,push 可以省略
  • git stash list 查看当前stash push操作的记录
  • git stash pop 取出最近一次修改,并应用到本地
  • git stash apply stash@{n} 应用 stash@{n} 对应的修改,但是不删除这条记录
  • git stash show stash@{n} 展示 stash@{n} 对应的修改的实际修改内容
Albert Shi wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客