【问题标题】:How can I sort a set of git commit IDs in topological order?如何按拓扑顺序对一组 git 提交 ID 进行排序?
【发布时间】:2014-05-08 00:08:20
【问题描述】:

我有一组提交 SHA1,没有特定的顺序。我想通过管道将此集合传递给命令,并按拓扑顺序返回这些提交。

这是执行此操作的一种方法:

git rev-list --all --topo-order | grep --file SET_OF_SHA1S

您可以想象,这是一种非常缓慢的方式,因为git rev-list 必须打印出所有的提交 SHA1,而不仅仅是我的集合中的那些。

有没有更好更快的方法来做到这一点?

用例:

我的测试框架测试某些 Git 提交并将结果存储在数据库中。我正在编写一个总结这些结果的网页,最好按顺序显示结果。按提交日期排序并不理想,因为某些重新定位的提交将具有完全相同的提交日期。

【问题讨论】:

  • 注意:git rev-list --topo-order 使用 Git 2.20(2018 年第四季度)会更快。见my answer below

标签: git


【解决方案1】:

这是加快速度的一种方法:

git rev-list --topo-order $(cat SET_OF_SHA1S) \
   | grep --file SET_OF_SHA1S --max-count $(wc -l SET_OF_SHA1S)

优化:

  • 只要求rev-list 列出可从您的 SHA1 集访问的所有提交。
  • 一旦rev-list 打印出足够多的包含您感兴趣的SHA1 集的提交,请告诉grep 停止使用--max-count 参数进行grepping。 grep 将依次关闭其输入,rev-list 将停止不必要地打印更多的 SHA1。

【讨论】:

  • 当您感兴趣的提交集与存储库大小相比较小,并且提交在历史上相对接近时,排除 @ 的父级可能是一个显着的改进来自rev-list 调用的987654328@。
  • @wchargin 你能解释一下这是做什么的吗?
  • @felixfbecker:git rev-list HASH 将查找并打印出 all 可从给定 ref 访问的提交,可能有数千个。 SET_OF_SHA1S 中所有提交的共同祖先的任何提交都不会影响排序,因此可以省略,在这种情况下,rev-list 可以在碰到它们时立即停止行走。
  • @felixfbecker:例如,在我针对 Linux 存储库运行的机器上,运行 git rev-list --topo-order 3829100a6372 bfafddd8de42 需要 6.038 秒,但在排除它们的合并基础 811ba489fa52(并计算合并base 本身只需要 0.313 秒)。
【解决方案2】:

您可以使用--no-walk 来防止git 转储除您提供的SHA-1 之外的任何SHA-1,并使用--topo-order 强制执行正确的顺序。 作为Mort pointed out in a comment,这样做不行。从 git 版本 2.4 开始,git 文档获得了新的文本(间接地)指出了这个问题。 (我认为这是git rev-list 中的一个错误,它应该加载足够多的提交图来进行拓扑排序,然后以正确的顺序仅输出用户指定的修订 ID。)

因此,我的原始脚本(留在此处)也不起作用。它可以通过从生成临时文件$TF2 的步骤中删除--no-walk 来使其工作,然后使用$TF1 的内容从它们的$TF2(排序)顺序中提取和打印“有趣的”修订。

这或多或少是Flimm's own answer 所做的。

[原始答案,有缺陷的脚本,如下]


我不确定我到底在用这段代码做什么,但很久以前,我写了一个脚本来检查提供的参数是否按拓扑顺序:

#! /bin/sh
#
# check a list of IDs to see if they're in "topo order"
usage()
{
    echo "usage: $0 id [...]"
}

case $# in
0) usage 1>&2; exit 1;;
esac

TF1=$(mktemp)
TF2=$(mktemp)
trap "rm -f $TF1 $TF2; exit" 0 1 2 3 15

# parse the arguments into one file
git rev-parse $@ > $TF1 || exit 1
# and topo-sort the arguments into another
git rev-list --topo-order --no-walk --reverse $@ > $TF2 || exit 1
# If the list is in the correct order the files will be the same
cmp -s $TF1 $TF2 || {
    # If the files differ, it's possible that some argument(s) name
    # the same rev...
    [ $(wc -l < $TF1) -eq $(wc -l < $TF2) ] || {
        echo "ERROR: there are repeats in $@"
        # finding them is a pain, we don't bother trying
        exit 1
    }
    echo "ERROR: $@ NOT in topo order"
    echo "use instead:"
    # read the topo-ordered raw IDs
    while read sha1; do
        # and find the (single) arg in $@ that names this one
        for i; do
            if [ $(git rev-parse $i) = $sha1 ]; then
                printf ' %s' $i
                break
            fi
        done
    done < $TF2
    echo
    exit 1
}
echo "$@ in topo order"
exit 0

认为我在这里想要的是发出相同的参数名称,例如,如果你说git-check-topo v1.7 1234567 branchX,它会告诉你使用(字面意思)branchX v1.7 1234567,如果这是得到的你是正确的顺序,而不是仅仅显示原始的 SHA-1。

为了您的目的,一个简单的:

git rev-list --topo-order --no-walk $@

(根据需要有或没有--reverse)应该可以工作,我认为。

【讨论】:

  • 我认为--no-walk 不会这样做。从 2.6.2 文档中,您可以看到 --no-walk 按照提供的顺序或按日期输出提交。 ` --no-walk[=(sorted|unsorted)] 只显示给定的提交,但不遍历它们的祖先。如果指定了范围,这将无效。如果给出了 unsorted 参数,则提交将按照在命令行中给出的顺序显示。否则(如果已排序或未给出参数),则提交按提交时间按时间倒序显示。不能与 --graph 结合使用。`
  • @Mort:有趣:该文本是在 git 2.4.0 中添加的。对源代码的检查表明它确实从未工作过(--no-walk 标志会导致代码在尝试拓扑排序之前返回,可能是因为无论如何都没有费心加载足够的图形来实现它)。 rev-list 命令不会reject 请求,它可以 这样做(通过进行必要的遍历,但仅将这些提交保留在内部);它只是不打扰。
【解决方案3】:

加快git rev-list --topo-order 的另一种方法是使用 Git 2.20(2018 年第四季度)

参见commit 561b583commit b454241commit 5284fc5commit f0d9cc4commit d6b4071commit 4b47a9acommit aca4240(2018 年 11 月 1 日)Derrick Stolee (derrickstolee)
(合并Junio C Hamano -- gitster --commit 62ca33e,2018 年 11 月 18 日)

revision.c: 基于生成的拓扑序算法

当前的--topo-order 算法要求在输出第一个值之前预先遍历所有可达的提交,对它们进行拓扑排序。

此补丁引入了一种新算法,该算法使用存储的代数以递增方式按拓扑顺序进行遍历,并在执行过程中输出提交。
这可以显着减少写入固定数量提交的计算时间,例如在使用“-n &lt;N&gt;”进行限制或填充寻呼机的第一页时。

在我的本地测试中,我以三种模式在 Linux 仓库上使用了以下 Git 命令:

  • HEAD~1 没有提交图,
  • HEAD~1 带有提交图,并且
  • 带有提交图的 HEAD。

这允许比较我们从提交图解析提交获得的好处,然后再次比较我们通过限制我们遍历的提交集获得的好处。

Test: git rev-list --topo-order -100 HEAD
HEAD~1, no commit-graph: 6.80 s
HEAD~1, w/ commit-graph: 0.77 s
HEAD, w/ commit-graph: 0.02 s

查看commit b454241中的所有详细信息。

注意:如commit d6b4071中所述:

rev-list 命令对 Git 的功能至关重要。

以下是几种重要的 rev-list 操作类型:

  • 基本git rev-list --topo-order HEAD
  • 范围git rev-list --topo-order compare..HEAD
  • 祖先git rev-list --topo-order --ancestry-path compare..HEAD
  • 对称差git rev-list --topo-order compare...HEAD

尽管如此,请使用 Git 2.23(2019 年第三季度),因为 Git 2.20 的拓扑结构在用于范围时引入了回归。

commit 1d8e31acommit 1b4d882(2019 年 5 月 21 日)Derrick Stolee (derrickstolee)
(由 Junio C Hamano -- gitster -- 合并于 commit bdc81d1,2019 年 6 月 17 日)

revision:保持 topo-walk 没有无趣的提交

b454241revision.c: generation-based topo-order algorithm, 2018-11-01, v2.20.0-rc0)中更新topo-order walk时,逻辑是对walk logic的巨大改写.
在那个巨大的变化中,我们不小心包含了 在expand_topo_walk() 中提交了无趣的提交。
这意味着像

这样的简单查询
git rev-list --topo-order HEAD~1..HEAD

将为从 HEAD 到达的所有提交扩展拓扑遍历,而不是 只有一次提交。

revision:使用生成 A..B --topo-order 查询

如果存在具有计算代数的提交图,则 'git rev-list --topo-order -n &lt;N&gt; &lt;rev&gt;' 查询将使用那些代 数字以减少在编写N 提交之前经过的提交次数。

b454241 中的一个警告(revision.c:基于生成的拓扑顺序 algorithm, 2018-11-01) 是为了不启用新的查询算法 修订范围为“A..B
该逻辑被放置为从“A”开始,并将这些提交标记为无趣,但在某些情况下,性能实际上比现有逻辑差。

这种性能下降的根本原因是生成 numbers 增加我们相对于 现有的按提交日期行走的启发式方法。
虽然代数实际上保证了算法是正确的,但现有的逻辑很少出错,而且增加的要求不值得付出代价。


在 Git 2.28(2020 年第三季度)中,“git log -L...”现在利用“此提交触及哪些路径?”存储在提交图系统中的信息。

commit 002933fcommit 3cb9d2bcommit 48da94bcommit d554672(2020 年 5 月 11 日)SZEDER Gábor (szeder)
请参阅 Derrick Stolee (derrickstolee)commit f32dde8(2020 年 5 月 11 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit c3a0282,2020 年 6 月 9 日)

line-log: 尝试使用基于代数的拓扑排序

签字人:SZEDER Gábor
签字人:Derrick Stolee

之前的补丁可以在历史遍历期间执行行级过滤,而不是在昂贵的预处理步骤中执行,但它仍然需要一些更简单的预处理步骤,尤其是拓扑排序。

然而,现在我们有存储代号的提交图,这使得以拓扑顺序递增地遍历历史成为可能,而无需准备 limit_list()sort_in_topological_order() 步骤;参见b45424181e ("[revision.c](https://github.com/git/git/blob/002933f3fe2b016022ebbbbb359f6aeba58309a4/revision.c): 基于生成的拓扑顺序算法", 2018-11-01, Git v2.20.0-rc0 -- merge)。

这个补丁结合了两者,所以我们可以在历史遍历期间进行拓扑排序和行级过滤,甚至消除那些更简单的预处理步骤,从而进一步减少显示第一次提交修改给定行之前的延迟范围。

revs-&gt;limited”标志在这方面起着核心作用,因为由于当前实现的限制,仅当此标志保持未设置时才启用基于代数的拓扑排序。

然而,自从12da1d1f6f 中引入该功能以来,行级日志始终在setup_revisions() 中设置此标志(“实现行历史搜索(git log -L)”,2013-03-28,Git v1 .8.4-rc0 -- merge)。

设置'limited'的原因尚不清楚,因为行级日志本身并不直接依赖它,也不影响limit_list()函数如何限制修订范围。

但是,有一个间接的依赖关系:行级日志需要拓扑排序,而“传统”sort_in_topological_order() 需要一个已经有限的提交列表,因为e6c3505b44(“确保我们之前生成了整个提交列表试图对其进行拓扑排序”,2005-07-06,Git v0.99 -- merge 列在 batch #0 中)。

基于代数的新拓扑排序不再需要有限的提交列表。

所以不要为行级日志设置'revs-&gt;limited',除非确实有必要,即:

  • 用户明确要求重写父级,因为这仍然在 line_log_filter() 预处理步骤中完成(参见上一个补丁),这也需要 sort_in_topological_order()turn limit_list()
  • 提交图文件不可用或尚不包含代号。
    在这些情况下,我们不得不依靠sort_in_topological_order()limit_list()
    generation_numbers_enabled() 的现有条件已经确保在这些情况下设置了“limited”标志;此补丁只是确保行级日志在该条件之前设置“revs-&gt;topo_order”。

虽然在 git.git 中可以衡量显示第一次提交之前减少的延迟,但需要更大的存储库才能使其清晰可见。

在这两种情况下,都选择了行范围以下,因此它们的修改与起始修订相当接近,因此这种更改的效果最为明显。

# git.git
$ time git --no-pager log -L:read_alternate_refs:sha1-file.c -1 v2.23.0

Before:

real    0m0.107s
user    0m0.091s
sys     0m0.013s

After:

real    0m0.058s
user    0m0.050s
sys     0m0.005s

# linux.git
$ time git --no-pager log \
 -L:build_restore_work_registers:arch/mips/mm/tlbex.c -1 v5.2

Before:

real   0m1.129s
user   0m1.061s
sys    0m0.069s

After:

real   0m0.096s
user   0m0.087s
sys    0m0.009s

Derrick Stolee 的附加测试:由于这个补丁提高了第一个结果的性能,我在 Linux 内核存储库上重复了上一个补丁的实验,在这里实时报告:

Command: git log -L 100,200:MAINTAINERS -n 1 >/dev/null
 Before: 0.71 s
 After: 0.05 s

现在,在报告第一个结果之前,我们已经删除了所有 ~910,000 次提交的完整拓扑顺序。剩下的性能改进是:

  1. 将父级重写逻辑更新为增量式,类似于“git log --graph”的行为方式。

  2. 使用changed-path Bloom 过滤器来减少在tree-diff 中查看路径是否更改所花费的时间。


在 Git 2.31(2021 年第一季度)中,拓扑行走代码路径被新的 trace2 统计信息覆盖。

Derrick Stolee (derrickstolee)commit 90b666d(2020 年 12 月 30 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit 02feca7,2021 年 1 月 15 日)

revision: 追踪地形步行统计

签字人:Derrick Stolee

42e50e7 ("revision.c: add trace2 stats around Bloom filter usage", 2020-04-06, Git v2.27.0-rc0 -- @987654364 @ 列于batch #6)。
为使用代数限制行走大小的拓扑行走算法添加类似的跟踪。

此信息有助于调查和描述启发式方法和其他更改的好处。

打印的信息是 JSON 格式,可以很好地格式化如下:

{
unt_explort_walked":2603,
unt_indegree_walked":2603,
unt_topo_walked":473
}

这些值中的每一个都计算拓扑行走的三个“阶段”中的每一个访问的提交数量,如b454241(“revision.c:基于生成的拓扑顺序算法”,2018- 11-01,Git v2.20.0-rc0 -- merge)。


在 Git 2.32(2021 年第二季度)中,引入了一个新的配置变量,允许选择在提交图文件中使用哪个版本的世代号。

commit 702110acommit c7ef8fe(2021 年 2 月 25 日)Derrick Stolee (derrickstolee)
(由 Junio C Hamano -- gitster -- 合并到 commit d20fa3c,2021 年 3 月 22 日)

commit-graph:使用config指定生成类型

签字人:Derrick Stolee

我们有两个既定的代号版本:1:拓扑级别 2:更正的提交日期

默认情况下启用更正的提交日期,但它们也会在 GDATGDOV 块中写入额外数据。
托管 Git 数据的服务可能希望更好地控制此功能何时推出,而不仅仅是更新 Git 二进制文件。

添加一个新的“commitGraph.generationVersion”配置选项,用于指定预期的代号版本。
如果该值小于 2,则永远不会从现有文件中写入或读取 GDAT 块。

git config 现在包含在其man page 中:

commitGraph.generationVersion

指定写入时使用的代号版本类型 或阅读提交图文件。
如果指定版本 1,则 不会写入或读取更正的提交日期。

默认为 2。

【讨论】:

  • 所以回答这个问题,运行的命令还是git rev-list --all --topo-order | grep --file SET_OF_SHA1S,只是碰巧在Git 2.20中更快?
  • 是的,就是这样。
猜你喜欢
  • 2013-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-03
  • 2010-12-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多