git stash 命令可以将您在工作目录中的修改临时暂存(stash)起来,以便您能切换上下文处理其他任务,后续再重新应用这些修改。当您处于代码修改中途需要快速切换分支或处理紧急问题,但又不希望提交不完整的代码时,这个功能非常实用。
暂存你的工作
执行 git stash 会将所有未提交的修改(包括已暂存和未暂存的变更)临时保存,并清空当前工作区。例如:
$ git status
On branch main
Changes to be committed:
new file: style.cssChanges not staged for commit:
modified: index.html
$ git stash
Saved working directory and index state WIP on main: 5002d47 our new homepage
HEAD is now at 5002d47 our new homepage
$ git status
On branch main
nothing to commit, working tree clean
此时您可以自由地进行其他修改、提交、切换分支等操作,后续随时可以重新应用暂存内容。
注意:暂存内容仅存储在本地仓库,推送时不会上传到服务器。
重新应用暂存修改
使用 git stash pop 可重新应用最近一次暂存修改并删除暂存记录:
$ git status
On branch main
nothing to commit, working tree clean
$ git stash pop
On branch main
Changes to be committed:
new file: style.css
Changes not staged for commit:
modified: index.html
Dropped refs/stash@{0} (32b3aa1d185dfe6d57b3c3cc3b32cbf3e380cc6a)
若想保留暂存记录,可使用 git stash apply:
$ git stash apply
On branch main
Changes to be committed:
new file: style.css
Changes not staged for commit:
modified: index.html
这在需要将同一份暂存应用到多个分支时非常有用。
暂存未跟踪或忽略的文件
默认情况下,git stash 不会暂存:
- 未添加到暂存区的新文件(untracked files)
- 被 .gitignore 忽略的文件
例如当存在未跟踪文件时:
$ script.js
$ git status
On branch main
Changes to be committed:
new file: style.css
Changes not staged for commit:
modified: index.html
Untracked files:
script.js
$ git stash
Saved working directory and index state WIP on main: 5002d47 our new homepage
HEAD is now at 5002d47 our new homepage
$ git status
On branch main
Untracked files:
script.js
使用 -u 选项包含未跟踪文件:
$ git status
On branch main
Changes to be committed:
new file: style.cssChanges not staged for commit:
modified: index.htmlUntracked files:
script.js
$ git stash -u
Saved working directory and index state WIP on main: 5002d47 our new homepage
HEAD is now at 5002d47 our new homepage
$ git status
On branch main
nothing to commit, working tree clean
使用 -a 选项可额外包含被忽略的文件:
管理多个暂存记录
你可以多次运行 git stash 来创建多个暂存,然后使用 git stash list 查看它们。默认情况下,暂存会被标识为一个“WIP”(工作进行中),它基于你创建暂存时所在的分支和提交。但随着时间的推移,你可能会难以记住每个暂存具体包含了什么内容:
$ git stash list
stash@{0}: WIP on main: 5002d47 our new homepage
stash@{1}: WIP on main: 5002d47 our new homepage
stash@{2}: WIP on main: 5002d47 our new homepage
为了提供更多的上下文信息,一个好的做法是为你的暂存添加描述,使用 git stash save "描述信息":
$ git stash save "add style to our site"
Saved working directory and index state On main: add style to our site
HEAD is now at 5002d47 our new homepage
$ git stash list
stash@{0}: On main: add style to our site
stash@{1}: WIP on main: 5002d47 our new homepage
stash@{2}: WIP on main: 5002d47 our new homepage
默认情况下,git stash pop 会重新应用最近创建的暂存:stash@{0}。 你也可以通过传入暂存的标识符作为最后一个参数,来选择要重新应用的暂存,例如:
$ git stash pop stash@{2}
查看暂存差异
你可以使用 git stash show 来查看某个暂存的摘要信息:
$ git stash show
index.html | 1 +
style.css | 3 +++
2 files changed, 4 insertions(+)
或者使用 -p 选项(或 --patch),来查看某个暂存的完整差异(diff):
$ git stash show -p
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..d92368b
--- /dev/null
+++ b/style.css
@@ -0,0 +1,3 @@
+* {
text-decoration: blink;
+}
diff --git a/index.html b/index.html
index 9daeafb..ebdcbd2 100644
--- a/index.html
+++ b/index.html
@@ -1 +1,2 @@
+<link rel="stylesheet" href="style.css"/>
部分暂存
你也可以选择仅暂存某个文件、一组文件,或者某些文件中的个别更改。如果你在运行 git stash 时加上 -p(或 --patch)选项,它会遍历工作目录中每个更改的“块(hunk)”,并询问你是否希望暂存它:
$ git stash -p
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..d92368b
--- /dev/null
+++ b/style.css
@@ -0,0 +1,3 @@
+* {
text-decoration: blink;
+}
Stash this hunk [y,n,q,a,d,/,e,?]? y
diff --git a/index.html b/index.html
index 9daeafb..ebdcbd2 100644
--- a/index.html
+++ b/index.html
@@ -1 +1,2 @@
+<link rel="stylesheet" href="style.css"/>
Stash this hunk [y,n,q,a,d,/,e,?]? n
你可以按 ? 键查看全部块操作命令。常用的有:
命令 | 描述 |
/ | 通过正则表达式搜索补丁(hunk) |
? | 显示帮助信息 |
n | 不暂存当前补丁(hunk) |
q | 退出(任何已经选择的补丁会被暂存) |
s | 将当前补丁拆分成更小的补丁 |
y | 暂存当前补丁 |
虽然没有明确的“取消”命令,但按下 CTRL+C(发送 SIGINT)可以中止暂存过程。
从暂存中创建分支
如果你当前分支的更改与你的暂存内容发生了分歧,那么在使用 git stash pop 或 git stash apply 时可能会产生冲突。为避免这种情况,可以使用 git stash branch 命令,从暂存中创建一个新分支:
$ git stash branch add-stylesheet stash@{1}
Switched to a new branch 'add-stylesheet'On branch add-stylesheet
Changes to be committed:
new file: style.cssChanges not staged for commit:
modified: index.htmlDropped refs/stash@{1} (32b3aa1d185dfe6d57b3c3cc3b32cbf3e380cc6a)
此命令会基于创建暂存时的提交检出一个新分支,并将暂存的更改应用到该分支上。
清理暂存
如果你决定不再需要某个暂存项,可以使用 git stash drop 删除它:
$ git stash drop stash@{1}
Dropped stash@{1} (17e2697fd8251df6163117cb3d58c1f62a5e7cdb)
你也可以删除所有暂存项:
$ git stash clear
Git Stash 的工作原理
如果你只是想知道如何使用 git stash,那么上面的内容已经足够。但如果你对 Git(尤其是 git stash)的底层实现感兴趣,请继续阅读。
实际上,stash 是以提交对象(commit objects)的形式存储在你的仓库中的。.git/refs/stash 是一个特殊引用,它指向最近一次创建的 stash,而之前创建的 stash 项则可以通过该引用的 reflog 访问。这就是你用 stash@{n} 来引用暂存的原因:你实际上是在引用 stash 引用的第 n 个 reflog 条目。
由于 stash 本质上是一个提交,你可以使用 git log 来查看它。
根据你暂存的内容,一次 git stash 操作会创建两个或三个新的提交。如下图所示:
- stash@{0}:一个新提交,用于保存你工作目录中受 Git 跟踪的文件的内容。
- stash@{0} 的第一个父提交:你运行 git stash 时 HEAD 指向的原始提交。
- stash@{0} 的第二个父提交:代表你运行 git stash 时的暂存区(index)。
- stash@{0} 的第三个父提交(如果有):表示运行 git stash 时工作目录中未跟踪文件的提交。仅在以下情况下创建:
- 工作目录中确实有未跟踪的文件;
- 并且你在执行 git stash 时使用了 --include-untracked 或 --all 选项。
Git stash 如何将你的工作目录和暂存区编码为提交:
- 在暂存前,你的工作目录可能包含对已跟踪文件的更改、未跟踪文件和被忽略的文件。这些更改中可能还有部分已被暂存。
- 执行 git stash 时,会将对已跟踪文件的更改编码为两个新的提交:一个表示未暂存的更改,一个表示已暂存的更改。同时,refs/stash 会更新指向这些提交。
- 使用 --include-untracked 选项时,未跟踪文件的更改也会被编码为一个额外的提交。
- 使用 --all 选项时,未跟踪文件和被忽略文件的更改会一起被编码到同一个提交中。
当你运行 git stash pop 时,上述提交中的更改会被应用到你的工作目录和暂存区中,同时 stash reflog 会更新以移除被弹出的提交。注意,这些被弹出的提交不会立即删除,但会成为将来垃圾回收的候选对象。