【问题标题】:Why does the --walk-refs (-g) option to git log disable --stat and --patch?为什么 git log 的 --walk-refs (-g) 选项会禁用 --stat 和 --patch?
【发布时间】:2020-10-25 16:18:25
【问题描述】:

this answer 中,我看到与git stash list 相比,使用git log -g stashgit log --walk-reflogs refs/stash 的缩写)可以获得更大的功率。例如,与git stash list 不同,我可以添加选项以缩小影响一组文件或目录的存储:git log -g stash -- Dir1 Dir2

但我发现,无论我如何订购参数,我都无法让-p/--patch--stat--walk-reflogs 一起工作。 git log 的帮助并不表明这些选项有任何不兼容。我是否错过了让它工作的方法,或者有什么原因导致--walk-reflogs 与检查补丁的属性不兼容?

【问题讨论】:

    标签: git version-control


    【解决方案1】:

    TL;DR

    并不是-g 禁用它们,而是git log 首先没有对隐藏提交做正确的事情。

    考虑使用 --format=%H 运行 git log 命令以获取原始哈希 ID,然后对在第一步。 Git 是一组工具,而不是解决方案:每个工具都会产生,或者无论如何都可以产生作为另一个工具输入的输出。

    或者——请注意,这是一个特例,它使用的东西充其量只是很少记录的——使用:

    git log -m --first-parent -p -g stash
    

    以便-m --first-parent 使-p 有效。这也适用于--stat

    git stash 进行的提交从技术上讲是合并提交,这一事实让你感到困扰。

    在普通的git log 中,当 Git 遍历 commit graph 而不是 reflogs 时,Git 会查看每个提交的父/子关系。例如,假设我们有:

    ...--F--G--H   <-- somebranch
    

    我们运行git log somebranch。 Git 首先从分支名称中找到提交 H 的哈希 ID,这样可以轻松访问提交 H 本身。 Git 加载 H 的元数据,其中包括早期提交 G 的哈希 ID,现在在内存中同时具有哈希 ID GH

    使用-p--statgit log 将在内部对这两个提交哈希 ID(GH)运行 git diff,并显示生成的差异或来自该差异的统计信息.然后 Git 将继续提交 G 并显示 it,加载 G 的元数据,从而生成早期提交 F 的哈希 ID。

    您在每个点看到的差异或统计数据是当前提交之间的差异,它是单个父项的子项,与其父项之间的差异,父项提交位于差异和当前提交作为右侧。然后git log 继续显示父提交。所以差异有点“介于”提交和它的父级之间。这一切都非常有道理和逻辑。

    但是,当遍历 reflogs 时,我们可能会遇到这样的情况:

    ...--G--H   <-- branch@{0}
             \
              I--J--K   <-- branch@{1}
    

    例如,在 git reset 丢弃提交 I-J-K 之后就是这种情况。 git log 命令会将提交 H 显示为与提交 G 的差异,然后将提交 K 显示为与 J 的差异。只要您为此做好准备,并了解这里发生的事情,就可以了:这就是 git log 实际上会做的事情。

    但我在上面用粗体表示了一个短语,关于一个单亲的提交。 git log 遇到合并提交时,它根本不会费心去做差异,至少在默认情况下是这样。也就是说,当提交图看起来像这样时:

    ...--I--J
             \
              M--N   <-- branch
             /
    ...--K--L
    

    并且git log 本身到达提交M,它根本不会显示-p--stat 输出。它从M 移动到J,并且在提交J 时,它确实 运行了一个差异(针对提交I)。它还会从M 移动到L——除非你要求--first-parent,也就是说——并且在L,将显示一个差异(来自K)。但在 M 本身,它必须做和/或显示 两个 差异,而且......它只是不打扰!

    可以强制git log 处理这些差异,但有几个注意事项。最重要的是,这些差异通常旨在对合并提交做一些有用的事情,因此它们默认会省略很多信息。他们省略的信息通常完全破坏了他们对git stash 的用处,因为虽然git stash 进行提交 ,但从技术上讲,这些合并提交没有有用的形式作为合并:当您稍后将git stash 与这些提交一起使用时,存储代码会将它们分开,一次提交一个,而不是像通常使用合并的方式使用它们。

    存储的形式

    git stash 的提交采用两种形式之一。你要么得到这个:

    ...--o--o--C   <-- branch (HEAD)
               |\
               I-W   <-- stash
    

    或:

    ...--o--o--C   <-- branch (HEAD)
               |\
               I-W   <-- stash
                /
               U
    

    当您运行git stash savegit stash push 时,您的当前提交 是提交C。 Git 通过特殊名称HEAD 找到它,该名称附加到您的分支名称branch,它指向提交C。 stash 命令现在构建两个 (I-W) 或三个提交,然后更新 refs/stash 以指向新的提交 W。两个或三个提交具有以下属性:

    • I(索引)提交保存了您运行 git stash 时 Git 的 index 又名 暂存区 中的任何内容。

      如果您使用git addgit add -p 或其他一些更新Git 索引的方法,以使Git 的索引/暂存区中的文件与您运行@987654396 时提交C 中的文件不匹配@, commit I 里面会有一些有用的东西。事实上,如果你在每个文件上使用git add,提交I将完全匹配提交W!否则,如果您在 no 文件上使用了git add,则提交I 将具有与提交C 完全相同的文件集作为其内容。无论哪种方式,由于 Git 的重复数据删除,任何共享内容都将被共享,因此不占用磁盘空间。但无论如何,提交 I 仍然存在:这就是 git stash 知道 W 是一个隐藏提交的方式。1

    • W(工作树)提交在您运行 git stash 时将工作树中的任何内容保存为跟踪文件(Git 索引中存在的文件)。

      这意味着commit W 拥有大多数人认为的隐藏内容:他们尚未暂存并提交的已修改文件。它实际上具有 所有 文件,包括那些 更改的文件,就像任何其他提交一样。 stash ref(或后来的 reflog 条目)直接指向提交 W,因此,例如,当您运行 git stash show stash@{2}git stash apply 时,git stash 就是这样找到提交 W,它从中找到提交 I,如果存在,也提交 U

    • U 提交(如果存在)包含同时存在于工作树中的任何未跟踪文件(可能包括被忽略的文件)。仅当您使用 -u-a 或其更长的拼写时,此提交才存在。它保存普通文件(来自提交 C 或您的工作树或任何地方),这使它成为一个非常奇怪的提交。出于这个原因,它根本没有父提交:它是一个根提交,就像某人在一个新的空存储库中所做的第一次提交一样。

    git stash 进行这两个或三个提交之后,它会重置事物(使用git reset --hard)。如果您进行了U 提交,git stash 也会从您的工作树中删除存储在U 提交中的所有文件。在这一点上对我们来说有趣的是——我们使用git log——尽管W 具有合并提交的form,但不是标准的content 合并提交:它不是通过合并其父级构建的,而是通过创建工作树文件的快照,其名称列在 Git 的索引中 / 在 I 提交中。2


    1这是一个非常糟糕的测试,因为任何合并提交都会通过它,但总比没有好。

    2从历史上看,索引中的“intent to add”标志一直存在错误。我还没有检查它们是否在任何特定版本的 Git 中针对 git stash 进行了修复,但我会提防绊倒它们:不要将 git stash 与 I-T-A 的东西一起使用。好吧,更一般地说,我会说:根本不要使用git stash,除非是非常特殊的短期情况。


    强制 git log 进行差异合并

    有三个选项可以使git log 显示带有合并的差异(或差异统计):

    • -m 告诉git log“拆分”一个合并。

      此选项接受任何合并提交,并且出于差异目的,假装它是多个单独的提交,每个提交都有一个父级。每个虚拟单亲提交都有合并所具有的 snapshot,但只有合并的 N 个父级之一。因此,标准的双父合并会产生两个差异,而三父合并会产生三个差异,依此类推。

      这或手动运行git diff真正查看合并中发生了什么的唯一方法。 对于真正的合并,您通常不会关心看到真正发生了什么,因为这些信息可能是压倒性的并且无关

    • -c 选项产生一个组合差异

    • --cc 选项(两个连字符和两个cs)产生一个密集组合差异(有时称为“压缩组合差异”,这使得--cc 拼写有意义,至少)。

    组合的 diff 选项很难描述,但两者都有一个关键元素,可以使它们在真正的合并中有用,而在隐藏时却无用。请记住,合并提交有两个或更多父级。 combined diff 将合并提交的内容与每个父级的内容进行比较。如果合并快照确实使用了父文件之一,该文件将完全从组合差异输出中省略

    对于真正的合并,这意味着:合并结果只是重新使用了一个分支的文件。通常,在检查合并时,您并不关心此文件:您只关心必须解决的冲突,其中生成的文件不再匹配任一输入父项-c--cc 选项旨在向您显示这些文件

    但有了隐藏,I 提交通常完全匹配CW 提交。如果它与W 提交匹配,这将忽略每个文件。如果IC 匹配,我们的状态会更好,但无论哪种方式,-c--cc 选项都会在此处走向错误的方向。

    最后,还有一个方便的专用选项--first-parent。这个标志的主要功能是改变 Git 如何遍历 合并,只跟随第一个父级。但是,还有一个次要功能。请注意,此选项的操作最近略有更新,因此 git log --first-parent -p 现在等同于 git log --first-parent -m -p,3 但无论您的 Git 年份如何,您都可以编写 git log --first-parent -mp 来调用次要特征。在这里,m 选项像往常一样“拆分”合并,但 --first-parent 与此操作结合以仅针对第一个父项进行区分(以及在使用常规图游走)。


    3此功能是 Git 2.29.0 中的新功能。要禁用它,请使用git log -m -p --no-diff-merges


    把这些放在一起

    最后,对于使用git log -g 查看存储,这一切意味着-m 选项是必要的,以便(a)启用-p--stat 选项和(b)使生成的差异有用。 --first-parent 选项是可取的,因为没有它,即使 Git 正在遍历 reflogs 而不是提交图,每个存储将显示为两个(常规存储)或三个(-u-a 存储)差异。

    如果您的 Git 版本是 2.29 或更高版本,您可以使用 git log --first-parent -p -g stash:现在隐含了 -m。或者,无论 Git 年份如何,您都可以使用 git log --first-parent -mpg,使用组合单字母标志的功能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-10-22
      • 1970-01-01
      • 1970-01-01
      • 2019-11-19
      • 2023-03-31
      • 2012-02-29
      • 2016-11-03
      • 1970-01-01
      相关资源
      最近更新 更多