“git log --graph”或“hg graphlog”如何工作?

Posted

技术标签:

【中文标题】“git log --graph”或“hg graphlog”如何工作?【英文标题】:How does 'git log --graph' or 'hg graphlog' work? 【发布时间】:2011-06-11 23:59:51 【问题描述】:

我知道 Git 中的历史记录存储在称为 DAG 的数据结构中。我听说过 DFS 并且知道它有点相关。

我很好奇,git log --graphhg graphlog 这样的程序是如何绘制历史的?我一直认为以如此好的方式绘制车道和所有东西是相当复杂的。

有人可以写一些伪代码来演示吗?

注意:我尝试查看 Git 或 hg 的代码,但很难理解并大致了解发生了什么。

【问题讨论】:

这是 Git 的 graph.c 供参考。 将“如何将 DAG 显示为文本图”问题的简化(但指定明确)版本作为 SO 问题发布,并将其标记为 code-golf。您将获得许多巧妙的解决方案,包括 Python、Ruby、C、Perl……您可能会要求人们发布他们的原始非高尔夫化代码以及他们的“挤出每个最后一个字符”版本。 另外,Git 的history graph API 很有用。 @Josh Lee 回答提供了 api、用法和示例。有了它,您应该了解 git log --graph 的操作方式。你也可以在api-history-graph.txt 找到 api。您需要asciidoc 才能从中获取 html 使用 Git 2.18(2018 年第二季度),git log --graph 现在有一个 commit-graph 文件用于加速步行。见my answer below 【参考方案1】:

首先,获取提交列表(如git rev-list),以及每个提交的父级。一个“列保留列表”保存在内存中。

然后对于每个提交:

如果提交没有为其保留列,请将其分配给空闲列。这就是分支头将如何开始。 根据列保留列表打印树形图形,然后是提交信息 当前列/提交的保留列表条目将使用当前提交的第一个父项进行更新,这样父项将打印在同一列中。 其他家长获得一个新的免费专栏。 如果这是一个合并,下一行将尝试将第二个父级链接到预期提交的列(这会导致循环和“≡桥”)

示例显示 git-forest 在 aufs2-util 上的输出,并额外提交以拥有多个分支)。

通过前瞻,人们可以预测合并点将下降多远,并在两根柱子之间挤压木材以获得更美观的结果。

【讨论】:

【参考方案2】:

我尝试查看 Git 或 hg 的代码,但很难理解并大致了解发生了什么。

对于 hg,您是否尝试按照 hg 本身或 graphlog 中的代码?

因为graphlog的代码很短。你可以在hgext/graphlog.py 中找到它,真正重要的部分是前 200 行,其余的是扩展的引导和找到选定的修订图。代码生成函数为ascii,其最后一个参数是调用asciiedge的结果(调用本身在generate的最后一行执行,该函数由graphlog提供给generate )

【讨论】:

【参考方案3】:

与一般的图形显示相比,这个特殊问题并不难。因为你想让节点保持它们提交的顺序,所以问题变得更加简单。

另请注意,显示模型是基于网格的,行是提交,列是过去/未来的边缘。

虽然我没有阅读 git 源代码,但您可能只是遍历提交列表,从最新开始,并维护过去的开放边缘列表。沿着边缘自然会导致拆分/合并列,最终会得到一种树 git/hg 显示。

在合并边时,您希望避免与其他边交叉,因此您必须尝试提前对列进行排序。这实际上是唯一可能不直截了当的部分。例如,可以执行双通道算法,在第一通道中为边缘创建列顺序,并在第二通道中进行绘制。

【讨论】:

git log --graph 的输出经常有边交叉,而且不是按时间顺序排列的。我认为这比你建议的要简单一些,即使它是一个相对的图表显示案例。 好吧,从顶部的最新开始并沿着边缘进入过去,即使没有严格的提交顺序,我所说的大部分内容仍然适用。根据提交图,可能无法避免频繁的边缘交叉,并且他们可能不会花太多钱来确定理想的顺序。不过,我不想暗示这是微不足道的,只是想出一个好的解决方案。【参考方案4】:

注意:Git 2.18(2018 年第 2 季度)现在会预先计算祖先遍历所需的信息并将其存储在单独的文件中以优化图遍历。

提交图的概念确实改变了“git log --graph”的工作方式。

作为mentioned here:

git config --global core.commitGraph true
git config --global gc.writeCommitGraph true
cd /path/to/repo
git commit-graph write

参见commit 7547b95、commit 3d5df01、commit 049d51a、commit 177722b、commit 4f2542b、commit 1b70dfd、commit 2a2e32b(2018 年 4 月 10 日)和commit f237c8b、commit 08fd81c、@98 commit ae30d7b、commit b84f767、commit cfe8321、commit f2af9f5(2018 年 4 月 2 日)Derrick Stolee (derrickstolee)。(由 Junio C Hamano -- gitster -- 合并到 commit b10edb2,2018 年 5 月 8 日)

您现在拥有命令git commit-graph:编写并验证 Git 提交图文件。

根据 packfiles 中的提交编写提交图文件。 包括来自现有提交图文件的所有提交。

design document 声明:

Git 遍历提交图的原因有很多,包括:

    列出和过滤提交历史记录。 计算合并基数。

随着提交计数的增加,这些操作可能会变慢。合并 基础计算出现在许多面向用户的命令中,例如“合并基础” 或“状态”,可能需要几分钟来计算,具体取决于历史形状。

这里有两个主要成本:

    解压缩和解析提交。 遍历整个图以满足拓扑顺序约束。

提交图文件是一种补充数据结构,可以加速 提交图走。 如果用户降级或禁用 'core.commitGraph' 配置设置,则现有 ODB 就足够了。

文件以“commit-graph”的形式存储在.git/objects/info 目录或备用目录的info 目录中。

提交图文件存储提交图结构以及一些 额外的元数据以加快图形遍历。 通过按字典顺序列出提交 OID,我们可以识别每个提交的整数位置,并使用这些整数位置引用提交的父级。 我们使用二进制搜索来查找初始提交,然后使用整数位置 用于在步行期间快速查找。

你可以看到test use cases:

git log --oneline $BRANCH
git log --topo-order $BRANCH
git log --graph $COMPARE..$BRANCH
git branch -vv
git merge-base -a $BRANCH $COMPARE

这将改善git log performance。


Git 2.19(2018 年第三季度)将处理锁定文件:

见commit 33286dc(2018 年 5 月 10 日)、commit 1472978、commit 7adf526、commit 04bc8d1、commit d7c1ec3、commit f9b8908、commit 819807b、commit e2838d8、@987654352(@、@9876 2018 年 5 月)和commit 83073cc、commit 8fb572a(2018 年 4 月 25 日)Derrick Stolee (derrickstolee)。 帮助者:Jeff King (peff)。(由 Junio C Hamano -- gitster -- 合并于 commit a856e7d,2018 年 6 月 25 日)

commit-graph:修复 .lock 文件存在时的 UX 问题

我们使用 lockfile API 来避免多个 Git 进程写入 .git/objects/info 目录中的提交图文件。 在某些情况下,此目录可能不存在,因此我们检查它是否存在。

现有代码在获取锁时做了如下操作:

    尝试获取锁。 如果失败,尝试创建.git/object/info目录。 尝试获取锁,必要时失败。

问题是如果lockfile存在,那么mkdir失败,给出 对用户没有帮助的错误:

"fatal: cannot mkdir .git/objects/info: File exists"

虽然从技术上讲,这尊重了锁定文件,但它对用户没有帮助。

相反,请执行以下操作:

    检查.git/objects/info是否存在;必要时创建。 尝试获取锁,必要时失败。

新的输出看起来像:

fatal: Unable to create
'<dir>/.git/objects/info/commit-graph.lock': File exists.

Another git process seems to be running in this repository, e.g.
an editor opened by 'git commit'. 
Please make sure all processes are terminated then try again. 
If it still fails, a git process may have crashed in this repository earlier:
remove the file manually to continue.

注意:提交图工具在核心对象中不起作用时 从未知类型提升为提交(例如,提交 通过引用它的标签访问)参与其中,这已经 使用 Git 2.21(2019 年 2 月)更正

参见SZEDER Gábor (szeder) 的commit 4468d44(2019 年 1 月 27 日)。(由 Junio C Hamano -- gitster -- 合并于 commit 2ed3de4,2019 年 2 月 5 日)


该算法正在 Git 2.23(2019 年第三季度)中进行重构。

见commit 238def5、commit f998d54、commit 014e344、commit b2c8306、commit 4c9efe8、commit ef5b83f、commit c9905be、commit 10bd0be、commit 10bd0be、commit 5af8039、@987654372和@(12) commit c794405(2019 年 5 月 9 日)Derrick Stolee (derrickstolee)。(由 Junio C Hamano -- gitster -- 合并于 commit e116894,2019 年 7 月 9 日)

Commit 10bd0be解释范围变化。


使用 Git 2.24(2109 年第三季度),在给定的提交对象名称上写入 commit-graph 的代码变得更加健壮。

参见SZEDER Gábor (szeder) 的commit 7c5c9b9、commit 39d8831、commit 9916073(2019 年 8 月 5 日)。(由 Junio C Hamano -- gitster -- 合并于 commit 6ba06b5,2019 年 8 月 22 日)


而且,仍然使用 Git 2.24(2019 年第 4 季度),解析和使用提交图文件的代码已针对损坏的输入变得更加健壮。

参见Taylor Blau (ttaylorr)commit 806278d、commit 16749b8、commit 23424ea(2019 年 9 月 5 日)。(由 Junio C Hamano -- gitster -- 合并于 commit 80693e3,2019 年 10 月 7 日)

t/t5318:引入失败的“git commit-graph write”测试

在损坏的存储库中调用“git commit-graph”时,当祖先提交以某种方式损坏时,可能会导致段错误。 这是由于“commit-graph.c”代码中的两个函数调用可能 返回 NULL,但在取消引用之前不检查是否为 NULL。

因此:

commit-graph.c:处理提交解析错误

要编写提交图块,“write_graph_chunk_data()”会获取要写入的提交列表并在写入必要数据之前解析每个提交,然后继续执行列表中的下一个提交。

由于这些提交中的大多数没有提前解析(列表中的 last 提交例外,它在 'copy_oids_to_commits' 的早期被解析),因此有可能对它们调用“parse_commit_no_graph()”可能会返回错误。 在取消引用以后的调用之前未能捕获这些错误可能会导致未定义的内存访问和 SIGSEGV。 ² 一个这样的例子是'get_commit_tree_oid()',它期望一个解析的对象作为它的输入(在这种情况下,commit-graph 代码传递'*list')。 如果'*list'导致解析错误,后续调用会失败。

通过检查 'parse_commit_no_graph()' 的返回值来避免此类问题,以避免将未解析的对象传递给需要已解析对象的函数,从而防止出现段错误。


在 Git 2.26(2020 年第一季度)中,计算提交图的代码已被教导使用更稳健的方式来判断两个对象目录是否引用同一事物。

请参阅commit a7df60c、commit ad2dd5b、commit 13c2499(2020 年 2 月 3 日)、commit 0bd52e2(2020 年 2 月 4 日)和 commit 1793280(2020 年 1 月 30 日)Taylor Blau (ttaylorr)。 (由Junio C Hamano -- gitster -- 合并于commit 53c3be2,2020 年 2 月 14 日)

commit-graph.h: 在 'struct write_commit_graph_context' 中存储一个 odb

签字人:Taylor Blau

commit-graph.h 中有很多地方,其中一个函数要么拥有(或几乎拥有)完整的 struct object_directory *, accesses ->path`,然后丢弃结构的其余部分。

在比较替代对象目录的位置时,这可能会让人头疼(例如,在决定是否可以合并两个提交图层的情况下)。 这些路径使用normalize_path_copy() 标准化,这可以缓解一些比较问题,但不是所有1。

通过在write_commit_graph_context 结构中存储struct object_directory* ,将char *object_dir 的用法替换为odb-&gt;path。 这是摆脱“commit-graph.c”中所有路径规范化的中间步骤。

解决用户提供的“--object-dir”参数现在需要我们将其与已知的替代项进行比较以确保相等。

在此补丁之前,未知的“--object-dir”参数将以零状态静默退出。

这显然会导致意外行为,例如验证不在存储库自己的对象存储(或其替代品之一)中的提交图,或导致拼写错误以掩盖合法的提交图验证失败。 当给定的 '--object-dir' 与任何已知的备用对象存储不匹配时,通过 'die()' 使此错误不静默。


在 Git 2.28(2020 年第三季度)中,commit-graph write --stdin-commits 得到了优化。

请参阅commit 2f00c35、commit 1f1304d、commit 0ec2d0f、commit 5b6653e、commit 630cd51、commit d335ce8(2020 年 5 月 13 日)、commit fa8953c(2020 年 5 月 18 日)和 commit 1fe1084(20 年 5 月 5 日) ) by Taylor Blau (ttaylorr)。(由 Junio C Hamano -- gitster -- 在 commit dc57a9b 中合并,2020 年 6 月 9 日)

commit-graph:丢弃COMMIT_GRAPH_WRITE_CHECK_OIDS标志

帮助:Jeff King签字人:Taylor Blau

由于7c5c9b9c57(“commit-graph:'write --stdin-commits'中的无效提交 oid 出错”,2019-08-05,Git v2.24.0-rc0 -- merge 在batch #1 中列出) ,commit-graph builtin 在接收到非提交 OID 作为 '--stdin-commits' 的输入时终止。

如果调用者不想自己剔除未提交,则此行为可能很麻烦,例如,将“git for-each-ref”管道传输到“git commit-graph write --stdin-commits”。在这种情况下,如果 'git commit-graph write' 编写包含与提交相关的输入的图表,并且默默地忽略输入的其余部分,那将是理想的。

已经提出了一些选项来实现“--[no-]check-oids”的效果,这将允许调用者让内置的提交图做到这一点。 经过一番讨论,很难想象一个调用者不想传递'--no-check-oids',建议我们应该完全摆脱抱怨未提交输入的行为。

如果调用者确实希望保留此行为,他们可以通过执行以下操作轻松解决此更改:

git for-each-ref --format='%(objectname) %(objecttype) %(*objecttype)' |
awk '
  !/commit/  print "not-a-commit:"$1 
   /commit/  print $1 
' |
git commit-graph write --stdin-commits

为了使引用不存在对象的有效 OID 在放松错误处理后确实是一个错误,请在将对象发送到提交图内部之前执行额外的查找以确保该对象确实存在。

这是使用 Git 2.28(2020 年第三季度)测试的。

参见commit 94fbd91(2020 年 6 月 1 日)和 commit 6334c5f(2020 年 6 月 3 日)Taylor Blau (ttaylorr)。(由 Junio C Hamano -- gitster -- 合并于 commit abacefe,2020 年 6 月 18 日)支持>

t5318:测试“--stdin-commits”是否尊重“--[no-]progress

签字人:Taylor Blau签字人:Derrick Stolee

最近针对 Git 的行覆盖测试未涵盖以下行:

builtin/commit-graph.c
5b6653e5 244) progress = start_delayed_progress(
5b6653e5 268) stop_progress(&progress);

当 '--stdin-commits' 和 '--progress' 都通过时执行这些语句。引入三个测试,对这些选项进行各种组合,以确保覆盖这些行。

更重要的是,这是在行使“--stdin-commits”的(有点)以前被忽略的特性,即它尊重“--progress”。

5b6653e523 之前 ("[builtin/commit-graph.c](https://github.com/git/git/blob/94fbd9149a2d59b0dca18448ef9d3e0607a7a19d/builtin/commit-graph.c):取消引用标签builtin",2020-05-13,Git v2.28.0 -- merge 列在 batch #2),取消引用来自 '--stdin-commits' 的输入是在 commit-graph.c 内部完成的。

现在可以从commit-graph.c 之外生成一个额外的进度表,添加一个相应的测试以确保它也遵守“--[no]-progress”。

生成进度表输出的其他位置(来自d335ce8f24 ("[commit-graph.c](https://github.com/git/git/blob/94fbd9149a2d59b0dca18448ef9d3e0607a7a19d/commit-graph.c) : 显示查找可达提交的进度”,2020-05-13,Git v2.28.0 -- merge 列在 batch #2)) 已经被任何通过 '--reachable' 的测试覆盖。


在 Git 2.29(2020 年第 4 季度)中,in_merge_bases_many() 是一种查看是否可以从一组提交中的任何提交访问提交的方法,在使用提交图功能时完全被破坏了,该功能已得到纠正.

参见Derrick Stolee (derrickstolee) 的commit 8791bf1(2020 年 10 月 2 日)。(由 Junio C Hamano -- gitster -- 合并到 commit c01b041,2020 年 10 月 5 日)

commit-reach: 修复 in_merge_bases_many 错误

报告人:Srinidhi Kaushik帮助人:Johannes Schindelin签字人:Derrick Stolee

回到f9b8908b ("[commit.c](https://github.com/git/git/blob/8791bf18414a37205127e184c04cad53a43aeff1/commit.c):使用代号in_merge_bases()", 2018 -05-01,Git v2.19.0-rc0 -- merge 列在 batch #1),启发式用于短路 in_merge_bases() walk。 只要调用者只检查两个提交,这就可以正常工作,但是当有多个提交时,这种启发式可能非常错误

此后的一些代码移动已将此方法更改为repo_in_merge_bases_many()commit-reach.c。启发式计算“参考”列表的最小代数,然后将该数字与“提交”的代数进行比较。

在最近的一个主题中,添加了一个测试,该测试使用in_merge_bases_many() 来测试是否可以从引用日志中提取的多个提交中访问该提交。但是,这突出了问题:如果任何参考提交的世代数小于给定的提交,那么如果存在具有更高世代数的一些参考提交,则会跳过遍历_even

这个启发式是错误的!它必须检查参考提交的 MAXIMUM 代数,而不是 MINIMUM。

修复本身是在repo_in_merge_bases_many() 中将min_generationmax_generation 交换。


在 Git 2.32 hopefullu(2021 年第一季度)之前,当存储库中使用的某些功能(例如嫁接)与提交图的使用不兼容时,我们过去常常默默地关闭提交图;我们现在告诉用户我们在做什么。

参见Johannes Schindelin (dscho) 的commit c85eec7(2021 年 2 月 11 日)。(由 Junio C Hamano -- gitster -- 合并到 commit 726b11d,2021 年 2 月 17 日)

这将显示 Git 2.31 的用途,但 it has been reverted,因为它目前的形式有点过分热心。

commit-graph:当与图表不兼容时,说明原因

签字人:Johannes Schindelin签字人:Derrick Stolee

gc.writeCommitGraph = true 时,提交图可能仍然未写入:替换对象、移植和浅存储库与提交图功能不兼容。

在这种情况下,我们需要向用户说明为什么没有编写提交图,而不是保持沉默。

警告将是:

repository contains replace objects; skipping commit-graph
repository contains (deprecated) grafts; skipping commit-graph
repository is shallow; skipping commit-graph

【讨论】:

另见 github.com/git/git/commit/… 来自 github.com/git/git/commit/…

以上是关于“git log --graph”或“hg graphlog”如何工作?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 git log --graph 显示标签名称和分支名称

PHP MySql MsSql 如何插入或更新 ['] 或 ["] 或 [`] 字符?

与、或、异或运算

jQuery 或原始 JavaScript 是不是预编译或缓存变量表达式或选择器?

QGraphicsView 或 QWidget 完成绘制或渲染时是不是存在信号或事件?

KendoGrid 禁用或启用(编辑、添加或删除按钮)基础(true 或 false 中)