# Git stash ![Saving changes](https://wac-cdn.atlassian.com/dam/jcr:75f75cb6-a6ab-4f0b-ab29-e366914f513c/hero.svg?cdnVersion=kg) ***[git add](2.3.1-git_add.md) / [git commit](2.3.2-git_commit.md) / [git diff](2.3.3-git_diff.md) / [git stash](2.3.4-git_stash.md) / [.gitignore](2.3.5-git_ignore.md)*** > ✍️ [童仲毅](https://github.com/geeeeeeeeek) | ⏳ 2018 年 12 月 11 日 > > ©️ 本文演绎自 Atlassian 编写的 [_Saving Changes_](https://www.atlassian.com/git/tutorials/saving-changes)。页面上所有内容采用知识共享-署名([CC BY 2.5 AU](http://creativecommons.org/licenses/by/2.5/au/deed.zh))许可协议。 `git stash` 临时储藏了你在当前工作副本中的更改,让你可以进行其它更改,之后再重新应用储藏的更改。储藏对这样的应用场景来说非常方便:你需要快速切换上下文,进行其它工作,但你之前的代码更改还未完成,也没准备好提交。 ## 目录 - [储藏你的工作](#储藏你的工作) - [重新应用储藏的更改](#重新应用储藏的更改) - [储藏未追踪或被忽略的文件](#储藏未追踪或被忽略的文件) - [管理多个储藏](#管理多个储藏) - [查看储藏与当前的差异对比](#查看储藏与当前的差异对比) - [储藏文件片段](#储藏文件片段) - [从当前储藏创建分支](#从当前储藏创建分支) - [清理储藏](#清理储藏) - [git stash 工作原理](#git stash 工作原理) ## 储藏你的工作 `git stash` 命令将你未提交的更改(不论是否缓存)储存下来,然后将它们移出当前工作副本。例如: ```shell $ git status On branch master Changes to be committed: new file: style.css Changes not staged for commit: modified: index.html $ git stash Saved working directory and index state WIP on master: 5002d47 our new homepage HEAD is now at 5002d47 our new homepage $ git status On branch master nothing to commit, working tree clean ``` 此时,你可以进行任何修改,创建新的提交,切换分支或者执行任意的 Git 操作;在你完成后再回来重新应用储藏的内容。 注意,储藏是一个本地的概念;在推送时它们不会被推送到远端服务器。 ## 重新应用储藏的更改 你可以使用 `git stash pop` 应用之前储藏的更改: ```shell $ git status On branch master nothing to commit, working tree clean $ git stash pop On branch master Changes to be committed: new file: style.css Changes not staged for commit: modified: index.html Dropped refs/stash@{0} (32b3aa1d185dfe6d57b3c3cc3b32cbf3e380cc6a) ``` *移出(pop)* 储藏内容将会将更改从储藏中移除并将它们重新添加到你的工作副本。 或者,你可以将更改重新添加到工作副本,但同时使用 `git stash apply` 将它们留在储藏中。 ```shell $ git stash apply On branch master Changes to be committed: new file: style.css Changes not staged for commit: modified: index.html ``` 这个命令可以帮助你将储藏的更改添加到多个分支。 现在你已经知道了储藏的基本用法,在使用时需要注意一点:Git 默认 *不会* 储藏未追踪或被忽略的文件。 ## 储藏未追踪或被忽略的文件 默认情况下,执行 `git stash` 将会储藏: - 添加到缓存区的更改(缓存的更改) - 当前被 Git 追踪的文件的更改(未缓存的更改) 但是它 **不会** 储存: - 工作副本中尚未缓存的新文件 - 被忽略的文件 因此,如果我们在上面的栗子中加入第三个文件,但是没有缓存它(不运行 `git add`),`git stash` 将不会储藏它。 ```shell $ script.js $ git status On branch master Changes to be committed: new file: style.css Changes not staged for commit: modified: index.html Untracked files: script.js $ git stash Saved working directory and index state WIP on master: 5002d47 our new homepage HEAD is now at 5002d47 our new homepage $ git status On branch master Untracked files: script.js ``` `-u` 选项(或 `--include-untracked`)告诉 `git stash` 同时储藏未追踪的文件: ```shell $ git status On branch master吃c Changes to be committed: new file: style.css Changes not staged for commit: modified: index.html Untracked files: script.js $ git stash -u Saved working directory and index state WIP on master: 5002d47 our new homepage HEAD is now at 5002d47 our new homepage $ git status On branch master nothing to commit, working tree clean ``` 你也可以在运行 `git stash` 时传入 `-a` 选项(或 `--all`)储藏被忽略的文件中的更改。 ## 管理多个储藏 储藏并不只限一个。你可以运行多次 `git stash` 创建多个储藏,然后使用 `git stash list` 查看所有储藏。默认情况下,储藏被认为是所在分支或提交顶端的“WIP”(work in progress,正在进行的工作)。一段时间后,分清每个储藏里的内容可能会变得有些困难。 ```shell $ git stash list stash@{0}: WIP on master: 5002d47 our new homepage stash@{1}: WIP on master: 5002d47 our new homepage stash@{2}: WIP on master: 5002d47 our new homepage ``` 为了提供更多的上下文信息,最好使用 `git stash save "描述"` 给你的储藏加上一段描述: ```shell $ git stash save "add style to our site" Saved working directory and index state On master: add style to our site HEAD is now at 5002d47 our new homepage $ git stash list stash@{0}: On master: add style to our site stash@{1}: WIP on master: 5002d47 our new homepage stash@{2}: WIP on master: 5002d47 our new homepage ``` 默认情况下, `git stash pop` 会应用最近创建的储藏:`stash@{0}`。 通过传入标识符作为最后一个参数,你可以选择应用哪个储藏,例如: ```shell $ git stash pop stash@{2} ``` ## 查看储藏与当前的差异对比 你可以使用 `git stash show` 查看储藏摘要: ```shell $ git stash show index.html | 1 + style.css | 3 +++ 2 files changed, 4 insertions(+) ``` 或者传入 `-p` 选项( 或`--patch`)查看储藏的完整差异列表: ```shell $ git stash show -p diff --git a/style.css b/style.css new file mode 100644 index 0000000..d92368b --- /dev/null +++ b/style.css @@ -0,0 +1,3 @@ +* { + text-decoration: blink; +} diff --git a/index.html b/index.html index 9daeafb..ebdcbd2 100644 --- a/index.html +++ b/index.html @@ -1 +1,2 @@ + ``` ## 储藏文件片段 你还可以选择只储藏一个文件、几个文件或者文件中的变更片段。如果你将 `-p` 选项(或 `--patch`)传给 `git stash`,它会遍历工作副本中所有被更改的文件片段,并询问是否想要储藏: ```shell $ git stash -p diff --git a/style.css b/style.css new file mode 100644 index 0000000..d92368b --- /dev/null +++ b/style.css @@ -0,0 +1,3 @@ +* { + text-decoration: blink; +} Stash this hunk [y,n,q,a,d,/,e,?]? y diff --git a/index.html b/index.html index 9daeafb..ebdcbd2 100644 --- a/index.html +++ b/index.html @@ -1 +1,2 @@ + Stash this hunk [y,n,q,a,d,/,e,?]? n ``` 你可以点击 **?** 查看完整的片段操作命令。常见的用法有: | **命令** | **描述** | | -------- | ------------------------------ | | */* | 使用正则表达式搜索片段 | | *?* | 帮助 | | *n* | 不缓存该片段 | | *q* | 退出(已选择的片段将会被缓存) | | *s* | 将当前片段切分成更小的片段 | | *y* | 缓存该片段 | 虽然没有显式的“退出”命令,键入 `CTRL-C`(SIGINT,系统中断信号)将会退出储藏流程。 ## 从当前储藏创建分支 如果分支与缓存中的更改出现了分叉,你在重新应用储藏时可能会遇到冲突。因此,你可以使用 `git stash branch` 将储藏中的更改应用到一个新的分支: ```shell $ git stash branch add-stylesheet stash@{1} Switched to a new branch 'add-stylesheet' On branch add-stylesheet Changes to be committed: new file: style.css Changes not staged for commit: modified: index.html Dropped refs/stash@{1} (32b3aa1d185dfe6d57b3c3cc3b32cbf3e380cc6a) ``` 以上命令在储藏所在的提交上检出了一个新的分支,然后将储藏中的更改应用到这个分支上。 ## 清理储藏 如果你不再需要某个储藏,你可以使用 `git stash drop` 删除: ```shell $ git stash drop stash@{1} Dropped stash@{1} (17e2697fd8251df6163117cb3d58c1f62a5e7cdb) ``` 或者你可以使用以下命令删除所有储藏: ``` $ git stash clear ``` ## git stash 工作原理 如果你只想知道如何使用 `git stash`,你可以跳过这一节。但如果你对 Git(以及 `git stash`)的底层实现感兴趣,那就看下去吧! 储藏实际上是仓库中的提交对象。`.git/refs/stash` 中特殊的引用标记指向你最近创建的储藏。之前创建的储藏出现在 `stash` 引用的引用日志中。这也就是为什么你会通过 `stash@{n}:` 引用储藏——事实上你指向的是 `stash`引用对象的第 n 个引用日志对象。由于储藏本质是一个提交,你可以使用 `git log` 查看它: ```shell $ git log --oneline --graph stash@{0} *-. 953ddde WIP on master: 5002d47 our new homepage |\ \ | | * 24b35a1 untracked files on master: 5002d47 our new homepage | * 7023dd4 index on master: 5002d47 our new homepage |/ * 5002d47 our new homepage ``` 根据储藏的内容,一个 `git stash` 操作会创建两个或三个新的提交。对于上图来说,这些提交是: - `stash@{0}` ,运行 `git stash` 时用于存放工作目录中被追踪文件的一个新的提交的 - `stash@{0}` 的上一个父节点,运行 `git stash` 时 `HEAD` 指向的那个的提交 - `stash@{0}` 的上二个父节点,运行 `git stash` 时表示缓存区的一个新的提交 - `stash@{0}` 的上三个父节点,运行 `git stash` 时用于存放工作目录中未被追踪文件的一个新的提交的. 这个父节点只有满足这两个条件时才会被创建: - 工作副本包含未被追踪的文件; - 执行 `git stash` 时声明了 `--include-untracked` 或 `--all` 选项。 `git stash` 将你的工作区和缓存区编码成提交的流程如下: - 在储藏前,你的工作区可能包含对于追踪文件、未追踪文件和忽略文件的更改。其中一些更改可能存在于缓存区。 ![Before stashing](https://www.atlassian.com/dam/jcr:3a2ede93-1f2d-45ae-9e0b-167cc0362f37/03.2017-12-12-00-32-53.svg) - 执行 `git stash` 时,被追踪文件的更改会被编码成有向无环图中两个新的提交,其中一个包含未缓存的提交,另一个包含已缓存的更改。特殊的 `refs/stash` 引用将会更新并指向它们。 ![Git stash](https://www.atlassian.com/dam/jcr:35edaf68-e8b1-484e-b5f0-292c532f048a/04.2017-12-12-00-32-53.svg) - 使用 `--include-untracked` 选项会将未被追踪文件的更改编码成一个新的提交。 ![Git stash --include-untracked](https://www.atlassian.com/dam/jcr:f7dd5493-a98d-449e-ae37-146d6270ccf7/05.2017-12-12-00-32-52.svg) - 使用 `--all` 选项会将未追踪和被忽略文件的更改包含在同一个提交中。 ![Git Stash --all](https://www.atlassian.com/dam/jcr:446fad60-0ff5-4383-8177-a5fc2813364d/06.2017-12-12-00-32-51.svg) 在运行 `git stash pop` 时,上述提交中的更改被用于更新你的工作副本和缓存区,而储藏的引用日志会移除该提交。注意,被移出的提交并不会被立即删除,而是在下次垃圾回收时被清理。 > 这篇文章是[**「git-recipes」**](https://github.com/geeeeeeeeek/git-recipes/)的一部分,点击 [**目录**](https://github.com/geeeeeeeeek/git-recipes/wiki/) 查看所有章节。 > > 如果你觉得文章对你有帮助,欢迎点击右上角的 **Star** :star2: 或 **Fork** :fork_and_knife:。 > > 如果你发现了错误,或是想要加入协作,请参阅 [Wiki 协作说明](https://github.com/geeeeeeeeek/git-recipes/issues/1)。