【问题标题】:Git commit history of a file: showing the right commits文件的 Git 提交历史:显示正确的提交
【发布时间】:2021-12-07 16:14:17
【问题描述】:

我确实有以下别名,以显示任何给定文件的提交历史记录:

file-history = log --follow --date-order --date=short -C

它运行良好,但从不显示“合并提交”,而文件可能已经在我们合并到 main 的分支中进行了修改,例如。

解决方案是添加选项-m,但随后会显示很多很多很多合并提交,其中大多数似乎与文件的提交历史无关。

编写这样一个别名以使其行为正确的正确方法是什么(就像在 BitBucket 中一样):显示所有确实更改文件的提交,并且只显示那些? p>

额外信息 --

使用 -m 显示提交太多;具体来说:

(在红色矩形中,我应该看到的......这就是 BitBucket 显示的......)

(顺便说一句,我不明白为什么提交 da3c94a1 是重复的。)

使用 -c 显示更多提交(应该报告的第一个提交在页面底部)并显示差异(我不想在这里看到):

--cc 的结果相同:

--first-parent 显示了奇怪的结果(因为我完全没有看到我感兴趣的提交):

新的额外信息 --

而且,使用 --first-parent -m,没有变化:

对 TOREK 的回答 --

为了简单起见,我创建了以下测试仓库:

    master    master
     C--D      I--J
    /    \    /    \
A--B      G--H      M--N  master
    \    /    \    /
     E--F      K--L
     br1       br2

我确实将br1br2 合并到master

我创建了一次只更改一个文件的提交。

提交更改了 file1(仅):

  • A
  • C
  • F
  • I
  • L

提交更改了 file2(仅):

  • B
  • D
  • E
  • H
  • J
  • K
  • N

修改了两个文件的提交:

  • Gbr1 合并到 master
  • Mbr2 合并到 master

让我们从测试开始:

$ git log --decorate --date=short
2021-11-05 d670be5 (HEAD -> master, origin/master, origin/HEAD) Commit N
2021-11-05 838f65c Merge branch 'br2' (Commit M)
2021-11-05 7ae0238 (br2) Commit L
2021-11-05 affed14 Commit K
2021-11-05 ecd490f Commit J
2021-11-05 ca2e68f Commit I
2021-11-05 45d8231 Commit H
2021-11-05 eb356b8 Merge branch 'br1'
2021-11-05 9aaa030 (br1) Commit F
2021-11-05 552a403 Commit E
2021-11-05 86a71ff Commit D
2021-11-05 611bef2 Commit C
2021-11-05 eceafb8 Commit B
2021-11-05 e137033 Initial commit

你知道吗?我本来希望看到这个:

2021-11-05 d670be5 (HEAD -> master, origin/master, origin/HEAD) Commit N
2021-11-05 838f65c Merge branch 'br2' (Commit M)
2021-11-05 ecd490f Commit J
2021-11-05 ca2e68f Commit I
2021-11-05 45d8231 Commit H
2021-11-05 eb356b8 Merge branch 'br1'
2021-11-05 86a71ff Commit D
2021-11-05 611bef2 Commit C
2021-11-05 eceafb8 Commit B
2021-11-05 e137033 Initial commit

也就是说,我预计既不会看到来自br1 的提交 EF,也不会看到 KL 来自br2。所以,看来我什么都不懂……

现在,让我们看看file2.txt 的文件历史... GitHub 和 BitBucket -- 我已经测试了它们 - 向我展示以下提交(并且只有那些) 要求显示文件的历史记录:

  • B
  • D
  • E
  • G
  • H
  • J
  • K
  • M
  • N

这是我预期的 2 个结果中的 1 个 - 另一个是 没有提交 EK 也一样,因为我本以为它们会被隐藏(如 作为分支的一部分,未在master 上提交)。

现在,让我们来玩一些“文件历史”命令:

$ git log --follow --date-order --date=short -C file2.txt
2021-11-05 d670be5 (HEAD -> master, origin/master, origin/HEAD) Commit N
2021-11-05 affed14 Commit K
2021-11-05 ecd490f Commit J
2021-11-05 45d8231 Commit H
2021-11-05 552a403 Commit E
2021-11-05 86a71ff Commit D
2021-11-05 eceafb8 Commit B

$ git log --follow --date-order --date=short -C -m file2.txt
2021-11-05 d670be5 (HEAD -> master, origin/master, origin/HEAD) Commit N
2021-11-05 838f65c Merge branch 'br2' (Commit M)
2021-11-05 838f65c Merge branch 'br2' (Commit M)
2021-11-05 affed14 Commit K
2021-11-05 ecd490f Commit J
2021-11-05 45d8231 Commit H
2021-11-05 eb356b8 Merge branch 'br1'
2021-11-05 eb356b8 Merge branch 'br1'
2021-11-05 552a403 Commit E
2021-11-05 86a71ff Commit D
2021-11-05 eceafb8 Commit B

$ git log --follow --date-order --date=short -C -c -s file2.txt
2021-11-05 d670be5 (HEAD -> master, origin/master, origin/HEAD) Commit N
2021-11-05 838f65c Merge branch 'br2' (Commit M)
2021-11-05 affed14 Commit K
2021-11-05 ecd490f Commit J
2021-11-05 45d8231 Commit H
2021-11-05 eb356b8 Merge branch 'br1'
2021-11-05 552a403 Commit E
2021-11-05 86a71ff Commit D
2021-11-05 eceafb8 Commit B

$ git log --follow --date-order --date=short -C --cc -s file2.txt
2021-11-05 d670be5 (HEAD -> master, origin/master, origin/HEAD) Commit N
2021-11-05 838f65c Merge branch 'br2' (Commit M)
2021-11-05 affed14 Commit K
2021-11-05 ecd490f Commit J
2021-11-05 45d8231 Commit H
2021-11-05 eb356b8 Merge branch 'br1'
2021-11-05 552a403 Commit E
2021-11-05 86a71ff Commit D
2021-11-05 eceafb8 Commit B

$ git log --follow --date-order --date=short -C -m --first-parent file2.txt
2021-11-05 d670be5 (HEAD -> master, origin/master, origin/HEAD) Commit N
2021-11-05 838f65c Merge branch 'br2' (Commit M)
2021-11-05 ecd490f Commit J
2021-11-05 45d8231 Commit H
2021-11-05 eb356b8 Merge branch 'br1'
2021-11-05 86a71ff Commit D
2021-11-05 eceafb8 Commit B

$ git log --follow --date-order --date=short -C --cc --full-history -s file2.txt
2021-11-05 d670be5 (HEAD -> master, origin/master, origin/HEAD) Commit N
2021-11-05 838f65c Merge branch 'br2' (Commit M)
2021-11-05 affed14 Commit K
2021-11-05 ecd490f Commit J
2021-11-05 45d8231 Commit H
2021-11-05 eb356b8 Merge branch 'br1'
2021-11-05 552a403 Commit E
2021-11-05 86a71ff Commit D
2021-11-05 eceafb8 Commit B

我们来一一分析结果:

$ git log --follow --date-order --date=short -C file2.txt

不显示合并提交。结果不完整。那么失败。

$ git log --follow --date-order --date=short -C -m file2.txt

确实显示了 file2.txt 已更改的所有提交,但重复了合并 提交。部分失败...

$ git log --follow --date-order --date=short -C -c -s file2.txt

$ git log --follow --date-order --date=short -C --cc -s file2.txt

两者都显示了file2.txt 所在的 9 个提交(7 个“正常”+ 2 个合并) 改变了。与 BitBucket 和 GitHub 上的结果相同。

$ git log --follow --date-order --date=short -C -m --first-parent file2.txt

显示master 上的所有提交,其中file2.txt 已更改,并且合并 提交。可能是我得到的其他预期结果,但不一样 BitBucket 和 GitHub。那就扔掉吧。

$ git log --follow --date-order --date=short -C --cc --full-history -s file2.txt

还显示了 9 个提交。

因此,给出与 GitHub 中的命令相同(完整)结果的命令 和 BitBucket 是:

$ git log --follow --date-order --date=short -C -c -s file2.txt
$ git log --follow --date-order --date=short -C --cc -s file2.txt
$ git log --follow --date-order --date=short -C --cc --full-history -s file2.txt

回到我的请求,这可能表达得很糟糕,它是 以下:我确实想查看所有确实更改了某些文件的提交,以便 显示其他文件在相同的提交中也发生了变化,这样做发现 对于某些特定的功能请求,我必须更改的文件列表。

根据我的真实示例,BitBucket 似乎是正确的 识别这些提交,并且我的 file-history 别名没有...... 显示提交不足,提交过多,甚至不适当的提交......

回到那个真实的例子,下面的命令:

$ git log --follow --date-order --date=short -C -c -s 32-factures-creation.R | wc -l
$ git log --follow --date-order --date=short -C --cc -s 32-factures-creation.R | wc -l
$ git log --follow --date-order --date=short -C --cc --full-history -s 32-factures-creation.R | wc -l

全部返回 440 行:

2021-10-18 d5590007 Merge branch 'master' of https://bitbucket.org/.../...
2021-10-18 6ccde740 Merge branch 'master' of https://bitbucket.org/.../...
2021-10-06 9d532874 Merge branch 'indexation-RMMMG-09-2021' into release/21.10
2021-10-04 d982c3d8 Merge branch 'indexation-RMMMG-09-2021' into release/21.10
2021-10-04 0a65134f Merge branch 'indexation-RMMMG-09-2021' into release/21.10
2021-10-02 728897b9 Merge branch 'indexation-RMMMG-09-2021' into release/21.10
2021-09-30 0df507b9 Simplify SQL expression in 32-factures-creation.R
2021-09-30 16f94a10 Update format of prsAnneeMois
2021-09-29 f9a6cafb Update "Facturation à l'employeur"
2021-10-02 22ef1194 Merge branch 'feature/103-upgrade-...-logo' into release/21.10
2021-09-20 9a2244d3 (tag: xxx_21-10-20_23-01-50, tag: sh_21-10-20_22-56-11, tag: sh_21-10-20_22-54-54, tag: 2021.10.20_23.04_xxx) Merge branch 'master' of https://bitbucket.org/mc.../...
2021-09-20 9fa77b1e Merge branch 'new-new-augm-eff'
2021-07-02 b4538cce Merge branch 'new-augm-eff' into release/21.07
2021-07-02 20c72364 (tag: 2021.07.01) Merge branch 'master' of https://bitbucket.org/.../...
...

这比我在 BitBucket 上看到的要多:

2021-09-30 0df507b9 Simplify SQL expression in 32-factures-creation.R
2021-09-30 16f94a10 Update format of prsAnneeMois
2021-09-29 f9a6cafb Update "Facturation à l'employeur"
...

所以,在上面,我仍然看到太多的提交。还是很疑惑……

【问题讨论】:

  • -m-c--cc 添加到您的git log。有关这些选项的作用,请参阅git diff 的文档。 -m 的问题在于它(如您所见)可能会显示许多合并提交:文件与 either 父文件不同的任何合并提交都会显示在此处。
  • 或者,考虑使用--first-parent 遍历only 主线,完全跳过任何合并的分支。然后-m 会做你想做的事(我想-c--cc 也会做,虽然我没有测试过)。
  • 亲爱的@torek,如您所见,替代选项不起作用:它们显示了太多的提交,其中一些甚至显示了完整的差异(这里不需要什么)... ;-(
  • -c--cc 选项不应这样做,因为它们会丢弃任何与至少一个父级匹配的文件。但是,-m 选项 这样做(显示比您希望查看的更多的合并),因为您将获得两个单独的合并结果比较:一个关于第一个父母,一个关于第二个。因此,如果合并的结果与 either 父级不同(在将其剥离为感兴趣的文件之后),-m 将显示它。
  • 我确实看到了您的输出,但这并不奇怪。您需要添加-s 以抑制实际差异,同时保留-c--cc。这两个都将确保出现"evil merge"

标签: git logging merge commit history


【解决方案1】:

您专门询问了如何在输出中查找合并提交。我现在认为,基于问题下的所有 cmets,这是一个错误:您不想要合并提交根本,即使他们确实更改了文件有问题。你想要的是阻止git log 执行历史简化

为此,只需将--full-history 标志提供给git log。但了解这个标志的含义也很重要: 特别是,我认为你不明白 Git 在这里试图向你展示什么(这并不奇怪,因为 Git 文档在首先解释 Git 想要做什么)。

要访问aha! moment,我们必须先简单回顾一下可能已经知道但可能已经进入您的脑海并忘记了的东西:

  • Git 是关于commits的,每个提交都是一个编号实体,通过其大而丑陋的随机散列 ID 找到;
  • 每个提交都存储一个快照和一些元数据,,元数据包括一些早期提交的原始哈希ID;和
  • 大多数 次提交只存储 一个 以前的提交哈希 ID。

这使得提交形成简单的后向链。让我们使用简单的大写字母作为假冒的哈希 ID,并按顺序分配它们以方便我们微不足道的人类大脑,假设我们有一个以哈希 ID H 的提交结尾的存储库,如下所示:

A <-B <-C ... <-F <-G <-H

也就是说,此存储库中的最后——因此也是最新的——提交是提交H。提交H 存储每个文件的完整快照一个向后的箭头(实际上,真正的提交哈希ID)早先提交G

使用G 中存储的快照H 中存储的快照,Git 可以比较这两个快照。无论这里有什么不同,这些都是我们更改的文件;通过比较这些文件,Git 可以产生差异,显示我们更改的特定 ,或者 Git 可以只列出我们更改的文件。这很简单,但这确实意味着要知道H 中的更改,Git 必须提取两个快照:来自H 的快照,以及来自其父级G 的快照。

git log 命令将对H 执行此操作,然后返回到G。现在,要查看 G 中的变化,Git 必须将其父 F 的快照与 G 中的快照进行比较。这足以知道G 发生了什么变化。

现在git log 可以再次后退。这会根据需要重复,直到我们一直运行到第一个提交,根据定义,它只是添加它在其快照中的所有文件。在根提交 A 之前没有任何内容,所以 一切 都是新的,现在 git log 可以停止。

合并这个问题

这适用于这些简单的线性链,但 Git 的提交并不总是简单的线性链。假设我们有一个 simple-so-far 存储库,其中只有一个名为 main 的分支,它以 H 结尾,但现在我们创建了一些新的分支名称,进行一些提交 on 这些新分支,并准备合并它们:

          I--J   <-- br1
         /
...--G--H
         \
          K--L   <-- br2

H 的提交在所有 分支上,而I-J 的提交仅在br1 上,K-L 的提交仅在br2 上。此时使用git log 向我们显示J,然后是I,然后是H,然后是G,依此类推,从br1 的最新提交沿箭头向后;或者,它向我们显示L,然后是K,然后是H,然后是G,依此类推,从br2 的最新提交向后箭头。

Git 当然会以通常的方式查找文件“更改”:比较L 中的快照与K 中的快照,或KH 等。因为每个提交都只有一个父提交,这很好用。

然而,一旦我们合并,我们就会遇到问题。合并本身的工作原理:

  • 比较 HJ 以了解 br1 的变化;
  • 比较HL,看看br2 发生了什么变化;和
  • 合并这些更改,并将合并更改应用于H 中的快照。

这会在br1 上保留“我们的”更改,并在br2 上添加“他们的”更改,如果这是我们进行合并的方向。或者,它在br2 上保留“我们的”更改,并在br1 上添加“他们的”更改。无论哪种方式,结果都是相同的(冲突解决方案除外,如果有的话,这取决于我们选择如何解决冲突)。

我们现在让 Git 进行一个新的合并提交M,它有:

  • 一个快照,但是
  • 两个父母。

看起来像这样:

          I--J
         /    \
...--G--H      M
         \    /
          K--L

我已经把标签拿走了,因为此时我们经常这样做:M 现在是最新的main 提交,而当我们添加另一个新提交时N 它只是扩展了main

          I--J
         /    \
...--G--H      M--N
         \    /
          K--L

N 像往常一样是一个普通的单父提交,因此比较MN 中的快照的好处和往常一样,可以像往常一样找到更改。

另一方面,合并提交M 非常棘手。 应该 git log 如何显示更改? 更改,在 Git 中,要求我们查看“该”父提交。但是M 没有 父级。 M两个 父母,JL。我们应该使用哪一个?

-m 标志表示运行两个单独的 git diff 操作,一个针对 J,然后另一个针对 L。这样我们将看到与J 相比发生了什么变化,即我们通过K-L 引入的内容,然后我们还将看到与L 相比发生了什么变化,即我们通过I-J 引入的内容。

添加--first-parent 意味着只遵循这些行之一,以便在M 我们将看到K-L 中发生的情况,但随后我们不会再看看KL。我们将回到J效果是 Git 假装,在 -m --first-parent 的持续时间内,提交图看起来像这样:

...--G--H--I--J--M--N

这或多或少确实是您所要求的,但这不是 Bitbucket 所做的。

以其他几种方式撤消合并混乱

我们可以,如果我们愿意,让git log 比较MJ L——即,使两个单独的@987654417 @s——但是丢弃这两个差异的大部分结果。 Git 有两种不同的“组合差异”模式,您可以通过-c--cc 获得。

不幸的是,没有人做你想做的事。它们也很难解释(我仍然不知道两者之间的真正区别是什么,尽管它们明显不同:我可以展示一些差异,但我不知道知道两个不同选项的目标是什么)。

历史简化

这里真正的关键是这个。假设有一些文件 F 出现在所有三个提交 MJL 中。请记住,我们图片的这个特殊的 sn-p 看起来像这样:

       I--J
      /    \
...--H      M
      \    /
       K--L
  • 如果 F所有三个 提交中都相同,则在此合并中它并不“有趣”。没有人对其进行任何更改。
  • 如果 FJM 中匹配,但在 LM 中不同,则“有趣的事情”发生了。如果 FLM 中匹配,则相同,但在 JM 中则不同。

git log大多数情况下 所做的是尝试找出文件的最终状态。为什么文件 F 看起来像 M 中的样子?但是想一想:如果 FJM 中不同,但在 LM匹配,那么我们对顶行的文件无关紧要!我们扔掉文件F的顶行副本,只保留底行副本。 p>

因此,如果您此时询问git log 关于 文件Fgit log 根本不会费心查看提交I-J。它仅在底部行之后。

另一方面,如果 FJ-vs-M 中完全匹配,但在 L-vs-M 中不同,git log -- F 将仅遵循 top 行,因为我们扔掉所有从 bottom 行出来的东西。

简而言之,这是历史简化git log 命令将在合并点完全丢弃合并的“一侧”。如果我们关心的文件匹配一侧,那是git log 将选择的一侧。如果我们关心的文件匹配合并的所有边,git log 将随机选择一侧,然后跟随那一侧。

这意味着git log 甚至从不查看合并“另一端”的任何文件,因此您不会在git log 输出中看到任何这些提交。该程序假设由于合并占据了“一边”而不是另一边,这就是 有趣的一面,而可能出现在另一面的一切都是无关紧要的渣滓,被丢弃。

这有时是你想要的

git log 进行这种历史简化的原因是它假设您的目标是了解为什么文件看起来像 最新 版本中的样子。被丢弃的任何无关的 dross-commits 都无关紧要,所以我们甚至不看它们。

当这就是你想要的,那就是你想要的!但有时您想知道:我确定我自己更改了这个,那在哪里? 或类似的东西。在这里,您必须告诉git log 根本不要进行历史简化。这个标志是--full-history。还有其他历史简化标志,以便您可以控制简化:毕竟它很有用。通读the git log documentation History Simplification section 以查看它们。

值得在这里再补充一点,这与所谓的邪恶合并有关(参见Evil merges in git?)。我们可以:

...--J
      \
       M
      /
...--L

M 中的快照文件J 中的文件完全无关L。更常见的是,我们可能在 M 中有一些文件隐藏在其中根本不是来自顶行或底行的更改,而是由于以下事实而产生的是否存在冲突和/或组合更改没有起作用

如果“隐藏”更改是由于冲突引起的,那还不错,但如果有人陷入不相关的修复,我们就有问题了。特别是,git log 在使用 git log -- path 时默认根本不显示合并提交。它假设在合并之前的提交中,将在path 参数中显示的任何有趣的东西都将在顶行或底行中找到。但是一个“邪恶的合并”可能会引入一个“有趣的变化”,不是在任何一行,这是你必须强制git log查看合并提交的时候,使用-m,或-c,或--cc

Bitbucket 用他们的软件做什么当然取决于他们。例如,我们不知道他们当前是否使用--full-history --cc。我们不知道将来他们是否会更改内部git log 选项。因此,尝试使您的命令行git log 输出与您的 Bitbucket 视图输出完全匹配没有任何意义,因为后者首先不受您的控制。如果您打算使用 git log,那么请专注于了解 git log 正在做什么以及如何使该工作对您有利。

【讨论】:

  • 亲爱的@torek,感谢您提供如此完整的答案。我会在我的帖子中评论它...
  • 完成。更新了问题。
  • 好的:我完全不确定 什么 Bitbucket 在这里的网络幕后做了什么,但我怀疑他们正在使用 git log --no-merges(或等效的 @987654475 @,或相同但使用git rev-list 而不是git log,因为他们可能直接使用管道命令)。无论他们可能使用什么 else,强制使用 --no-merges 选项会使所有合并提交都不再显示。
  • 我还要提一下,在你的新迷你仓库中用于测试目的,在你将 br1br2 合并到 master 之后,沿着底行的提交 在 @987654481 @。从master(提交N)开始没有--first-parentgit log 将遍历所有提交。这就是真正的合并特别之处:在git merge B 之后,以前在B 但不在当前分支上的提交现在在两个分支上。不过,新的合并提交仅在当前分支上。
  • 感谢您的解释,我确实向前迈出了一些步伐,但我仍然无法理解“3 个好命令”的输出,因为它们似乎给了我误报。不过,我显然欠你最广泛的答案。
【解决方案2】:

但从不显示“合并提交”,而文件可以在我们合并到 main 的分支中被修改,例如

如果您要这样做,请添加 --first-parent -m(正如我在 cmets 中看到的 @torek 建议的那样)。不仅仅是-m,它本身更像是一个仅用于绝望案件的取证工具。

这里发生的事情是,没有--first-parent,Git 已经在提交中向您展示了这些更改。合并不会引入任何 更改。如果 Git 向您显示合并差异,它最终会向您显示所有内容至少两次。

这就是为什么避免在合并提交中引入新工作或更正是一个好主意的原因。该法案使人们无法推断合并的作用。合并冲突检测和解决已经是无限的。 Git 与任何 vcs 一样好,而且比我认为地球上所有其他 vcs 都要好,但它永远不可能完美。假设列表中的值必须与剩余值的总和具有某种数学关系,两个分支上的更改保留了该关系,但是当组合时,该关系被破坏。或者任何其他这样的条件:代码被添加到一个依赖于现有编译值的分支中,但另一个分支使它成为用户可配置的。想一想那个。

所以--first-parent -m -p 会向您显示即使合并的差异,但只有合并分支引入的更改和冲突解决方案,主线中较早完成的工作将显示在引入它的提交中。

【讨论】:

  • 亲爱的@jthill,正如您在 NEW EXTRA INFORMATION 部分中看到的那样,将 -m 添加到 --first-parent 后没有任何变化选项... ;-( 我没有看到正确的提交。与 BitBucket 的报告几乎没有任何共同点。
猜你喜欢
  • 1970-01-01
  • 2021-07-05
  • 2013-01-23
  • 1970-01-01
  • 2011-01-19
  • 2015-04-02
  • 2014-11-01
  • 2015-09-02
  • 2019-11-14
相关资源
最近更新 更多