为什么 git 日志的 --walk-refs (-g) 选项会禁用 --stat 和 --patch?
Why does the --walk-refs (-g) option to git log disable --stat and --patch?
从 this answer 开始,我发现与 git stash list
相比,通过使用 git log -g stash
(git log --walk-reflogs refs/stash
的缩写)我可以获得更多的能量。例如,与 git stash list
不同,我可以添加选项来缩小影响一组文件或目录的存储:git log -g stash -- Dir1 Dir2
.
不过,我发现无论我如何排列参数,我都无法让 -p/--patch
或 --stat
与 --walk-reflogs
一起工作。 git 日志的帮助并未表明这些选项有任何不兼容之处。我是否缺少让它工作的方法,或者是否有某些原因导致 --walk-reflogs
与检查补丁的属性不兼容?
TL;DR
并不是 -g
禁用了 它们,而是 git log
一开始就没有对隐藏提交做正确的事情。
考虑 运行使用 --format=%H
使用 git log
命令获取原始哈希 ID,然后 运行使用 第二个 在第一步中找到的每个哈希 ID 上的一组 Git 命令(例如 git stash show --stat
)。 Git 是一组工具,而不是一个解决方案:每个工具都会产生,或者无论如何都可以产生输出,作为另一个工具的输入。
或者——请注意,这是一个特殊情况,它最多只使用了很少记录的内容——使用:
git log -m --first-parent -p -g stash
这样 -m --first-parent
使 -p
有效。这也适用于 --stat
。
长
从技术上讲,git stash
所做的提交是 合并提交。
在正常的 git log
中,当 Git 遍历 提交图 而不是 reflogs 时, Git 正在查看 parent/child 每个提交的关系。例如,假设我们有:
...--F--G--H <-- somebranch
我们运行 git log somebranch
。 Git 首先从 b运行ch 名称中找到提交 H
的哈希 ID,这使它可以轻松访问提交 H
本身。 Git 加载 H
的元数据,其中包括早期提交 G
的哈希 ID,现在在内存中有两个哈希 ID,G
和 H
.
使用 -p
或 --stat
,git log
将 运行 在这两个提交哈希 ID 上添加 git diff
——G
和 H
—在内部显示结果差异,或来自该差异的统计数据。然后 Git 将继续提交 G
并显示 it,加载 G
的元数据,它产生早期提交的哈希 ID [=41] =].
您在每个点看到的差异或统计数据是当前提交 是单个父 的子项及其父提交与父提交之间的差异作为差异的左侧,当前提交作为右侧。然后 git log
继续显示父提交。所以差异有点“介于”提交和它的父级之间。这一切都非常有道理和逻辑。
然而,当遍历 reflogs 时,我们可能会遇到这样的事情:
...--G--H <-- branch@{0}
\
I--J--K <-- branch@{1}
例如,在丢弃提交 I-J-K
的 git reset
之后就是这种情况。 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
本身,它必须做 and/or 显示 两个 差异,并且......它不会打扰!
你可以强制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 save
或 git stash push
时,您的 当前提交 是提交 C
。 Git 通过特殊名称 HEAD
找到它,该名称附加到您的 b运行ch 名称 branch
,指向提交 C
。 stash 命令现在构建两个 (I-W
) 或三个提交,然后更新 refs/stash
以指向新提交 W
。两个或三个提交具有这些属性:
I
(索引)提交包含 Git 的 index 又名 暂存区中的任何内容当时你运行git stash
.
如果您使用 git add
或 git add -p
或其他更新 Git 索引的方法,以便 Git 中的文件 index/staging-area 与当时提交 C
中的文件不匹配ou 运行 git stash
, commit I
会有一些有用的东西。事实上,如果您在 每个 文件上使用 git add
,提交 I
将与提交 W
完全匹配!否则,如果您在 没有 个文件上使用 git add
,提交 I
的内容将与提交 C
完全相同的文件集.无论哪种方式,由于 Git 的 de-duplication,任何共享内容都将被共享,因此不占用磁盘 space。但是无论如何,提交 I
仍然存在:这就是 git stash
知道 W
是 隐藏提交的方式。1
W
(work-tree) 提交保存工作树中的任何内容,作为跟踪文件(Git 索引中存在的文件) , 当时你 运行 git stash
.
这意味着提交 W
具有大多数人通常认为的隐藏内容:他们尚未 staged-and-committed 修改的文件。它实际上有 所有 文件,包括 未 更改的文件,就像任何其他提交一样。 stash
ref(或稍后的 reflog 条目)直接指向提交 W
,因此当您 运行 git stash show stash@{2}
或 git stash apply
时,就是这样git stash
找到提交 W
,从中找到提交 I
,如果存在,也找到提交 U
。
U
提交(如果存在)包含同时存在于您的工作树中的所有未跟踪文件(可能包括被忽略的文件)。此提交仅在您使用 -u
或 -a
或其更长的拼写时存在。它 不 保存普通文件(来自提交 C
或您的 work-tree 或任何地方),这使它成为一个非常奇怪的提交。出于这个原因,它根本没有父提交:它是一个 root 提交,就像某人在一个新的空存储库中所做的第一次提交。
在 git stash
完成这两个或三个提交后,它会重置内容(使用 git reset --hard
)。如果您进行了 U
提交,git stash
也会从您的工作树中删除存储在 U
提交中的所有文件。在这一点上对我们来说有趣的是——我们正在使用 git log
——尽管如此,W
具有合并提交的 形式 ,但不是合并提交的标准 content:它不是通过合并其父项构建的,而是通过制作工作树文件的快照,其名称列在 Git' s 索引 / 在 I
提交中。2
1这是一个非常糟糕的测试,因为任何合并提交都会通过它,但总比没有好。
2从历史上看,索引中的“intent to add”标志存在错误。我没有检查它们是否在 Git 的任何特定版本中针对 git stash
进行了修复,但我会小心避免被它们绊倒:不要将 git stash
与 I-T-A 东西。好吧,更一般地说,我会说:根本不要使用 git stash
,除了非常特殊的 short-term 情况。
强制 git log
差异合并
有三个选项可以使 git log
通过合并显示差异(或 diff-stat):
-m
告诉 git log
“拆分”合并。
此选项接受任何合并提交,并且出于差异目的,假装它是多个单独的提交,每个提交都有一个父级。每个虚拟 single-parent 提交都具有合并所具有的 snapshot,但只有合并的 N 个父项之一。因此,标准 two-parent 合并会产生两个差异,而 three-parent 合并会产生三个差异,依此类推。
这,或者 运行宁 git diff
你自己手动,是 真正 看到合并中发生的事情的唯一方法。 对于真正的合并,您通常不会在意看到真正发生了什么,因为这些信息可能既多又[=281] =]不相关.
-c
选项生成 组合差异 .
--cc
选项(两个连字符和两个 c
s)产生一个 密集组合差异(有时称为“压缩combined diff”,这使得 --cc
的拼写至少有意义。
组合差异选项很难描述,但两者都有一个关键元素,可以使它们在真正的合并中有用,但在存储中无用。请记住,合并提交有两个或多个父项。 combined diff 将合并提交的内容与每个父项的内容进行比较。如果合并快照确实使用父文件之一,该文件将在合并差异输出中完全省略。
真正的合并,就是说:合并结果只是re-used一个b运行ch的文件批发。通常,在检查合并时,您并不关心这个文件:您只关心必须解决的 冲突结果文件不再匹配任一输入父 。 -c
和 --cc
选项旨在向您显示 这些文件 .
但是通过隐藏,I
提交通常 完全匹配 C
或 W
提交。如果它确实匹配 W
提交,这将 忽略每个文件 。如果 I
匹配 C
我们的状态更好,但是 -c
和 --cc
选项在这里都是错误的方向。
最后,还有一个方便的 special-purpose 选项,--first-parent
。此标志的主要功能是改变 Git 如何通过 遍历 合并,仅跟随第一个父级。但是,还有一个次要功能。请注意,此选项的操作最近已略有更新,因此 git log --first-parent -p
现在等同于 git log --first-parent -m -p
、3 但无论您的 Git vintage,您可以编写 git log --first-parent -mp
来调用次要功能。在这里,m
选项像往常一样“拆分”合并,但是 --first-parent
与此操作结合以 diff 仅针对第一个父 (以及步行使用常规图形遍历时只有第一个父级)。
3此功能是 Git 2.29.0 中的新增功能。要禁用它,请使用 git log -m -p --no-diff-merges
.
把这些放在一起
最后这一切意味着,为了查看带有 git log -g
的存储,-m
选项是必需的,以便 (a) 启用 -p
或--stat
选项和 (b) 使生成的差异有用。 --first-parent
选项是可取的,因为如果没有它,即使 Git 遍历引用日志而不是提交图,每个存储将显示为两个(常规存储)或三个(-u
或 -a
隐藏)差异。
如果您的 Git 版本是 2.29 或更高版本,您可以使用 git log --first-parent -p -g stash
:现在隐含了 -m
。或者,无论 Git 年份如何,您都可以使用 git log --first-parent -mpg
,使用组合 single-letter 标志的能力。
从 this answer 开始,我发现与 git stash list
相比,通过使用 git log -g stash
(git log --walk-reflogs refs/stash
的缩写)我可以获得更多的能量。例如,与 git stash list
不同,我可以添加选项来缩小影响一组文件或目录的存储:git log -g stash -- Dir1 Dir2
.
不过,我发现无论我如何排列参数,我都无法让 -p/--patch
或 --stat
与 --walk-reflogs
一起工作。 git 日志的帮助并未表明这些选项有任何不兼容之处。我是否缺少让它工作的方法,或者是否有某些原因导致 --walk-reflogs
与检查补丁的属性不兼容?
TL;DR
并不是 -g
禁用了 它们,而是 git log
一开始就没有对隐藏提交做正确的事情。
考虑 运行使用 --format=%H
使用 git log
命令获取原始哈希 ID,然后 运行使用 第二个 在第一步中找到的每个哈希 ID 上的一组 Git 命令(例如 git stash show --stat
)。 Git 是一组工具,而不是一个解决方案:每个工具都会产生,或者无论如何都可以产生输出,作为另一个工具的输入。
或者——请注意,这是一个特殊情况,它最多只使用了很少记录的内容——使用:
git log -m --first-parent -p -g stash
这样 -m --first-parent
使 -p
有效。这也适用于 --stat
。
长
从技术上讲,git stash
所做的提交是 合并提交。
在正常的 git log
中,当 Git 遍历 提交图 而不是 reflogs 时, Git 正在查看 parent/child 每个提交的关系。例如,假设我们有:
...--F--G--H <-- somebranch
我们运行 git log somebranch
。 Git 首先从 b运行ch 名称中找到提交 H
的哈希 ID,这使它可以轻松访问提交 H
本身。 Git 加载 H
的元数据,其中包括早期提交 G
的哈希 ID,现在在内存中有两个哈希 ID,G
和 H
.
使用 -p
或 --stat
,git log
将 运行 在这两个提交哈希 ID 上添加 git diff
——G
和 H
—在内部显示结果差异,或来自该差异的统计数据。然后 Git 将继续提交 G
并显示 it,加载 G
的元数据,它产生早期提交的哈希 ID [=41] =].
您在每个点看到的差异或统计数据是当前提交 是单个父 的子项及其父提交与父提交之间的差异作为差异的左侧,当前提交作为右侧。然后 git log
继续显示父提交。所以差异有点“介于”提交和它的父级之间。这一切都非常有道理和逻辑。
然而,当遍历 reflogs 时,我们可能会遇到这样的事情:
...--G--H <-- branch@{0}
\
I--J--K <-- branch@{1}
例如,在丢弃提交 I-J-K
的 git reset
之后就是这种情况。 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
本身,它必须做 and/or 显示 两个 差异,并且......它不会打扰!
你可以强制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 save
或 git stash push
时,您的 当前提交 是提交 C
。 Git 通过特殊名称 HEAD
找到它,该名称附加到您的 b运行ch 名称 branch
,指向提交 C
。 stash 命令现在构建两个 (I-W
) 或三个提交,然后更新 refs/stash
以指向新提交 W
。两个或三个提交具有这些属性:
I
(索引)提交包含 Git 的 index 又名 暂存区中的任何内容当时你运行git stash
.如果您使用
git add
或git add -p
或其他更新 Git 索引的方法,以便 Git 中的文件 index/staging-area 与当时提交C
中的文件不匹配ou 运行git stash
, commitI
会有一些有用的东西。事实上,如果您在 每个 文件上使用git add
,提交I
将与提交W
完全匹配!否则,如果您在 没有 个文件上使用git add
,提交I
的内容将与提交C
完全相同的文件集.无论哪种方式,由于 Git 的 de-duplication,任何共享内容都将被共享,因此不占用磁盘 space。但是无论如何,提交I
仍然存在:这就是git stash
知道W
是 隐藏提交的方式。1W
(work-tree) 提交保存工作树中的任何内容,作为跟踪文件(Git 索引中存在的文件) , 当时你 运行git stash
.这意味着提交
W
具有大多数人通常认为的隐藏内容:他们尚未 staged-and-committed 修改的文件。它实际上有 所有 文件,包括 未 更改的文件,就像任何其他提交一样。stash
ref(或稍后的 reflog 条目)直接指向提交W
,因此当您 运行git stash show stash@{2}
或git stash apply
时,就是这样git stash
找到提交W
,从中找到提交I
,如果存在,也找到提交U
。U
提交(如果存在)包含同时存在于您的工作树中的所有未跟踪文件(可能包括被忽略的文件)。此提交仅在您使用-u
或-a
或其更长的拼写时存在。它 不 保存普通文件(来自提交C
或您的 work-tree 或任何地方),这使它成为一个非常奇怪的提交。出于这个原因,它根本没有父提交:它是一个 root 提交,就像某人在一个新的空存储库中所做的第一次提交。
在 git stash
完成这两个或三个提交后,它会重置内容(使用 git reset --hard
)。如果您进行了 U
提交,git stash
也会从您的工作树中删除存储在 U
提交中的所有文件。在这一点上对我们来说有趣的是——我们正在使用 git log
——尽管如此,W
具有合并提交的 形式 ,但不是合并提交的标准 content:它不是通过合并其父项构建的,而是通过制作工作树文件的快照,其名称列在 Git' s 索引 / 在 I
提交中。2
1这是一个非常糟糕的测试,因为任何合并提交都会通过它,但总比没有好。
2从历史上看,索引中的“intent to add”标志存在错误。我没有检查它们是否在 Git 的任何特定版本中针对 git stash
进行了修复,但我会小心避免被它们绊倒:不要将 git stash
与 I-T-A 东西。好吧,更一般地说,我会说:根本不要使用 git stash
,除了非常特殊的 short-term 情况。
强制 git log
差异合并
有三个选项可以使 git log
通过合并显示差异(或 diff-stat):
-m
告诉git log
“拆分”合并。此选项接受任何合并提交,并且出于差异目的,假装它是多个单独的提交,每个提交都有一个父级。每个虚拟 single-parent 提交都具有合并所具有的 snapshot,但只有合并的 N 个父项之一。因此,标准 two-parent 合并会产生两个差异,而 three-parent 合并会产生三个差异,依此类推。
这,或者 运行宁
git diff
你自己手动,是 真正 看到合并中发生的事情的唯一方法。 对于真正的合并,您通常不会在意看到真正发生了什么,因为这些信息可能既多又[=281] =]不相关.-c
选项生成 组合差异 .--cc
选项(两个连字符和两个c
s)产生一个 密集组合差异(有时称为“压缩combined diff”,这使得--cc
的拼写至少有意义。
组合差异选项很难描述,但两者都有一个关键元素,可以使它们在真正的合并中有用,但在存储中无用。请记住,合并提交有两个或多个父项。 combined diff 将合并提交的内容与每个父项的内容进行比较。如果合并快照确实使用父文件之一,该文件将在合并差异输出中完全省略。
真正的合并,就是说:合并结果只是re-used一个b运行ch的文件批发。通常,在检查合并时,您并不关心这个文件:您只关心必须解决的 冲突结果文件不再匹配任一输入父 。 -c
和 --cc
选项旨在向您显示 这些文件 .
但是通过隐藏,I
提交通常 完全匹配 C
或 W
提交。如果它确实匹配 W
提交,这将 忽略每个文件 。如果 I
匹配 C
我们的状态更好,但是 -c
和 --cc
选项在这里都是错误的方向。
最后,还有一个方便的 special-purpose 选项,--first-parent
。此标志的主要功能是改变 Git 如何通过 遍历 合并,仅跟随第一个父级。但是,还有一个次要功能。请注意,此选项的操作最近已略有更新,因此 git log --first-parent -p
现在等同于 git log --first-parent -m -p
、3 但无论您的 Git vintage,您可以编写 git log --first-parent -mp
来调用次要功能。在这里,m
选项像往常一样“拆分”合并,但是 --first-parent
与此操作结合以 diff 仅针对第一个父 (以及步行使用常规图形遍历时只有第一个父级)。
3此功能是 Git 2.29.0 中的新增功能。要禁用它,请使用 git log -m -p --no-diff-merges
.
把这些放在一起
最后这一切意味着,为了查看带有 git log -g
的存储,-m
选项是必需的,以便 (a) 启用 -p
或--stat
选项和 (b) 使生成的差异有用。 --first-parent
选项是可取的,因为如果没有它,即使 Git 遍历引用日志而不是提交图,每个存储将显示为两个(常规存储)或三个(-u
或 -a
隐藏)差异。
如果您的 Git 版本是 2.29 或更高版本,您可以使用 git log --first-parent -p -g stash
:现在隐含了 -m
。或者,无论 Git 年份如何,您都可以使用 git log --first-parent -mpg
,使用组合 single-letter 标志的能力。