Git Stash 用法详解:临时保存工作区修改
在软件开发的日常工作中,我们常常会遇到这样的情况:正在处理一个新功能或修复一个 Bug,突然间需要切换到另一个分支去处理一个紧急的 Bug,或者需要拉取远程仓库的最新代码。然而,当前的工作区可能存在大量尚未完成、无法提交的修改。如果直接切换分支,这些修改可能会导致冲突,甚至丢失;如果强行提交一个“半成品”的修改,又会污染提交历史,给团队协作带来麻烦。
这时,Git Stash 就如同一位救星,为我们提供了完美的解决方案。它允许我们临时保存当前工作区和暂存区的所有修改,将工作区恢复到干净的状态,以便我们进行其他操作。完成其他任务后,我们再随时恢复之前保存的修改,继续原来的工作。
本文将深入探讨 Git Stash 的各个方面,从基本概念到高级用法,再到实际应用场景和注意事项,助您成为 Git Stash 的使用高手。
一、 Git Stash 简介:为什么需要它?
Git Stash 的核心功能是临时存储未提交的本地修改。它将你的工作目录和暂存区中的所有更改(包括已跟踪文件的修改和新添加的文件到暂存区的更改)打包成一个临时的“存储单元”,并将其推入一个栈中。这个栈是与你的本地仓库关联的,但它不是 Git 提交历史的一部分。
Git Stash 解决的核心问题:
- 快速切换上下文: 当你需要立即切换到另一个分支(例如修复高优先级 Bug)时,但当前分支的工作尚未完成,无法提交。
- 拉取远程更新: 当你的本地分支有未提交的修改,而你想拉取远程仓库的最新代码时,这些修改可能会与远程更新冲突。Stash 允许你先保存本地修改,拉取更新,再恢复修改。
- 清理工作区: 当你在进行一些实验性改动,或者仅仅想暂时清空工作区以便查看某个历史版本时,Stash 可以帮助你快速实现。
- 避免不完整的提交: 避免为了切换分支而被迫提交一些不完整的、未经测试的代码。
Stash 不会做什么?
- 不会保存未跟踪的文件(Untracked Files): 默认情况下,Git Stash 不会存储那些从未被
git add过的文件。 - 不会保存被忽略的文件(Ignored Files): 同样,被
.gitignore规则忽略的文件也不会被 Stash。
二、 Git Stash 的核心命令详解
理解 Git Stash 的各个命令是熟练使用的基础。我们将逐一介绍:
2.1 git stash 或 git stash save [message]:保存当前修改
这是最常用、最基本的命令,用于将当前工作区和暂存区的修改保存起来。
git stash: 这是最简洁的用法。Git 会自动生成一个默认的描述信息(例如 “WIP on master: 0a1b2c3 Initial commit”),其中 “WIP” 表示 “Work In Progress”,后面是当前分支名和最新一次提交的哈希值与描述。git stash save "Your custom message": 推荐使用此形式。你可以为你的 stash 添加一个有意义的描述信息,这在你有多个 stash 并且需要区分它们时非常有用。消息会出现在git stash list的输出中。
保存的内容:
- 已修改但未暂存的文件(Modified but not staged)。
- 已暂存的文件(Staged files)。
示例:
“`bash
假设你在 feature 分支,对 file1.txt 进行了修改,并对 file2.txt 进行了修改并已暂存。
git status
On branch feature
Changes to be committed:
(use “git restore –staged …” to unstage)
modified: file2.txt
Changes not staged for commit:
(use “git add …” to update what will be committed)
(use “git restore …” to discard changes in working directory)
modified: file1.txt
保存这些修改,并添加描述
git stash save “WIP: Implemented login functionality”
再次查看状态,工作区和暂存区都已干净
git status
On branch feature
nothing to commit, working tree clean
“`
此时,你的修改已经被成功保存到 stash 栈中,并且工作区和暂存区都变得干净,你可以放心地切换分支或执行其他操作。
2.2 git stash list:查看所有保存的修改
此命令用于列出所有已保存的 stash。
输出格式:
stash@{0}: On master: Initial commit
stash@{1}: On feature: Added new user registration page
stash@{2}: WIP on feature: 0a1b2c3 Implemented login functionality
stash@{n}:n是一个整数,表示 stash 在栈中的位置。stash@{0}总是代表最新保存的 stash。On <branch_name>:表示该 stash 是在哪个分支上创建的。message:你保存时提供的描述信息,或者 Git 自动生成的默认信息。
示例:
“`bash
git stash list
stash@{0}: WIP on feature: 0a1b2c3 Implemented login functionality
stash@{1}: On master: Urgent bug fix
``stash@{0}
这个输出告诉我们,我们目前有两个 stash,最新的一个 () 是在feature` 分支上创建的,描述是 “WIP: Implemented login functionality”。
2.3 git stash apply [stash@{n}]:应用保存的修改(不删除 stash)
apply 命令用于将指定的 stash 重新应用到当前工作区。
git stash apply: 默认应用最新(即stash@{0})的 stash。git stash apply stash@{n}: 应用指定的 stash。
特点:
- 应用 stash 后,该 stash 仍然保留在 stash 列表中。这意味着你可以多次应用同一个 stash,或者在不同的分支上应用它(尽管这通常不推荐,因为它可能导致冲突)。
- 不会自动恢复暂存区状态:
apply命令会将 stash 中的所有修改都应用到工作区,但不会自动将它们重新添加到暂存区。你需要手动git add来暂存它们。
冲突处理:
如果应用的 stash 与当前工作区存在冲突,Git 会在冲突文件中标记出冲突区域,你需要手动解决冲突,然后 git add 解决后的文件,最后 git commit。
示例:
“`bash
假设我们之前保存了一个 stash
git stash list
stash@{0}: WIP on feature: 0a1b2c3 Implemented login functionality
切换到 feature 分支
git checkout feature
应用最新保存的 stash
git stash apply
此时 git status 会显示之前保存的修改,但它们都处于未暂存状态
git status
On branch feature
Changes not staged for commit:
(use “git add …” to update what will be committed)
(use “git restore …” to discard changes in working directory)
modified: file1.txt
modified: file2.txt (Note: this was staged before, but apply makes it unstaged)
再次查看 stash 列表,stash 仍然存在
git stash list
stash@{0}: WIP on feature: 0a1b2c3 Implemented login functionality
“`
2.4 git stash pop [stash@{n}]:应用保存的修改并删除 stash
pop 命令的功能与 apply 类似,但它在成功应用 stash 后,会自动从 stash 列表中删除该 stash。
git stash pop: 默认弹出并应用最新(即stash@{0})的 stash。git stash pop stash@{n}: 弹出并应用指定的 stash。
特点:
- 这是最常用的恢复 stash 的方式,因为它在使用后会保持 stash 列表的整洁。
- 与
apply相同,它会将 stash 中的所有修改都应用到工作区,但不会自动将它们重新添加到暂存区。
冲突处理:
如果 pop 过程中发生冲突,Git 会提示你解决冲突。解决冲突后,stash 不会被自动删除。你需要手动解决冲突,git add,git commit,然后根据需要手动 git stash drop 掉那个冲突的 stash。
示例:
“`bash
假设我们有一个 stash
git stash list
stash@{0}: WIP on feature: 0a1b2c3 Implemented login functionality
弹出并应用最新 stash
git stash pop
此时 git status 会显示之前保存的修改
git status
On branch feature
Changes not staged for commit:
(use “git add …” to update what will be committed)
(use “git restore …” to discard changes in working directory)
modified: file1.txt
modified: file2.txt
再次查看 stash 列表,stash 已被删除
git stash list
(没有输出,表示 stash 列表为空)
“`
2.5 git stash drop [stash@{n}]:删除一个保存的修改
此命令用于从 stash 列表中删除指定的 stash。
git stash drop: 默认删除最新(即stash@{0})的 stash。git stash drop stash@{n}: 删除指定的 stash。
注意: 删除操作是不可逆的。一旦删除,该 stash 中的修改就找不回来了。
示例:
“`bash
git stash list
stash@{0}: WIP on feature: 0a1b2c3 Implemented login functionality
stash@{1}: On master: Urgent bug fix
删除最新的 stash
git stash drop
Dropped stash@{0} (WIP on feature: 0a1b2c3 Implemented login functionality).
git stash list
stash@{0}: On master: Urgent bug fix
删除 stash@{0} (现在它代表了原来的 stash@{1})
git stash drop stash@{0}
Dropped stash@{0} (On master: Urgent bug fix).
git stash list
(没有输出,表示 stash 列表为空)
“`
2.6 git stash clear:删除所有保存的修改
此命令用于清空整个 stash 列表,删除所有已保存的 stash。
极其危险的操作! 请务必在确认所有 stash 都已不再需要时才使用此命令。
示例:
“`bash
git stash list
stash@{0}: WIP on feature: 0a1b2c3 Implemented login functionality
stash@{1}: On master: Urgent bug fix
git stash clear
(没有任何输出,但所有 stash 都被删除了)
git stash list
(没有输出,表示 stash 列表为空)
“`
三、 Git Stash 的高级用法
除了上述核心命令,Git Stash 还提供了一些高级选项,使其功能更加强大和灵活。
3.1 git stash show [stash@{n}] [-p]:查看 stash 的内容
这个命令允许你查看一个 stash 到底包含了哪些修改,而无需将其应用到工作区。
git stash show: 默认显示最新 stash (stash@{0}) 的简要差异统计信息(哪些文件被修改,以及增删行数)。git stash show stash@{n}: 显示指定 stash 的简要差异。git stash show -p或git stash show --patch: 显示最新 stash (stash@{0}) 的完整差异(像git diff一样显示具体修改内容)。git stash show -p stash@{n}: 显示指定 stash 的完整差异。
示例:
“`bash
查看最新 stash 的简要统计
git stash show
file1.txt | 2 +-
file2.txt | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
查看最新 stash 的完整差异
git stash show -p
diff –git a/file1.txt b/file1.txt
index a1b2c3d..e4f5g6h 100644
— a/file1.txt
+++ b/file1.txt
@@ -1,3 +1,4 @@
Line 1
-Line 2 old
+Line 2 new content
Line 3
+New line 4
查看 stash@{1} 的完整差异
git stash show -p stash@{1}
“`
3.2 git stash -u 或 git stash --include-untracked:保存未跟踪文件
默认情况下,git stash 不会保存那些从未被 git add 过的“未跟踪文件”。如果你想将它们也一并保存,可以使用 -u 或 --include-untracked 选项。
示例:
“`bash
假设有一个 untracked_file.txt 和 file1.txt 的修改
git status
On branch feature
Changes not staged for commit:
(use “git add …” to update what will be committed)
(use “git restore …” to discard changes in working directory)
modified: file1.txt
Untracked files:
(use “git add …” to include in what will be committed)
untracked_file.txt
git stash -u save “Including untracked file”
git status
On branch feature
nothing to commit, working tree clean
此时 untracked_file.txt 也会被 stash
``apply
当你或pop这个 stash 时,untracked_file.txt` 将会重新出现在你的工作区。
3.3 git stash -a 或 git stash --all:保存所有文件(包括被忽略文件)
这个选项比 -u 更进一步,它会保存所有文件,包括那些被 .gitignore 规则忽略的文件。这在你需要彻底清空工作区,甚至包括那些通常被忽略的编译产物或日志文件时非常有用。
注意: 谨慎使用此选项,因为它可能会保存一些你本不希望被 Git 触及的文件。
示例:
“`bash
假设有 .log 文件被 .gitignore 忽略,以及未跟踪文件和修改过的文件
git stash -a save “Stashing everything including ignored”
此时工作区会变得非常干净
“`
3.4 git stash branch <new_branch_name> [stash@{n}]:从 stash 创建新分支
如果你发现某个 stash 实际上代表了一个可以独立开发的新功能或 Bug 修复,你可以直接从这个 stash 创建一个新的分支。这个命令会创建一个新的分支,将 stash 中的修改应用到该分支,然后删除该 stash。
工作流程:
- 创建一个新分支,并切换到该分支。
- 将指定的 stash(或最新的 stash)应用到这个新分支。
- 如果应用成功,则删除该 stash。
示例:
“`bash
git stash list
stash@{0}: WIP on feature: 0a1b2c3 Implemented login functionality
stash@{1}: On master: Urgent bug fix
从最新的 stash 创建一个名为 “feature-login” 的新分支
git stash branch feature-login
此时,你会在 “feature-login” 分支,并且之前 stash@{0} 的修改已经应用到了这个分支,
并且该 stash 已经被删除。
git status
On branch feature-login
Changes not staged for commit:
(use “git add …” to update what will be committed)
(use “git restore …” to discard changes in working directory)
modified: file1.txt
modified: file2.txt
git stash list
stash@{0}: On master: Urgent bug fix (原来的 stash@{1} 现在变成了 stash@{0})
“`
3.5 局部 Stash (git stash push -p / --patch 和 git stash --keep-index)
虽然 git stash 默认会保存所有修改,但有时你只想保存工作区的一部分修改,而保留另一部分或已暂存的修改。这可以通过结合 Git 的其他功能来实现。
-
交互式局部暂存 (
git add -p) +git stash --keep-index:- 使用
git add -p交互式地将你想要保留在工作区或提交的修改添加到暂存区。 - 然后使用
git stash --keep-index。这个命令会保存所有未暂存的修改,而保留已暂存的修改不动。
- 使用
-
交互式局部 Stash (
git stash push -p或git stash --patch):
这个命令允许你像git add -p一样,交互式地选择哪些修改要被 stash 起来,哪些要保留在工作区。示例:
“`bash
假设 file1.txt 和 file2.txt 都有修改
git status
…
modified: file1.txt
modified: file2.txt
交互式选择要 stash 的修改
git stash push –patch
会逐个提示你 file1.txt 和 file2.txt 中的每个 hunk (修改块) 是否要 stash
输入 ‘y’ 或 ‘n’ 来决定
Stashed changes to file1.txt
Stashed changes to file2.txt
Saved working directory and index state WIP on master: …
“`
这个方法非常灵活,可以精确控制哪些修改被 stash。
四、 Git Stash 的实际应用场景
理解了命令,我们来看看 Git Stash 在实际开发中的常见应用场景。
4.1 情景一:紧急 Bug 修复 (Hotfix)
这是 git stash 最经典的用例。
- 问题: 你正在
feature-X分支上开发新功能,代码改动很多,尚未达到可提交的状态。突然,线上出现一个紧急 Bug,需要立即在master或develop分支上进行修复。 - 解决方案:
- 在
feature-X分支上,运行git stash save "WIP for feature X",临时保存你的工作。 - 切换到
master或develop分支:git checkout master。 - 创建并切换到 Bug 修复分支:
git checkout -b hotfix/critical-bug。 - 修复 Bug,提交代码,并推送到远程仓库。
- 切换回
feature-X分支:git checkout feature-X。 - 恢复之前保存的工作:
git stash pop。 - 继续开发
feature-X。
- 在
4.2 情景二:切换分支
当你需要查看或处理其他分支,但当前分支有未提交的修改时。
- 问题: 你在
dev分支工作,需要切换到test分支进行一些测试,但dev分支上的代码还没写完。 - 解决方案:
- 在
dev分支上,git stash保存当前修改。 - 切换到
test分支:git checkout test。 - 完成你在
test分支上的操作。 - 切换回
dev分支:git checkout dev。 - 恢复之前保存的修改:
git stash pop。
- 在
4.3 情景三:拉取远程更新
当你的本地分支有修改,但需要先拉取远程仓库的最新代码时。
- 问题: 你在
master分支有本地修改,尝试git pull时被 Git 阻止,因为合并远程代码会与本地修改冲突。 - 解决方案:
git stash保存本地修改。git pull拉取远程最新代码。git stash pop恢复本地修改。- 如果出现冲突,解决冲突并提交。
4.4 情景四:清理工作区进行试验
当你只想临时清空工作区,进行一些实验性改动,或者仅仅想看看某个历史提交的状态。
- 问题: 你想在一个干净的工作区下,基于某个旧的提交进行一些快速测试,或者想丢弃当前所有的本地修改,重新开始。
- 解决方案:
git stash保存当前工作。- 现在工作区是干净的,你可以
git checkout <commit_hash>查看历史状态,或者开始新的实验。 - 实验结束后,如果你决定放弃之前的 stash,可以直接
git stash drop;如果你想恢复,就git stash pop。
4.5 情景五:处理代码审查或合并冲突
当你在一个复杂的合并操作中遇到冲突,或者在代码审查期间需要临时修改其他部分的代码。
- 问题: 你正在进行一个特性分支到主分支的合并,遇到了大量的冲突。解决冲突需要时间,但你突然发现需要修改一个独立于合并冲突的 Bug。
- 解决方案:
- 在解决冲突之前(或在解决了一部分冲突后),如果你需要暂停解决冲突并处理其他事情,可以先
git stash push "Merge conflict WIP"。 - 此时,冲突状态会被保存,工作区会回到合并前的状态(或你在 stash 时已经解决了一部分的冲突会被保存)。
- 你可以切换分支去处理那个 Bug。
- 处理完 Bug 后,切换回原来的分支。
git stash pop恢复之前的合并冲突状态,继续解决冲突。
- 在解决冲突之前(或在解决了一部分冲突后),如果你需要暂停解决冲突并处理其他事情,可以先
五、 Git Stash 的注意事项与最佳实践
Git Stash 虽好用,但仍有一些需要注意的地方和最佳实践,以避免潜在的问题。
5.1 Stash 不是提交,它是本地的、临时的
- 不是提交: Stash 不是 Git 提交历史的一部分。这意味着它不会出现在
git log中,也不会被git push到远程仓库。 - 本地且临时: Stash 只存在于你本地的仓库中。如果你克隆了一个新的仓库副本,或者在另一台机器上工作,你是看不到这些 stash 的。因此,不要将 stash 作为长期保存重要工作的手段。重要的工作应该通过提交到 Git 仓库或创建分支来保存。
5.2 及时清理 Stash
- 避免堆积: 随着时间的推移,stash 列表可能会变得很长,让你难以辨别每个 stash 的具体内容。
- 使用描述信息: 在使用
git stash save "message"时,务必添加有意义的描述信息,帮助你快速识别。 - 完成后删除: 当你不再需要某个 stash 时,应及时使用
git stash drop或git stash pop删除它,保持列表的整洁。
5.3 理解 apply 与 pop 的区别
apply: 恢复但不删除 stash。当你可能需要多次应用同一个 stash,或者不确定是否彻底用完它时,可以选择apply。pop: 恢复并删除 stash。这是更常用的方式,因为它在使用后会保持 stash 列表的整洁。通常,当你确定这个 stash 只用一次且用完即弃时,使用pop。
5.4 冲突处理
- 准备冲突: 当你
apply或pop一个 stash 时,如果当前工作区与 stash 存在差异,可能会发生冲突。 - 解决冲突: Git 会在冲突文件中标记出冲突区域,你需要手动编辑文件解决冲突。
- 后续操作: 解决冲突后,需要
git add <resolved_file>,然后git commit。如果使用的是pop并且发生了冲突,stash 不会自动删除,你需要手动git stash drop它。
5.5 谨慎使用 clear 命令
git stash clear会无情地删除所有 stash,并且这个操作是不可逆的。- 请只在你百分之百确定所有 stash 都不再需要时才使用此命令。
5.6 处理未跟踪文件和被忽略文件
- 默认
git stash不会处理未跟踪文件和被忽略文件。 - 如果需要将它们也包含在 stash 中,请使用
git stash -u(include untracked) 或git stash -a(all files)。 - 考虑这些文件的性质:对于编译产物、日志文件等,通常不应纳入版本控制,也不应随意 stash。
5.7 不是替代分支的方案
git stash是一个方便的工具,用于临时保存未提交的修改,以便在同一分支或不同分支间切换上下文。- 但它绝不是替代 Git 分支的方案。如果你有长期、独立的功能开发或 Bug 修复任务,请始终使用
git branch来创建新的分支。分支是 Git 管理不同开发线的最基本和最强大的机制。
六、 总结
Git Stash 是 Git 提供的一个极其便利且强大的工具,它使得开发者在处理日常工作流中的各种中断和切换时,能够保持工作区的整洁和效率。通过临时保存工作区和暂存区的修改,你可以在不提交不完整代码的前提下,自由地切换分支、拉取更新、修复紧急 Bug 或进行试验。
熟练掌握 git stash 的基本命令(save/push、list、apply、pop、drop、clear)和高级用法(show、-u、-a、branch、--patch)是提升 Git 工作效率的关键。同时,遵循最佳实践,如及时清理 stash、添加有意义的描述信息、理解 apply 和 pop 的差异,以及正确处理冲突,将帮助你更好地驾驭这一工具。
记住,git stash 是你的临时避风港,但重要的、长期性的工作始终应该通过 git commit 和 git branch 来妥善管理。希望通过本文的详细介绍,您能对 Git Stash 有一个全面而深入的理解,并在日常开发中运用自如!