7.3 Git 工具 - 贮藏与清理

贮藏与清理

有时,当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状态, 而这时你想要切换到另一个分支做一点别的事情。 问题是,你不想仅仅因为过会儿回到这一点而为做了一半的工作创建一次提交。 针对这个问题的答案是 git stash 命令。

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

Note 迁移到 git stash push 截至 2017 年 10 月下旬,Git 邮件列表上进行了广泛讨论,该讨论中弃用了 git stash save 命令, 代之以现有 git stash push 命令。主因是 git stash push 引入了贮藏选定的 路径规范 的选项, 而有些东西 git stash save 不支持。 git stash save 不会很快就消失,所以不用担心它突然不见。 不过你可能想要迁移到 push 来获取新功能。

贮藏工作

为了演示贮藏,你需要进入项目并改动几个文件,然后可以暂存其中的一个改动。 如果运行 git status,可以看到有改动的状态:

  1. $ git status
  2. Changes to be committed:
  3. (use "git reset HEAD <file>..." to unstage)
  4. modified: index.html
  5. Changes not staged for commit:
  6. (use "git add <file>..." to update what will be committed)
  7. (use "git checkout -- <file>..." to discard changes in working directory)
  8. modified: lib/simplegit.rb

现在想要切换分支,但是还不想要提交之前的工作;所以贮藏修改。 将新的贮藏推送到栈上,运行 git stashgit stash push

  1. $ git stash
  2. Saved working directory and index state \
  3. "WIP on master: 049d078 added the index file"
  4. HEAD is now at 049d078 added the index file
  5. (To restore them type "git stash apply")

可以看到工作目录是干净的了:

  1. $ git status
  2. # On branch master
  3. nothing to commit, working directory clean

此时,你可以切换分支并在其他地方工作;你的修改被存储在栈上。 要查看贮藏的东西,可以使用 git stash list

  1. $ git stash list
  2. stash@{0}: WIP on master: 049d078 added the index file
  3. stash@{1}: WIP on master: c264051 Revert "added file_size"
  4. stash@{2}: WIP on master: 21d80a5 added number to log

在本例中,有两个之前的贮藏,所以你接触到了三个不同的贮藏工作。 可以通过原来 stash 命令的帮助提示中的命令将你刚刚贮藏的工作重新应用:git stash apply。 如果想要应用其中一个更旧的贮藏,可以通过名字指定它,像这样:git stash apply stash@{2}。 如果不指定一个贮藏,Git 认为指定的是最近的贮藏:

  1. $ git stash apply
  2. On branch master
  3. Changes not staged for commit:
  4. (use "git add <file>..." to update what will be committed)
  5. (use "git checkout -- <file>..." to discard changes in working directory)
  6. modified: index.html
  7. modified: lib/simplegit.rb
  8. no changes added to commit (use "git add" and/or "git commit -a")

可以看到 Git 重新修改了当你保存贮藏时撤消的文件。 在本例中,当尝试应用贮藏时有一个干净的工作目录,并且尝试将它应用在保存它时所在的分支。 并不是必须要有一个干净的工作目录,或者要应用到同一分支才能成功应用贮藏。 可以在一个分支上保存一个贮藏,切换到另一个分支,然后尝试重新应用这些修改。 当应用贮藏时工作目录中也可以有修改与未提交的文件——如果有任何东西不能干净地应用,Git 会产生合并冲突。

文件的改动被重新应用了,但是之前暂存的文件却没有重新暂存。 想要那样的话,必须使用 --index 选项来运行 git stash apply 命令,来尝试重新应用暂存的修改。 如果已经那样做了,那么你将回到原来的位置:

  1. $ git stash apply --index
  2. On branch master
  3. Changes to be committed:
  4. (use "git reset HEAD <file>..." to unstage)
  5. modified: index.html
  6. Changes not staged for commit:
  7. (use "git add <file>..." to update what will be committed)
  8. (use "git checkout -- <file>..." to discard changes in working directory)
  9. modified: lib/simplegit.rb

应用选项只会尝试应用贮藏的工作——在堆栈上还有它。 可以运行 git stash drop 加上将要移除的贮藏的名字来移除它:

  1. $ git stash list
  2. stash@{0}: WIP on master: 049d078 added the index file
  3. stash@{1}: WIP on master: c264051 Revert "added file_size"
  4. stash@{2}: WIP on master: 21d80a5 added number to log
  5. $ git stash drop stash@{0}
  6. Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)

也可以运行 git stash pop 来应用贮藏然后立即从栈上扔掉它。

贮藏的创意性使用

有几个贮藏的变种可能也很有用。 第一个非常流行的选项是 git stash 命令的 --keep-index 选项。 它告诉 Git 不仅要贮藏所有已暂存的内容,同时还要将它们保留在索引中。

  1. $ git status -s
  2. M index.html
  3. M lib/simplegit.rb
  4. $ git stash --keep-index
  5. Saved working directory and index state WIP on master: 1b65b17 added the index file
  6. HEAD is now at 1b65b17 added the index file
  7. $ git status -s
  8. M index.html

另一个经常使用贮藏来做的事情是像贮藏跟踪文件一样贮藏未跟踪文件。 默认情况下,git stash 只会贮藏已修改和暂存的 已跟踪 文件。 如果指定 --include-untracked-u 选项,Git 也会贮藏任何未跟踪文件。 然而,在贮藏中包含未跟踪的文件仍然不会包含明确 忽略 的文件。 要额外包含忽略的文件,请使用 --all-a 选项。

  1. $ git status -s
  2. M index.html
  3. M lib/simplegit.rb
  4. ?? new-file.txt
  5. $ git stash -u
  6. Saved working directory and index state WIP on master: 1b65b17 added the index file
  7. HEAD is now at 1b65b17 added the index file
  8. $ git status -s
  9. $

最终,如果指定了 --patch 标记,Git 不会贮藏所有修改过的任何东西, 但是会交互式地提示哪些改动想要贮藏、哪些改动需要保存在工作目录中。

  1. $ git stash --patch
  2. diff --git a/lib/simplegit.rb b/lib/simplegit.rb
  3. index 66d332e..8bb5674 100644
  4. --- a/lib/simplegit.rb
  5. +++ b/lib/simplegit.rb
  6. @@ -16,6 +16,10 @@ class SimpleGit
  7. return `#{git_cmd} 2>&1`.chomp
  8. end
  9. end
  10. +
  11. + def show(treeish = 'master')
  12. + command("git show #{treeish}")
  13. + end
  14. end
  15. test
  16. Stash this hunk [y,n,q,a,d,/,e,?]? y
  17. Saved working directory and index state WIP on master: 1b65b17 added the index file

从贮藏创建一个分支

如果贮藏了一些工作,将它留在那儿了一会儿,然后继续在贮藏的分支上工作,在重新应用工作时可能会有问题。 如果应用尝试修改刚刚修改的文件,你会得到一个合并冲突并不得不解决它。 如果想要一个轻松的方式来再次测试贮藏的改动,可以运行 git stash branch 以你指定的分支名创建一个新分支,检出贮藏工作时所在的提交,重新在那应用工作,然后在应用成功后丢弃贮藏:

  1. $ git stash branch testchanges
  2. M index.html
  3. M lib/simplegit.rb
  4. Switched to a new branch 'testchanges'
  5. On branch testchanges
  6. Changes to be committed:
  7. (use "git reset HEAD <file>..." to unstage)
  8. modified: index.html
  9. Changes not staged for commit:
  10. (use "git add <file>..." to update what will be committed)
  11. (use "git checkout -- <file>..." to discard changes in working directory)
  12. modified: lib/simplegit.rb
  13. 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 选项来运行命令, 这意味着“做一次演习然后告诉你 将要 移除什么”。

  1. $ git clean -d -n
  2. Would remove test.o
  3. Would remove tmp/

默认情况下,git clean 命令只会移除没有忽略的未跟踪文件。 任何与 .gitignore 或其他忽略文件中的模式匹配的文件都不会被移除。 如果你也想要移除那些文件,例如为了做一次完全干净的构建而移除所有由构建生成的 .o 文件, 可以给 clean 命令增加一个 -x 选项。

  1. $ git status -s
  2. M lib/simplegit.rb
  3. ?? build.TMP
  4. ?? tmp/
  5. $ git clean -n -d
  6. Would remove build.TMP
  7. Would remove tmp/
  8. $ git clean -n -d -x
  9. Would remove build.TMP
  10. Would remove test.o
  11. Would remove tmp/

如果不知道 git clean 命令将会做什么,在将 -n 改为 -f 来真正做之前总是先用 -n 来运行它做双重检查。 另一个小心处理过程的方式是使用 -i 或 “interactive” 标记来运行它。

这将会以交互模式运行 clean 命令。

  1. $ git clean -x -i
  2. Would remove the following items:
  3. build.TMP test.o
  4. *** Commands ***
  5. 1: clean 2: filter by pattern 3: select by numbers 4: ask each 5: quit
  6. 6: help
  7. What now>

这种方式下可以分别地检查每一个文件或者交互地指定删除的模式。

Note 在一种奇怪的情况下,可能需要格外用力才能让 Git 清理你的工作目录。 如果你恰好在工作目录中复制或克隆了其他 Git 仓库(可能是子模块),那么即便是 git clean -fd 都会拒绝删除这些目录。这种情况下,你需要加上第二个 -f 选项来强调。