章节 ▾ 第二版

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 会给你合并冲突。

文件的更改被重新应用了,但你在暂存之前暂存的文件没有被重新暂存。要做到这一点,你必须运行 git stash apply 命令并加上 --index 选项,告诉命令尝试重新应用暂存的更改。如果你当时运行的是这个命令,你就会恢复到原来的位置。

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