git stash:如何临时保存修改并随后恢复
git stash 把未提交的修改收进单独的存储,并把工作目录恢复到最后一次提交的状态,方便你切换到其他任务。恢复修改用 git stash pop(应用并从存储中删除)或 git stash apply(应用并保留);Git 中没有 git unstash 命令。
为什么需要 git stash
典型场景:你的任务做到一半,文件已经修改,但还不适合提交;这时有人要求你立刻修复另一个分支中的 bug。直接切换分支不行,或者你担心会丢失修改。git stash 会把所有未提交的修改先“收进口袋”,并把工作目录恢复到最后一次提交的状态。
$ git stash
Saved working directory and index state WIP on feature/login: 3f2a1c4 Login form
$ git status
On branch feature/login
nothing to commit, working tree clean
现在可以切换分支、修复 bug,然后回来取出暂存的内容。每次保存都会成为 stash 栈中的一条记录。Git 内部把它存为一个特殊的提交,因此数据是可靠的,并且不依赖于某个分支。
如何保存修改:常用方式
添加有意义的描述
默认的描述“WIP on 分支名”过一周就没有任何意义了。建议写上说明:
git stash push -m "form validation, e-mail not finished"
不带参数的 git stash 是 git stash push 的简写。旧的写法 git stash save "说明" 仍然可用,但在新的操作中请使用 push -m。
连同新文件一起保存
默认情况下,stash 只收入已跟踪的文件,即已修改并加入索引的文件。从未执行过 git add 的新文件会留在工作目录中。这是一个常见的意外。
git stash -u # --include-untracked:同时包含未跟踪的文件
git stash -a # --all:同时包含 .gitignore 中忽略的文件
使用 -a 要小心:node_modules、构建产物和本地配置都会一起被收走。通常 -u 就足够了。
只保存部分修改
git stash push -m "config" src/config.js # 只保存指定的文件
git stash -p # 交互式选择代码块
git stash --staged # 只保存已在索引中的内容(Git 2.35+)
git stash --keep-index # 保存全部内容,但让索引留在工作目录中
--keep-index 在提交之前很有用:你把工作目录中只保留打算提交的部分,运行测试,确认这次提交本身是完整可用的。
如何恢复修改(git unstash)
Git 中并不存在 git unstash 命令——很多人搜索这个词,是想找回暂存的内容。实际上使用以下两个命令之一。
| 命令 | 作用 | stash 记录 |
|---|---|---|
git stash pop | 应用最近一次保存的修改 | 若无冲突应用成功,则删除该记录 |
git stash apply | 应用最近一次保存的修改 | 保留该记录 |
git stash pop stash@{2} | 应用指定的记录 | 删除该记录 |
git stash apply --index | 同时恢复索引和工作目录的划分 | 保留该记录 |
$ git stash pop
On branch feature/login
Changes not staged for commit:
modified: src/auth.js
modified: src/form.js
Dropped refs/stash@{0} (8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d)
注意:默认情况下,即使保存时某些内容已经在索引中,恢复时也会把它们作为普通修改还原。如果需要保留已暂存的状态,加上 --index。
pop 还是 apply
日常工作中 pop 比较方便:取出即忘。如果你想把同样的修改应用到多个分支,或者对能否顺利应用没有把握,可以选择 apply——记录会保留下来作为备份,之后再用 git stash drop 删除。
管理 stash 列表
$ git stash list
stash@{0}: On feature/login: form validation, e-mail not finished
stash@{1}: WIP on main: 9b7e0d2 Update dependencies
stash@{2}: On feature/api: cache experiment
stash 栈遵循“后进先出”的原则:stash@{0} 总是最新的记录,每次 push 或 drop 之后,编号都会发生变化。
| 命令 | 用途 |
|---|---|
git stash show | 最新记录的修改统计摘要 |
git stash show -p stash@{1} | 指定记录的完整 diff |
git stash drop stash@{1} | 删除某一条记录 |
git stash clear | 一次删除全部记录 |
git stash branch 名称 stash@{0} | 基于保存时的提交创建新分支,应用该记录并删除它 |
如果希望 git stash show 默认显示完整的 diff 并包含未跟踪的文件,可以设置 stash.showPatch 和 stash.showIncludeUntracked。
分步示例:任务中途出现紧急 bug
- 连同新文件一起保存当前工作:
git stash push -u -m "login: draft"。 - 切换到主分支并创建修复分支:
git switch main,git pull,git switch -c hotfix/null-email。 - 修复错误,提交,并发起评审。
- 回到原来的任务:
git switch feature/login。 - 取出修改:
git stash pop。用git status确认一切都在。
如今许多人在这种场景中使用 git worktree:为同一个仓库再开一个工作目录,这样根本无需暂存任何东西。但对于只需切换五分钟的情况,stash 仍然更简单。
应用 stash 时的冲突
如果在保存之后,分支中的同一批代码行也发生了变化,应用时就会产生冲突:
$ git stash pop
Auto-merging src/auth.js
CONFLICT (content): Merge conflict in src/auth.js
The stash entry is kept in case you need it again.
此时即使使用 pop,记录也不会被删除。像平常一样解决文件中的冲突,然后执行 git add 标记解决结果(如果不想让修改留在索引中,则使用 git restore --staged),最后用 git stash drop 删除已经无用的记录。
如果冲突比较复杂,可以换一种方式:git stash branch tmp-login。Git 会基于保存修改时所在的那个提交创建分支,在那里应用修改通常不会产生冲突;之后再通过普通的 merge 与主分支合并。
如何找回误删的 stash
如果误执行了 git stash drop 或 clear,并不意味着数据已经丢失。记录本身就是一个提交,在垃圾回收清除它之前,可以在“悬空”对象中找到:
git fsck --no-reflog | grep "dangling commit"
git show 8c1d2e3 # 确认是不是需要的 stash
git stash apply 8c1d2e3
如果刚刚通过 drop 或 pop 删除了记录,哈希值会直接打印在输出中,即 Dropped refs/stash@{0} (...) 这一行。把它传给 git stash apply 就可以了。
stash 的内部结构
了解其结构可以消除对丢失数据的恐惧。每条 stash 记录都是一个普通的提交,由引用 refs/stash 指向。它有两个父提交:你当时所在的 HEAD 提交,以及一个保存索引状态的单独提交。使用 -u 时还会增加第三个父提交,保存未跟踪的文件。stash@{N} 列表本质上就是 refs/stash 引用的 reflog。
由此得出一些实用的推论。任何接受提交作为参数的命令都可以用于 stash:git diff stash@{0},git show stash@{0}:src/auth.js(查看记录中某个文件的内容),或者 git checkout stash@{0} -- src/auth.js,只取出一个文件,而不应用整条记录。被删除的记录则会留在对象库中,直到被垃圾回收清除。
stash 与其他 Git 命令
一些命令可以自动暂存修改。git rebase --autostash 和 git pull --rebase --autostash 在开始前暂存未提交的修改,结束后再恢复。通过配置 rebase.autoStash true 和 merge.autoStash true 可以让这一行为始终生效。如果恢复时出现冲突,Git 会把记录保存在普通的 stash 栈中,并给出提示。
用 git switch 切换分支也不一定需要 stash:如果你的修改所涉及的文件在两个分支之间没有差异,Git 会直接把修改带过去。只有当 Git 提示“Your local changes would be overwritten by checkout”,或者你不想把修改带到另一个分支时,才需要 stash。
常见错误
- 新文件没有被保存。 它们未被跟踪,需要使用
-u。 - 把 stash 当作长期存储。 一个月后列表里会有十条没有名字的“WIP on main”,没人记得它们是什么。长期的工作应当提交到单独的分支,至少还能推送到服务器。
- 忘记冲突之后记录仍然保留。 之后又应用了一次。解决冲突后,一定要执行
drop。 - stash 不会与服务器同步。 它只存在于本地仓库中,
git push不会发送它。如果删除了项目文件夹,stash 也会一起消失。 - 应用了错误的记录。 在
pop之前,先用git stash list和git stash show -p确认。
总结
git stash 是一种快速释放工作目录、同时不丢失未提交修改的方法:用 git stash push -u -m "描述" 保存,用 git stash pop 恢复。需要保险时使用 apply;用 git stash list 和 show -p 避免混乱;不要把 stash 当成仓库——长期的工作应当使用提交和分支。在课程中,通过 stash 在任务之间切换的做法有实践讲解。
常见问题
能否在其他分支上应用 stash?
可以,stash 不绑定于某个分支:切换到目标分支后执行 git stash pop 或 apply。如果修改无法顺利套用,就会出现普通的冲突。
git stash 会保存索引中的修改吗?
会保存,但普通的 pop 或 apply 会把它们恢复为未暂存的修改。要同时恢复索引,请使用 git stash apply --index。
stash 最多可以保存多少条记录?
没有硬性上限,但超过 reflog 保留期的记录可能在垃圾回收时被删除。实际使用中,栈里保留几条最近的记录就够了。
如何只 stash 某一个文件?
在 push 时指定路径,并使用 -- 分隔:git stash push -m 描述 -- src/config.js。只有该文件会进入 stash,其余修改留在工作目录中。
stash 与临时提交有什么区别?
提交存在于分支中,可以推送到服务器,之后还能用 amend 或 rebase 修改。stash 只保存在本地的独立栈中,适合短时间的切换。