Git
章节 ▾ 第二版

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")

你可以看到,当你保存了 stash 后,Git 会重新修改你回退过的文件。在这种情况下,你在尝试应用 stash 时,工作目录是干净的,并且你尝试将其应用到保存它的同一个分支。拥有一个干净的工作目录并在同一个分支上应用它并不是成功应用 stash 的必要条件。你可以在一个分支上保存 stash,稍后切换到另一个分支,然后尝试重新应用更改。你也可以在工作目录中修改和未提交的文件,当你应用 stash 时,如果任何内容不再能够干净地应用,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` 命令,并指定 stash 的名称来删除它。

$ 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` 命令来应用 stash,然后立即从堆栈中删除它。

创意 Stashing

还有一些 stash 选项可能也很有用。第一个很受欢迎的选项是 `git stash` 命令的 `--keep-index` 选项。这会告诉 Git 不仅将所有暂存的内容包含到创建的 stash 中,而且还会同时将其保留在索引中。

$ 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

你可能还想对 stash 做的另一件常见的事情是,除了暂存已跟踪的文件外,还暂存未跟踪的文件。默认情况下,`git stash` 只会暂存已修改和已暂存的 *已跟踪* 文件。如果你指定 `--include-untracked` 或 `-u`,Git 会将未跟踪的文件包含到创建的 stash 中。但是,将未跟踪的文件包含到 stash 中仍然不会包含明确 *忽略* 的文件;要另外包含被忽略的文件,请使用 `--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

从 Stash 创建分支

如果你暂存了一些工作,然后在那里停留一段时间,继续在暂存工作所在的分支上工作,你可能会在重新应用工作时遇到问题。如果 apply 尝试修改你已经修改过的文件,你将得到一个合并冲突,并且必须尝试解决它。如果你想要一种更简单的方法来再次测试暂存的更改,你可以运行 `git stash branch <new branchname>`,这会为你创建一个新分支,并使用你选择的名称,检出你暂存工作时的提交,在该提交上重新应用你的工作,然后如果应用成功,则删除 stash。

$ 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` 来删除所有内容,但将其保存在一个 stash 中。

假设你确实想删除无用文件或清理你的工作目录,你可以使用 `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` 选项来强调。

scroll-to-top