章节 ▾ 第二版

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 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 `,它会为你创建一个新分支,使用你选择的分支名称,检出你暂存工作时的提交,在那里重新应用你的工作,然后如果应用成功则丢弃该暂存。

$ 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` 更改为 `-f` 实际执行之前,先用 `-n` 运行它进行双重检查。另一种谨慎操作的方法是使用 `-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` 选项以示强调。

scroll-to-top