$ git stash show -p stash@{N}
和
$ git show stash@{N}
后者显示了一些附加的提交信息,但实际的差异要短得多。 (前者显示大约十几个文件,而后者仅显示一个文件。)
那么,这两者之间到底有什么区别,为什么它们不同?
我是否也可以依赖像 git diff stash@{M} stash@{N}
这样的东西是正确的?
git stash
保存的东西是我称之为"隐藏包"的内容。它由两个1不同的提交组成: "索引" 提交(暂存区)和 "工作树" 提交。 工作树提交是一种有趣的合并提交。
让我在这里再画一遍(参见引用答案以获取更长的版本),只是为了正确说明。假设您只有一个分支,上面有三个提交:A
到 C
。您在该分支上进行了一些更改,然后运行 git stash save
(或只是简单地运行 git stash
)。这就是你得到的:
A - B - C <-- HEAD=master
|\
i-w <-- the "stash"
现在你可能会创建(或切换到)另一个分支,但为了说明问题,让我们假设你把那个存储留在那里,并在 master
上进行更多的“常规”提交:
A - B - C - D - E <-- HEAD=master
|\
i-w <-- stash
A - B - C - D - E <-- HEAD=master
|\ |\
i-w i-w <-- stash
.
-------------- stash@{1}
git stash drop
时,stash脚本只是操作stash
引用的reflog,以删除已删除的stash-bag的ID。这就是为什么所有“更高级”的stash都会重新编号。实际的stash-bag本身将在下一个git gc
上进行垃圾回收。)676699a0e0cdfd97521f3524c763222f1c30a094
。您可以编写它。它总是表示相同的提交。提交永远无法更改,并且那是提交整个内容的加密哈希,因此如果该特定提交存在,则该值始终是其名称。)HEAD
和HEAD~2
,以及类似于HEAD@{yesterday}
或master@{1}
的reflog风格名称。(有一个命令git rev-parse
,可以将这样的名称字符串转换为哈希值。试试看:运行git rev-parse HEAD
、git rev-parse stash
等。Git中的大多数功能都使用git rev-parse
或其更强大的兄弟git rev-list
将名称转换为SHA-1值。)git show
与git stash show
,以及git diff
等内容。首先处理git stash show
,因为这是您应该使用的。此外,git stash
子命令将验证您命名的提交或者通过stash
引用查找到的提交是否“看起来”像一个存储,即这些有趣的合并提交之一。
如果您运行git stash show -p
,git会向您显示差异(-p
atch)。但它到底在显示什么呢?
回到存储袋的图表中。每个存储袋都悬挂在一个特定的提交上。上面,“主”存储现在从提交E
悬挂下来,而早期的stash@{1}
存储则从C
悬挂下来。
git stash show -p
的作用是将该stash工作树提交(w
)与stash所挂靠的提交进行比较。4
当然,您也可以自己执行此操作。假设您想要比较挂在提交E
下的stash
中的w
与名为master
的分支名称指向的提交E
进行比较。因此,您可以运行:git diff master stash
。这里的名称stash
指的是(当前的)stash提交w
,而master
指的是提交E
,因此这将产生与git stash show -p stash
完全相同的补丁。(如果您想将stash@{1}
中的w
与提交C
进行比较,只需运行git diff
,然后指定这两个提交名称即可。当然,使用git stash show -p stash@{1}
更容易些。)5
那么,git show
呢?这个有点复杂。 git show
可以展示一个提交,你给它了一个 stash
引用(无论是 stash
本身还是其中一个 reflog 变种)。那是一个有效的提交标识符,并且它解析为一个 stash-bags 中的一个 w
工作树提交。但是,当 git show
看到一个 合并 提交时会有不同的行为。正如文档所说:
它还会以
git diff-tree --cc
生成的特殊格式呈现合并提交。
因此,git show stash@{1}
会显示一个“组合差异”,假设提交 w
是提交 C
和 i
的正常合并,生成 w
。实际上它并不是一个正常的合并,虽然组合差异可能实际上是有用的,前提是你知道你正在查看什么。阅读 --cc
在 git diff-tree
下的文档以了解其详细信息,但我要注意的是,--cc
意味着包括这一部分:
在...仅列出从所有父级修改的文件。
stash
的情况下,如果在运行 git stash
之前使用 git add
添加文件,使得 i
对 w
的差异为空,则您在此处的输出中将看不到这些文件。
最后,如果你执行git diff stash@{M} stash@{N}
:这只是要求git diff
比较不同的w
ork-tree提交。这意味着什么取决于你要比较什么,通常取决于存储区袋子的附加位置。
1实际上是两个或三个,但我会将其画成两个。使用git stash save
(或普通的git stash
,这意味着git stash save
)可以得到两个提交。如果你添加-u
或-a
选项以保存未跟踪或所有文件,则会得到三个提交。这会影响存储恢复,但不会影响git stash show
命令的输出。
2"引用名称"只是一个名称,类似于分支或标签名称。有许多可能的引用名称形式。分支和标签只是具有特殊目的的名称。"远程分支"是这些引用的另一种形式,"stash"也是一个引用。
事实上,HEAD
只是另一个引用,尽管它是一个非常特殊的引用。它非常重要,以至于如果你删除HEAD
文件,git会认为你的仓库实际上不再是仓库。
HEAD
、ORIG_HEAD
、MERGE_HEAD
等),所有引用都以字符串refs/
开头。分支以refs/heads/
开头,标签以refs/tags/
开头,“远程分支”以refs/remotes/
开头。换句话说,引用具有“命名空间”,通常以refs/
开头,然后在此之下再加一个单词来标识它们所在的位置。refs/stash
(并停在那里,没有refs/stash/jimmy_kimmel
或类似的东西)。
3实际上,这确实使用了引用日志。这意味着,除了“主要”存储在refs/stash
中的暂存内容外,其他暂存内容将会可能过期。(幸运的是,如musiphil所述,自git 1.6.0以来,默认情况下这些内容不会过期;你必须配置它们的过期时间才能使其发生——这可能不是你想要的.)
4它聪明的做法,使用后缀^
符号进行标记,已经在我的other answer中详细解释。
5如果你想查看这些存储包中的索引提交,该怎么办呢? 嗯,好问题! :-) 存储脚本没有一个好的答案。 看到这些的简单方法是使用“^2”后缀来命名每个存储的第二个父项,即“i”提交。 如果您有一个包含未跟踪或所有文件的第三个提交的存储,那么它就是第三个父项:提交“w”看起来像是一个三个父项的合并,而“stash ^ 3”可以获取第三个父项。 但是再次说明,“w”不是正常合并,所以很棘手。 可能最好的查看存储的所有部分的简单方法是将其转换为自己的单独分支,使用“git stash branch”。
git stash show -p stash@{N}
将显示存储中的所有更改,包括添加到暂存区的更改。但是,git show stash@{N}
不会包括在存储之前已经暂存的更改。似乎git stash
命令足够聪明,可以将它们合并在一起,而git show
只是显示blob stash@{0}
的内容。git diff stash@{M} stash@{N}
将按预期工作。git stash show -p
仅查看工作目录版本,完全忽略索引版本。在大多数存储情况下,这并不重要,但是如果您添加了一堆内容,然后大部分撤消工作树副本,则来自git stash show
的输出可能会非常误导人。这是存储脚本中我不太满意的东西之一。(但如果有明显的修复方法,那么无疑它们已经存在其中。:-))(另外我说“大部分还原”,因为如果您将工作树版本与HEAD完全同步,就会遇到“stash”的错误). - torek
refs/stash
之外的其他隐藏储藏,都会过期,就像所有的reflog条目一样”:GIT v1.6.0 Release Notes中提到,“默认情况下,隐藏储藏条目永不过期。将[gc“refs/stash”]中的reflogexpire设置为合理值,以恢复传统的自动过期行为”。 - musiphilgit stash pop
无法保留我的索引而感到沮丧。阅读了您的答案中的细节,尤其是关于有两个提交存储的事实,我更加仔细地查看了隐藏修补程序。我“发现”了git stash pop --index
,它“尝试恢复的不仅是工作树的更改,还包括索引的更改”。要是我早点意识到这一点就好了。感谢您指出正确的方向。 - Rhubbarbgit stash save --include-untracked
(-u
),在其中我设置了所有四种组合的索引和非索引修改以及添加的文件。 在这种情况下,我发现似乎存储了 3 (三) 个提交:这种情况下的额外提交包含未跟踪的文件。 - Rhubbarb