章节 ▾ 第二版

7.3 Git 工具 - 储藏与清理

储藏与清理

经常会有这样的情况,当你正在进行项目中某一部分的工作,里面的东西处于一种比较杂乱的状态,而你想要切换分支去处理另外一些事情。 问题在于,你不想因为只是为了回到之前的工作点而做一个半成品提交。 对于这种情况, git stash 命令会提供帮助。

储藏(Stashing)会处理工作目录的脏的状态——也就是,修改的追踪文件与暂存的改动——然后将未完成的修改保存到一个栈上,而你可以在任何时候重新应用这些改动(甚至在另外一个分支上)。

注意
迁移到 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 stash 或者 git 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 文件,以便你可以进行完全干净的构建,你可以将 -x 添加到 clean 命令。

$ 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 选项来强调。

scroll-to-top