Git 文件的提交历史:显示正确的提交

Git commit history of a file: showing the right commits

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

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

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

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

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

额外信息 --

使用 -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.

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

更改文件 1 的提交(仅):

提交更改了 file2(仅):

更改两个文件的提交:

让我们从测试开始:

$ 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

也就是说,我原以为 br1KL 都不会提交 EF 来自 br2。所以,看来我什么都不懂...

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

这是我预期的 2 个结果中的一个 -- 另一个是 同样没有提交 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

都显示了 9 次提交(7 次“正常”+ 2 次合并),其中 file2.txt 变了。与 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

所有return我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"
...

所以,在上面,我仍然看到太多的提交。还是一头雾水...

but never shows "merge commits", while the file can have been modified in a branch we did merge into main, for example

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

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

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

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

您专门询问了有关在输出中查找合并提交的问题。我现在认为,根据问题下的所有评论,这是一个错误:你 不想 合并提交 根本 ,甚至如果他们确实更改了相关文件。你想要的是停止git log执行历史简化

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

要达到 aha! moment,我们必须先简单回顾一下可能已经知道但可能已被您抛在脑后忘记的东西:

  • Git 是关于 提交 ,每个提交都是一个编号的实体,通过其丑陋的大 random-looking 哈希 ID 找到;
  • 每个提交存储一个快照和一些元数据,并且元数据包括一些早期提交集的原始哈希ID;和
  • 大多数 提交仅存储 一个 先前的提交哈希 ID。

这使得提交形成简单的 backwards-looking 链。让我们使用简单的大写字母作为伪装的哈希 ID,并按顺序分配它们以使我们微弱的人类大脑更容易处理,并想象我们有一个以哈希 ID H 的提交结尾的存储库,如下所示:

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

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

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

git log 命令将为 H 执行此操作,然后后退一步到 G。现在,要查看 G 中的更改,Git 必须将其 parent F 的快照与 G 中的快照进行比较。这足以知道 G.

中发生了什么变化

现在git log又可以倒退了。这会根据需要重复,直到我们 运行 一直回到第一个提交,根据定义,它只是 添加 它在快照中的所有文件。根提交之前什么都没有A,所以一切都是新的,现在git log可以停止了。

合并乱码

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

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

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

Git 当然会以通常的方式查找文件“更改”:比较 LK 中的快照,或者 KH,等等。因为每个提交都有一个 parent 提交,所以这工作正常。

一旦我们合并,但是,我们遇到了问题。合并本身通过以下方式工作:

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

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

我们现在 Git 进行了新的 合并提交 M,其中有:

  • 一张快照,但是
  • 两个 parents.

看起来像这样:

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

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

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

N 是一个 普通的单个 parent 提交 像往常一样,所以比较 MM 中的快照的好处=75=] 照常工作,照常查找变化。

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

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

添加 --first-parent 意味着 仅遵循其中一行 以便在 M 我们将看到,例如 [=44= 中发生了什么],但是 我们将不再查看 KL。我们将回到 J效果是Git 假装,在-m --first-parent期间,提交图如下所示:

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

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

通过其他几种方式消除合并混乱

我们可以,如果我们这样选择,git log比较MJ L——即,做两个独立的 git diffs——然后 丢弃这两个 diffs 的大部分结果。 Git 有两种不同的“组合差异”模式,您可以使用 -c--cc.

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

历史简化

这里真正的关键是这个。假设有一些文件 F 出现在所有三个提交 MJL 中。请记住,我们图片的这个特定片段如下所示:

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

这里git log大多数情况下所做的是试图找出文件的最终状态。为什么文件 F 看起来像 M 中的样子?但是想一想:If FJM 中不同,但是 在 [=] 中匹配 52=] vs M,那么我们在 顶行 对文件所做的任何操作都是无关紧要的! 我们 丢弃了 文件 F 的 top-row 副本,仅保留 bottom-row 副本。

所以,如果你现在问git log关于文件Fgit log根本懒得看提交 I-J。它仅跟在 底部 行之后。

另一方面,如果 FJ-vs-M 中完全匹配,但在 L-vs-[ 中不同=72=], git log -- F 将仅跟随 top 行,因为我们 丢弃了 来自 [=241] 的所有内容=]底部行。

这是历史简化的简而言之。 git log 命令将在合并点 完全排除合并的“一侧” 如果可以的话。如果我们关心的文件 匹配 一侧,那么 git log 将选择这一侧。如果我们关心的文件与合并的 所有 面相匹配,git log 将随机选择一侧,然后跟随那一侧。

这意味着 git log 甚至从不查看合并“另一端”的任何文件 因此您不会在git log 输出。该程序是假设,因为合并将“一侧”置于另一侧,那是有趣的一侧,以及可能出现在另一侧的所有内容其他都是无关紧要的糟粕,弃之不用

有时候这就是你想要的

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

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

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

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

M 中的快照文件 J [=] 中的文件完全无关 241=]或L。更常见的是,我们可能在 M 中有一些文件,其中隐藏了一个更改 根本不是来自 顶部或底部行,而是由于事实上,存在冲突 and/or 组合更改没有 工作

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

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