1. 简介

Git 的 stash 功能允许我们在不提交变更的前提下临时保存工作状态,这在切换分支时特别有用。虽然我们可以一次性暂存所有修改(包括已暂存和未暂存的文件),但 Git 也支持仅暂存部分文件或部分内容

本文将介绍几种在 Git 中选择性暂存变更的方法。我们将从创建一个示例仓库开始,演示如何使用命令行选择性地暂存特定文件、通过文件指定路径、暂存未追踪文件,以及使用交互式补丁方式暂存部分变更内容。

本文所有示例均在 Debian 12(Bookworm)与 GNU Bash 5.2.15 下测试通过,适用于大多数 POSIX 兼容环境。

2. 示例仓库

我们先创建一些修改,以便后续演示使用:

$ echo "$(date)" >> trackedfile
$ touch untrackedfile
$ rm deletedfile
$ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    deletedfile
        modified:   trackedfile

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        untrackedfile

no changes added to commit (use "git add" and/or "git commit -a")

✅ 假设我们在每个章节开始前都会恢复到这个状态。

3. 使用 push 指定文件暂存

Git 提供了 git stash push 命令用于创建暂存条目。默认情况下,git stash 等价于 git stash push

从 Git 2.x 开始,push 支持直接传入文件路径参数,仅暂存这些文件的修改:

$ git stash [push] [--] <FILE_PATH_1> <FILE_PATH_2> ... <FILE_PATH_N>

📌 注意:push-- 是可选的,但建议保留以避免语法歧义。

示例:仅暂存 deletedfile

$ git stash -- deletedfile
Saved working directory and index state WIP on master: 9d7f6b2 last commit message

查看当前暂存列表:

$ git stash list
stash@{0}: WIP on master: 9d7f6b2 last commit message

查看暂存内容:

$ git stash show 0
 deletedfile | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

deletedfile 被暂存,其他修改仍然保留在工作区:

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   trackedfile

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        untrackedfile

no changes added to commit (use "git add" and/or "git commit -a")

💡 小技巧:若要暂存多个文件,只需在命令后按空格列出即可。

4. 通过文件指定暂存路径

除了在命令行中列出文件路径,我们也可以通过一个文件来指定要暂存的文件列表,使用 --pathspec-from-file 参数:

$ git stash --pathspec-from-file=<LIST_FILE_PATH>

例如,创建一个 filelist 文件包含两个路径:

$ cat filelist
trackedfile
deletedfile
$ git stash --pathspec-from-file=filelist
Saved working directory and index state WIP on master: 9d7f6b2 last commit message

查看暂存内容:

$ git stash show
 deletedfile | 0
 trackedfile | 1 +
 2 files changed, 1 insertion(+)

📌 注意:如果文件名包含特殊字符,建议使用 --pathspec-file-nul 并以 NULL 分隔路径。

5. 暂存未追踪文件

默认情况下,即使指定路径,Git 也无法暂存未追踪文件。需要使用 --include-untracked(或 -u)标志:

$ git stash --include-untracked -- untrackedfile
Saved working directory and index state WIP on master: 9d7f6b2 last commit message

📌 该标志同样适用于 --pathspec-from-file

6. Git 补丁机制简介

Git 的许多命令都支持 --patch(或 -p)参数,它会将修改拆分为“补丁块(hunk)”,并提供交互式界面让我们选择哪些块要暂存:

$ git add --patch
diff --git a/deletedfile b/deletedfile
deleted file mode 100644
index e69de29..0000000
(1/1) Stage deletion [y,n,q,a,d,?]? y

diff --git a/trackedfile b/trackedfile
index e69de29..ee82e24 100644
--- a/trackedfile
+++ b/trackedfile
@@ -0,0 +1 @@
+Sun Feb 22 10:02:10 AM EST 2024
(1/1) Stage this hunk [y,n,q,a,d,e,?]? n

常用选项:

  • y:暂存当前块
  • n:跳过当前块
  • q:跳过当前块及后续所有块
  • a:暂存当前块及该文件后续所有块
  • d:跳过当前块及该文件后续所有块
  • s:将当前块拆分为更小的块
  • e:手动编辑当前块
  • ?:显示帮助

7. 使用补丁方式暂存修改

git stashpushsave 子命令也支持 --patch,用于交互式选择要暂存的修改块:

$ git stash push --patch
diff --git a/deletedfile b/deletedfile
deleted file mode 100644
index e69de29..0000000
(1/1) Stash deletion [y,n,q,a,d,?]? y

diff --git a/trackedfile b/trackedfile
index e69de29..ee82e24 100644
--- a/trackedfile
+++ b/trackedfile
@@ -0,0 +1 @@
+Sun Feb 22 10:02:10 AM EST 2024
(1/1) Stash this hunk [y,n,q,a,d,e,?]? n

📌 注意:该操作是原子性的,即如果中途按 Ctrl+C 取消,不会有任何修改被暂存。

⚠️ --patch 会隐式启用 --keep-index,即暂存已暂存的变更,但不会影响暂存区。如需清除暂存区,应使用 --no-keep-index

如需暂存未追踪文件,仍需加上 --include-untracked

8. 总结

方法 是否支持未追踪文件 是否支持交互式选择 是否支持路径文件
git stash -- <file>
--pathspec-from-file ✅(需 -u
--patch ✅(需 -u

Git 的 stash 功能非常强大,尤其是在我们需要保存部分工作状态时。通过本文介绍的几种方式,你可以更精细地控制哪些修改被暂存、哪些保留,从而提升工作效率。


原始标题:Stashing Selected Files and Changes in a Git Working Tree