【问题标题】:Difference between `git stash show -p stash@{N}` and `git show stash@{N}`?`git stash show -p stash@{N}` 和 `git show stash@{N}` 的区别?
【发布时间】:2014-03-11 09:46:01
【问题描述】:

我认为它们应该基本相同,但是当我尝试时

$ git stash show -p stash@{N}

和

$ git show stash@{N}

后者显示了一些额外的提交信息,但实际的差异要短得多。 (前者显示大约十几个文件,但后者只有一个。)

那么,这两者到底有什么区别,为什么不一样呢?

我也可以依靠git diff stash@{M} stash@{N} 之类的东西来正确吗?

【问题讨论】:

    标签: git git-stash git-show


    【解决方案1】:

    储物袋

    git stash 保存的东西就是我习惯称之为"stash bag" 的东西。它由两个1 单独的提交组成:“索引”提交(暂存区)和“工作树”提交。工作树提交是一种有趣的合并提交。

    让我在这里再画一次(请参阅参考答案以获得更长的版本),只是为了正确说明它。为简单起见,我们假设您有一个小型 repo,其中只有一个分支和三个提交,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
    

    这里的重点是“stash-bag”,index 和 work-tree 提交对,仍然像以前一样挂起相同的提交。提交不能更改,stash-bag 提交也是如此。

    但现在您通过进行一些更改(仍在master)并再次运行git stash save 来创建一个新 stash。

    旧的储物袋怎么办? “参考名称”2stash,现在指向 new stash-bag。但是旧的 stash-bag 提交仍然存在。他们现在需要一个“reflog”样式名称,stash@{1}。3

    不管怎样,你现在拥有的是这样的:

    A - B - C - D - E     <-- HEAD=master
            |\      |\
            i-w     i-w   <-- stash
              .
               -------------- stash@{1}
    

    (当您使用git stash drop 时,stash 脚本只是操纵stash ref 的reflog 以删除丢弃的stash-bag 的ID。这就是为什么所有“更高”的都被重新编号的原因。实际的stash- bag 本身就是垃圾收集的下一个git gc。)

    接下来的一点是了解正在发生的事情的关键。

    任何时候 git 需要你命名一个特定的提交,你可以用许多不同的方式来做。

    每个提交都有一个“真实名称”,即您看到的又大又丑的 SHA-1 哈希值,例如 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 值。)

    (有关如何命名修订的完整描述,请参阅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 会显示一个差异(-patch)。但它到底显示了什么?

    回到带有储物袋的图表。每个 stash-bag 都挂在一个 specific 提交上。上面,“主”存储现在从提交 E 挂起,stash@{1} 之前的存储从 C 挂起。

    git stash show -p 所做的是将该存储的工作树提交 w 与存储挂起的提交进行比较。4

    你当然可以自己做。假设您想比较stash 中的w,它挂起提交E,与提交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 在看到 merge 提交时的行为不同。正如文档所说:

    它还以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 差异为空,您将看不到那些文件在此处的输出中。

    最后,如果您 git diff stash@{M} stash@{N}:这只是要求 git diff 比较不同的 work-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事实上,这确实确实使用了reflog。这意味着,除其他外,除了“主要”存储,refs/stash,将可能会过期。 (幸运的是,作为musiphil notes,自 git 1.6.0 以来的默认设置是这些不会过期;您必须为它们配置过期时间才能实现这一点——这可能不是您想要的.)

    4它使用后缀^ 表示法的巧妙方式在我的other answer 中有详细说明。

    5如果您想查看这些存储袋中的index-commits 怎么办?啊,好问题! :-) stash 脚本没有一个好的答案。查看这些的简单方法是使用^2 后缀来命名每个存储的第二个父级,即i 提交。而且,如果你有一个包含未跟踪文件或所有文件的第三个提交的存储,那就是第三个父级:提交 w 看起来像一个三父级合并,stash^3 得到第三个。但同样,w 不是正常的合并,所以很棘手。查看存储的所有部分的最佳简单方法可能是使用git stash branch 将其转换为自己的单独分支。

    【讨论】:

    • 这是一个非常详细的答案。感谢您抽出宝贵时间。
    • 关于“除了“主”之外的“隐藏”,refs/stash,将过期,就像所有 reflog 条目过期一样”:GIT v1.6.0 Release Notes 说,“默认情况下,隐藏条目永不过期。设置将 [gc "refs/stash"] 中的 reflogexpire 设置为合理的值,以恢复传统的自动过期行为。
    • @musiphil:啊,很高兴知道。在较新的 git 版本中也有针对其他 refs 的新控件,但由于某种原因,我错过了这一点。
    • 不应该 git diff stash master 是 git diff master stash 来生成与 git stash show -p stash 完全相同的补丁吗?
    • @musiphil:确实!会修复的。
    【解决方案2】:

    我相信这是由于 git 单独存储工作目录和索引的怪癖。 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 add 一堆东西然后大部分还原工作树副本,git stash show 的输出实际上可能会产生误导。在存储脚本中,这是我不太满意的事情之一。 (但是没有明显的修复方法,否则毫无疑问它们已经在那里了。:-) )(另外我说“大部分恢复”,因为如果你让工作树版本与你命中的 HEAD 完全同步stash 中的一个错误。)
    • 我很欣赏另一个答案中的详细回复,但为了快速了解 TL;DR,这里的这个答案给了我我需要知道的关于在隐藏效果之前上演/未上演的关键差异.谢谢! (我假设这是一个准确的解释?是的......?)
    猜你喜欢
    • 1970-01-01
    • 2017-11-25
    • 2013-02-23
    • 1970-01-01
    • 2023-02-08
    • 2017-11-24
    • 1970-01-01
    • 2012-09-10
    相关资源
    最近更新 更多