我认为它们应该基本相同,但是当我尝试时
$ 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
这里的重点是"储藏袋",一对i
ndex 和w
ork-tree 提交,仍然像以前一样挂在同一个提交上。 提交无法更改,存储包提交也是如此。
但是现在您通过进行一些更改(仍在master
)并再次运行git stash save
来创建新的存储。
旧的储物袋会怎样? "参考名称">2stash
,现在指向新的储物袋。 但是旧的藏袋提交仍然存在。 他们现在需要一个"reflog"样式的名称,stash@{1}
.3
无论如何,你现在拥有的是这个:
A - B - C - D - E <-- HEAD=master
| |
i-w i-w <-- stash
.
-------------- stash@{1}
(当你使用git stash drop
时,存储脚本只是操作stash
ref 的 reflog 以删除丢弃的存储袋的 ID。 这就是为什么所有"更高"的都被重新编号的原因。 实际的储物袋本身在下一个git gc
被垃圾收集。
接下来的一点是理解正在发生的事情的关键。
每当 git 需要你命名一个特定的提交时,你可以通过许多不同的方式中的任何一种来做到这一点。
每个提交都有一个"真实名称",即您看到的大丑陋 SHA-1 哈希,值如676699a0e0cdfd97521f3524c763222f1c30a094
。 你可以写那个。 它总是意味着相同的提交。 提交永远无法更改,这是提交全部内容的加密哈希,因此如果该特定提交存在,则该值始终是其名称。
不过,这对人们来说不是一个好名字。 所以我们有别名:像分支和标签名称,相对名称,如HEAD
和HEAD~2
,以及 reflog 样式的名称,如HEAD@{yesterday}
或master@{1}
。 (git rev-parse
,有一个命令可以将这样的名称字符串转换为哈希值。 尝试一下:运行git rev-parse HEAD
、git rev-parse stash
等。 git 中的大多数东西都使用git rev-parse
或它的老大哥,它做更多的事情,git rev-list
,将名称转换为 SHA-1 值。
(有关如何命名修订的完整说明,请参阅 gitrevisions。 Git 使用 SHA-1 不仅仅是提交,但在这里让我们只考虑提交。
Git stash show、git show 和 git diff
好的,最后,我们可以讨论您的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
所做的是将该存储的工作树提交(w
)与挂起该存储的提交进行比较。四
你当然可以自己做。 假设你想比较stash
中挂起提交E
的w
与可以通过分支名称命名的E
master
。 所以你可以运行:git diff master 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 变体之一)。 这是一个有效的提交标识符,它解析为其中一个存储袋中的w
工作树提交之一。 但是git show
在看到合并提交时会有所不同。 正如文档所说:
它还以
git diff-tree --cc
生成的特殊格式呈现合并提交。
所以git show stash@{1}
向你展示了一个"组合差异",假设提交w
是提交C
和i
的正常合并,产生w
。 毕竟这不是一个正常的合并,尽管组合的差异实际上可能很有用,只要你知道你在看什么。 阅读git diff-tree
下的--cc
文档,详细了解其工作原理,但我会注意到--cc
暗示-c
其中包括以下位:
。仅列出从所有父级修改的文件。
在stash
的情况下,如果您在运行git stash
之前git add
了 -ed 文件,因此i
-vs-w
diff 为空,您将不会在此处的输出中看到这些文件。
最后,如果你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/
开头,然后在下面加上另一个单词来标识它们所在的位置。
stash 引用拼写为refs/stash
(并停在那里,没有refs/stash/jimmy_kimmel
或类似的东西)。
3事实上,这确实使用了 reflog。 这意味着,除其他事项外,"主要"存储refs/stash
,意志可能会过期。 (幸运的是,正如 musiphil 所指出的,自 git 1.6.0 以来的默认设置是这些不会过期;您必须为它们配置过期时间才能实现这一点——这可能不是您想要的。
4它这样做的巧妙方法,使用后缀^
符号,在我的另一个答案中详细说明。
5如果你想看看这些藏袋中的i
ndex提交怎么办? 啊,好问题!:-) 存储脚本没有一个好的答案。 查看这些内容的简单方法是使用^2
后缀来命名每个存储的第二个父项,即提交i
。 而且,如果你有一个包含未跟踪或所有文件的第三个提交的存储,那就是第三个父项:提交w
看起来像一个三父级合并,stash^3
得到第三个。 但同样,w
不是正常的合并,所以很棘手。 查看存储的所有部分的最好简单方法可能是使用git stash branch
将其转换为自己的单独分支。
我相信这是由于git单独存储工作目录和索引的怪癖。git stash show -p stash@{N}
将显示存储中的所有更改,包括添加到舞台的更改。 但是git show stash@{N}
不包括在存储之前阶段的更改。 似乎git stash
命令足够聪明,可以将它们组合在一起,而git show
只是向您显示 blobstash@{0}
的内容是的,git diff stash@{M} stash@{N}
将按您的预期工作。