章节 ▾ 第二版

7.3 Git 工具 - 储藏与清理

储藏与清理

通常情况下,当你正在项目的一部分上工作时,情况会变得杂乱,而你想暂时切换分支去做别的事情。问题在于,你不想仅仅为了以后能回到当前点而提交半成品工作。解决这个问题的办法是使用 git stash 命令。

储藏(Stashing)会获取你工作目录的脏状态——即你修改过的已跟踪文件和暂存的变更——并将其保存在一个未完成变更的堆栈中,你可以随时重新应用这些变更(甚至可以在不同的分支上)。

注意
迁移到 git stash push

从 2017 年 10 月底开始,Git 邮件列表进行了广泛讨论,建议弃用 git stash save 命令,转而使用现有的替代方案 git stash push。主要原因是 git stash push 引入了储藏选定 路径规范(pathspecs) 的选项,而 git stash save 不支持这一点。

git stash save 不会很快消失,所以不必担心它会突然失效。但你可能希望开始迁移到 push 替代方案,以利用新的功能。

储藏你的工作

为了演示储藏,你将进入项目并开始处理几个文件,可能还会暂存其中一个变更。如果你运行 git status,可以看到你的脏状态

$ git status
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   lib/simplegit.rb

现在你想切换分支,但还不想提交你正在做的工作,所以你要储藏这些变更。要将一个新的储藏推送到你的堆栈,运行 git stashgit stash push

$ git stash
Saved working directory and index state \
  "WIP on master: 049d078 Create index file"
HEAD is now at 049d078 Create index file
(To restore them type "git stash apply")

现在你可以看到你的工作目录是干净的

$ git status
# On branch master
nothing to commit, working directory clean

此时,你可以切换分支并在其他地方工作;你的变更已存储在堆栈中。要查看你已存储的储藏,可以使用 git stash list

$ git stash list
stash@{0}: WIP on master: 049d078 Create index file
stash@{1}: WIP on master: c264051 Revert "Add file_size"
stash@{2}: WIP on master: 21d80a5 Add number to log

在这种情况下,之前保存了两个储藏,所以你可以访问三种不同的已储藏工作。你可以通过运行原始储藏命令帮助输出中显示的命令来重新应用刚刚储藏的工作:git stash apply。如果你想应用较早的储藏之一,可以通过名称指定它,如下所示:git stash apply stash@{2}。如果你不指定储藏,Git 会默认假定为最近的储藏并尝试应用它

$ git stash apply
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   index.html
	modified:   lib/simplegit.rb

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

你可以看到 Git 重新修改了你在保存储藏时还原的文件。在这种情况下,当你尝试应用储藏时工作目录是干净的,并且你是在保存它的同一个分支上应用它的。拥有干净的工作目录并在同一分支上应用并非成功应用储藏的必要条件。你可以将储藏保存在一个分支上,稍后切换到另一个分支,并尝试重新应用这些变更。在应用储藏时,你的工作目录中也可以有已修改且未提交的文件——如果任何内容无法顺利应用,Git 会提示合并冲突。

文件变更已重新应用,但之前暂存的文件并未重新暂存。为此,你必须使用 --index 选项运行 git stash apply 命令,以告知该命令尝试重新应用暂存的变更。如果你那样做了,就会回到最初的位置

$ git stash apply --index
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   lib/simplegit.rb

apply 选项仅尝试应用已储藏的工作——它仍会保留在你的堆栈中。要将其删除,可以运行 git stash drop 并加上要删除的储藏名称

$ git stash list
stash@{0}: WIP on master: 049d078 Create index file
stash@{1}: WIP on master: c264051 Revert "Add file_size"
stash@{2}: WIP on master: 21d80a5 Add number to log
$ git stash drop stash@{0}
Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)

你也可以运行 git stash pop 来应用储藏,然后立即将其从堆栈中删除。

创造性储藏

还有一些其他可能很有帮助的储藏变体。第一个非常流行的选项是 git stash 命令的 --keep-index 选项。这告诉 Git 不仅要在创建的储藏中包含所有已暂存的内容,同时还要将它们留在索引中。

$ git status -s
M  index.html
 M lib/simplegit.rb

$ git stash --keep-index
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file

$ git status -s
M  index.html

另一个常见的需求是同时储藏未跟踪的文件以及已跟踪的文件。默认情况下,git stash 只会储藏已修改和已暂存的 已跟踪 文件。如果你指定 --include-untracked-u,Git 会将未跟踪的文件包含在创建的储藏中。但是,包含未跟踪文件的储藏仍不会包含明确 忽略 的文件;要额外包含忽略的文件,请使用 --all(或仅 -a)。

$ git status -s
M  index.html
 M lib/simplegit.rb
?? new-file.txt

$ git stash -u
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file

$ git status -s
$

最后,如果你指定 --patch 标志,Git 将不会储藏所有已修改的内容,而是会以交互方式提示你哪些变更要储藏,哪些要保留在工作目录中。

$ git stash --patch
diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 66d332e..8bb5674 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -16,6 +16,10 @@ class SimpleGit
         return `#{git_cmd} 2>&1`.chomp
       end
     end
+
+    def show(treeish = 'master')
+      command("git show #{treeish}")
+    end

 end
 test
Stash this hunk [y,n,q,a,d,/,e,?]? y

Saved working directory and index state WIP on master: 1b65b17 added the index file

从储藏创建分支

如果你储藏了一些工作,将其搁置了一段时间,并继续在储藏该工作时的分支上进行开发,重新应用工作时可能会遇到问题。如果应用操作尝试修改你后来修改过的文件,就会出现合并冲突,你将不得不解决它。如果你想要一种更简便的方法来再次测试已储藏的变更,可以运行 git stash branch <new branchname>,这会为你创建一个具有所选分支名的新分支,检出你储藏工作时所在的提交,在那里重新应用你的工作,如果应用成功,则删除储藏

$ git stash branch testchanges
M	index.html
M	lib/simplegit.rb
Switched to a new branch 'testchanges'
On branch testchanges
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   index.html

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   lib/simplegit.rb

Dropped refs/stash@{0} (29d385a81d163dfd45a452a2ce816487a6b8b014)

这是一个轻松恢复已储藏工作并在新分支上继续开发的快捷方式。

清理工作目录

最后,你可能不想储藏工作目录中的某些工作或文件,而只是想摆脱它们;这就是 git clean 命令的用途。

清理工作目录的一些常见原因可能是移除由合并或外部工具生成的冗余文件,或者为了运行干净的构建而移除构建产物。

使用此命令时需要非常小心,因为它旨在从工作目录中移除未跟踪的文件。如果你改变主意,通常无法找回这些文件的内容。一个更安全的选项是运行 git stash --all 来移除所有内容但将其保存在储藏中。

假设你确实想移除冗余文件或清理工作目录,可以使用 git clean 来实现。要移除工作目录中所有未跟踪的文件,可以运行 git clean -f -d,这会移除所有文件以及因此变空的任何子目录。-f 表示“强制(force)”或“真正执行此操作”,如果 Git 配置变量 clean.requireForce 没有明确设置为 false,则必须使用此选项。

如果你想查看它会执行什么操作,可以运行带有 --dry-run(或 -n)选项的命令,这表示“进行预演并告诉我你 删除什么”。

$ git clean -d -n
Would remove test.o
Would remove tmp/

默认情况下,git clean 命令只会移除未被忽略的未跟踪文件。任何匹配 .gitignore 或其他忽略文件中模式的文件都不会被移除。如果你也想移除这些文件,例如移除构建生成的所有 .o 文件以进行彻底的干净构建,可以在 clean 命令中添加 -x

$ git status -s
 M lib/simplegit.rb
?? build.TMP
?? tmp/

$ git clean -n -d
Would remove build.TMP
Would remove tmp/

$ git clean -n -d -x
Would remove build.TMP
Would remove test.o
Would remove tmp/

如果你不知道 git clean 命令会做什么,请始终先用 -n 运行它,在将 -n 更改为 -f 并真正执行之前进行双重检查。另一种保持谨慎的方法是使用 -i 或“交互式(interactive)”标志运行它。

这将在交互模式下运行 clean 命令。

$ git clean -x -i
Would remove the following items:
  build.TMP  test.o
*** Commands ***
    1: clean                2: filter by pattern    3: select by numbers    4: ask each             5: quit
    6: help
What now>

这样你可以逐个检查每个文件,或者以交互方式指定要删除的模式。

注意

有一种特殊情况可能需要你非常强硬地要求 Git 清理工作目录。如果你碰巧处于一个工作目录下,且在该目录下复制或克隆了其他 Git 仓库(可能是作为子模块),即使是 git clean -fd 也会拒绝删除这些目录。在这种情况下,你需要添加第二个 -f 选项以增强强制力。