Git stash:暂时搁置修改,再回到原来的工作

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包含已跟踪修改,-u额外包含未跟踪文件,-a再包含忽略文件;stash由工作树、索引和可选未跟踪文件提交组成
原创示意图:保存范围与 stash 的提交结构。左侧各选项的范围逐层扩大。

管理多条 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 到这些提交的过程,可以按下面的顺序理解:

  1. 执行前,工作树可能同时包含已跟踪文件的修改、未跟踪文件和已忽略文件;其中一部分修改也可能已经暂存进 index。
  2. 普通 git stash 把已跟踪文件的工作树状态和 index 状态编码为两个新提交,并更新特殊引用 refs/stash。
  3. --include-untracked 还会用额外提交保存未跟踪文件。
  4. --all 把已忽略文件和未跟踪文件放到同一个额外提交中。

执行 git stash pop 时,Git 使用这些提交中的修改更新工作副本;根据选项和应用情况处理 index,并从 stash reflog 中移除弹出的记录。弹出的提交对象不会立刻物理删除,但会成为后续垃圾回收的候选对象。

来源与权利:Atlassian 原文,Copyright © 2026 Atlassian;原页未声明开放内容许可证,本文译稿依据另行授权刊载。图示为本文作者原创并单独授权使用,不属于 Atlassian 原文。版本与命令边界参考 Git 官方 git-stash 手册(2.56.0)。本文中的命令、差异和终端输出均为来源示例或说明性内容。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容