Git stash:暂时搁置修改,再回到原来的工作
原文:Git stash,Atlassian Git Tutorials。作者:Atlassian 团队,原页未列个人署名。以下为正文完整中文译稿;针对现行 Git 行为的补充均标为编者注。
git stash 会暂时搁置工作副本中的修改,让你先去处理别的事情,之后再回来重新应用这些修改。当代码写到一半,还不适合提交,却需要迅速切换任务时,stash 非常有用。
搁置当前工作
git stash 会把尚未提交的修改——包括已暂存和未暂存的修改——保存起来,供以后使用,然后从当前工作副本中撤去这些修改。例如:
$ git status
On branch main
Changes to be committed:
new file: style.css
Changes 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。
stash 保存在本地 Git 仓库中;通常的 git push 不会把这些记录传到服务器。
重新应用搁置的修改
可以用 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)
pop 会把修改重新应用到工作副本,并从 stash 列表中移除该记录。
也可以用 git stash apply 把修改应用到工作副本,同时保留 stash 记录:
$ git stash apply
On branch main
Changes to be committed:
new file: style.css
Changes not staged for commit:
modified: index.html
如果想把同一份修改应用到多个分支,保留记录就很有用。
掌握这些基本用法后,还需要了解一个默认行为:Git 不会自动搁置未跟踪文件或已忽略文件中的修改。
搁置未跟踪文件和已忽略文件
默认情况下,git stash 会保存:
- 已经加入 index 的修改,即已暂存修改。
- Git 已跟踪文件中的未暂存修改。
它不会保存:
- 工作副本中新建、但尚未暂存的文件。
- 被忽略的文件。
假如在前面的例子中再创建一个 script.js,但不执行 git add,git stash 就不会把它一并搁置:
$ 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
编者注:原文这一段开头出现了 $ script.js,却没有给出创建文件的实际命令。它不是通用的建文件命令,故在上方记录中移去这一孤立行,并在正文明确前提为“已经创建 script.js”;其余命令和关键输出保留。
加入 -u,也就是 --include-untracked,便会把未跟踪文件也存入 stash:
$ 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 -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,也就是 --all。

管理多条 stash 记录
你不必只保留一条 stash。可以多次执行 git stash,再用 git stash list 查看记录。默认描述只是 WIP(work in progress,进行中的工作),加上创建 stash 时所在的分支和提交。时间一长,这些描述很难让人想起每条记录具体包含什么:
$ 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
因此,最好为 stash 写一段描述。原文使用 git stash save "message":
$ 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 官方手册已将 save 标为弃用,建议使用 git stash push -m "add style to our site"。原例保留用于忠实呈现来源;日常新命令宜采用 push -m。与 save 不同,push 还可以接受 pathspec 选择路径。
默认情况下,git stash pop 会重新应用最新创建的记录,即 stash@{0}。也可以把记录标识放在最后,选择另一条记录:
$ git stash pop stash@{2}
编者注:在 PowerShell 等会解释花括号的 shell 中,宜把标识写成 'stash@{2}'。这是 shell 引号层面的补充,原文命令上方仍按原样保留。
查看 stash 中的差异
git stash show 可以查看一条记录的变更摘要:
$ git stash show
index.html | 1 +
style.css | 3 +++
2 files changed, 4 insertions(+)
加入 -p 或 --patch,则查看完整差异:
$ 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"/>
编者注:这是原文用来展示输出格式的差异片段,其中 CSS 的 text-decoration: blink 是过时演示内容;这里不把它当成现代网页实现建议。默认 show 的范围主要是已跟踪文件;要查看保存的未跟踪文件,还可按当前 Git 手册使用 git stash show --include-untracked -p。
只搁置部分修改
你还可以只搁置单个文件、一组文件,或文件中的部分修改。给 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
这个例子选择搁置样式文件的变更块,保留 HTML 中的变更。输入 ? 可以显示完整的交互命令列表。常用命令如下:
| 命令 | 作用 |
|---|---|
/ |
用正则表达式查找变更块。 |
? |
显示帮助。 |
n |
不搁置当前变更块。 |
q |
退出;此前已经选中的变更块仍会被搁置。 |
s |
把当前变更块拆分为更小的块。 |
y |
搁置当前变更块。 |
交互列表中没有明确的 abort 命令,但按 Ctrl+C(SIGINT)可以中止 stash 过程。
从 stash 创建分支
如果分支上的代码与 stash 中的修改已经明显分叉,pop 或 apply 时就可能发生冲突。此时可以用 git stash branch 创建一个新分支,再把 stash 应用到该分支:
$ 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.css
Changes not staged for commit:
modified: index.html
Dropped refs/stash@{1} (32b3aa1d185dfe6d57b3c3cc3b32cbf3e380cc6a)
该命令以创建 stash 时的提交为起点,创建并检出新分支,再把搁置的修改恢复到新分支上。成功应用指定的 stash 引用后,相应记录会被移除。
清理 stash
确定某条记录不再需要后,可以用 git stash drop 删除它:
$ git stash drop stash@{1}
Dropped stash@{1} (17e2697fd8251df6163117cb3d58c1f62a5e7cdb)
以下命令会删除所有 stash 记录:
$ git stash clear
编者注:drop 和 clear 会删除找回未提交工作所依赖的记录。先用 list/show 确认内容已经恢复或另有保留,再考虑清理;被清除对象之后可能被垃圾回收,无法保证恢复。删除前应确认内容已恢复或另有保留。
git stash 的内部工作方式
如果你只想学会使用 stash,读到这里已经足够。若想了解 Git 如何在底层实现它,还可以继续看提交对象的结构。
stash 实际上编码为仓库中的提交对象。特殊引用 refs/stash 指向最新的 stash;此前的记录则保存在该引用的 reflog 中。因此,stash@{n} 实际上是在引用 stash 引用的第 n 条 reflog 记录。因为 stash 本身是一个提交,所以也可以用 git log 检查它。
编者注:原文把特殊引用写为 .git/refs/stash。这里采用引用名 refs/stash,避免暗示它必然以该路径上的松散文件存在;引用存储方式可能不同。这只是存储表述的精确化,没有改变教程的模型。
根据保存的内容,一次 stash 操作会创建两个或三个新提交:
stash@{0}:新建的工作树提交,记录执行 stash 时工作副本中已跟踪文件的状态。- 它的第一个父提交:执行 stash 时 HEAD 指向的已有提交。
- 它的第二个父提交:新建提交,记录执行 stash 时 index 的状态。
- 它的第三个父提交:可选的新建提交,记录工作副本中的未跟踪文件。原文的条件是工作副本确有未跟踪文件,且调用时使用了
--include-untracked或--all。使用--all时,忽略文件也会进入这一额外快照。
从工作树和 index 到这些提交的过程,可以按下面的顺序理解:
- 执行前,工作树可能同时包含已跟踪文件的修改、未跟踪文件和已忽略文件;其中一部分修改也可能已经暂存进 index。
- 普通
git stash把已跟踪文件的工作树状态和 index 状态编码为两个新提交,并更新特殊引用refs/stash。 --include-untracked还会用额外提交保存未跟踪文件。--all把已忽略文件和未跟踪文件放到同一个额外提交中。
执行 git stash pop 时,Git 使用这些提交中的修改更新工作副本;根据选项和应用情况处理 index,并从 stash reflog 中移除弹出的记录。弹出的提交对象不会立刻物理删除,但会成为后续垃圾回收的候选对象。











暂无评论内容