章节 ▾ 第二版

7.3 Git 工具 - 储藏与清理

储藏与清理

通常,当你正在项目的某个部分上工作时,事情处于一团糟的状态,你希望暂时切换分支去做其他事情。问题是,你不想提交一半的工作,只是为了以后能回到这个点。解决这个问题的方法是 git stash 命令。

储藏会把你的工作目录的“脏”状态——即你修改过的已跟踪文件和已暂存的更改——保存在一个未完成更改的堆栈上,你可以在任何时候(甚至在不同的分支上)重新应用它们。

注意
迁移到 git stash push

截至 2017 年 10 月末,Git 邮件列表中有大量讨论,其中命令 git stash save 正在被弃用,转而使用现有的替代方案 git stash push。主要原因是 git stash push 引入了储藏选定 路径规范 的选项,这是 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 表示“强制”或“真的这样做”,如果 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 或“交互式”标志运行它。

这将以交互模式运行 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 选项来强调。