git name-rev是做什么的?

s3fp2yjn  于 2023-02-28  发布在  Git
关注(0)|答案(4)|浏览(197)

根据git name-rev doc
查找适合人类理解的符号名,用于git rev-parse可以解析的任何格式的修订。
但是我不明白,这个命令有什么用?它和git describe命令有什么区别?我想both也做同样的事情-给一个SHA1 Id,给我们返回一个最接近它的引用名?

1sbrub3j

1sbrub3j1#

Marek R's answer是正确的,但有点不完整。
本质上,git name-rev会产生一个名称加上一些相对表达式(如果需要的话),以便从名称移动到提交,而git describe会产生一些名称--通常是标记名,但也可能是其他名称--加上一些额外的字符串(如果需要的话),这并不是特别"相对"的:在某些情况下有一个计数,但它不如git name-rev中的计数有用。如果git describe不能使用原始名称,它会添加g和一个缩写的哈希ID:

$ git describe
v2.19.1-272-gf84b9b09d4

但是:

$ git name-rev HEAD
HEAD master

如果我们将--all添加到git describe的参数中,git describe确实使用分支名称,但输出不同:

$ git describe --all
heads/master

它特别注意到它使用的是分支名称,因为如果分支名称和标记名称之间存在冲突(如果也存在refs/tags/master),单独解析master将生成与标记关联的散列ID,而不是分支名称。
除此之外,git describe还可以在工作树与提交不匹配时添加-dirty,并且自Git 2.16.0以来,git describe可以生成一个name:pathname字符串来给你一个表达式来命名一个特定的已存储blob:

$ git describe HEAD:Makefile
v2.19.0-237-ge3d4ff037d:Makefile

这不是git name-rev所能处理的。

目的不同

[git name-rev]有什么用?
我自己从来没有见过有人使用它,但请看下面。让我们从git describe的用途开始:git describe通常用于生成对特定构建的有用描述。因为它默认只使用 * tags *,所以它的输出相对稳定(如果我们假设标记永远不会改变,它就是稳定的)。当git describe表示v2.19.1-272-gf84b9b09d4时,我们知道:

  • 提交发生在v2.19.1之后
  • 实际提交者散列ID以f84b9b09d4开始

并且同一提交的未来git describe输出很可能相同(尽管如果我们添加新的注解标签,它们可能会改变)。通过v2.19.1..f84b9b09d4可达的272个提交的计数:

$ git rev-list --count v2.19.1..f84b9b09d4
272

运行git rev-parse v2.19.1-272-gf84b9b09d4将始终产生f84b9b09d40408cf91bbc500d9f190a7866c3e0f,无论我明天、下周还是明年再做一次,只要标签v2.19.1保持在原来的位置。如果我们使用此字符串作为构建标识符,并且避免使用分支名称并小心地注意构建是否"脏",我们可以立即判断出是否可以在以后轻松地再次复制这个相同的构建,如果可以,如何复制(例如,通过运行git checkout v2.19.1-272-gf84b9b09d4)。
另一方面,当git name-revmastermaster~3或其他什么的时候,不能保证我明天能回到那个仓库,用同样的表达式找到同样的提交。明年,几乎可以保证它是 * 错误的 *。所以git name-rev的输出只在一小段时间内有效-本质上,直到你移动了一些分支名称。
同时,git name-rev有一个git describe没有的技巧:它将解析其标准输入,查找看起来像是哈希ID的内容。当它找到这些内容时,它将把它们复制到标准输出,并添加 (*expression*),前提是哈希转换为名称:

$ X=$(git rev-parse master)
$ echo embedded $X hash | git name-rev --stdin
embedded f84b9b09d40408cf91bbc500d9f190a7866c3e0f (master) hash
$ Y=$(echo $X | sed s/f/0/)
$ echo embedded invalid $Y hash | git name-rev --stdin
embedded invalid 084b9b09d40408cf91bbc500d9f190a7866c3e0f hash

这意味着,如文档所示,你可以运行git log | git name-rev,并让所有这些提交哈希成为修饰哈希,但是哈希必须是完全哈希。

$ git log --oneline -n 10 | git name-rev --stdin
f84b9b09d4 Sync with 2.19.1
cae598d998 Git 2.19.1
1958ad504b Sync with 2.18.1
268fbcd172 Git 2.18.1
44f87dac99 Sync with 2.17.2
6e9e91e9ca Git 2.17.2
1a7fd1fb29 fsck: detect submodule paths starting with dash
a124133e1e fsck: detect submodule urls starting with dash
e43aab778c Sync with 2.16.5
27d05d1a1a Git 2.16.5

与:

$ git log --pretty=tformat:'%H %s' -n 10 | git name-rev --stdin
f84b9b09d40408cf91bbc500d9f190a7866c3e0f (master) Sync with 2.19.1
cae598d9980661a978e2df4fb338518f7bf09572 (tags/v2.19.1^0) Git 2.19.1
1958ad504befa6a09c475cc8ab9de43b359de137 (tags/v2.19.1~1) Sync with 2.18.1
268fbcd172cdb306e8a3e7143cc16677c963d6cd (tags/v2.18.1^0) Git 2.18.1
44f87dac99574a8073ffb1ba8b10bd4d3945f61b (tags/v2.18.1~1) Sync with 2.17.2
6e9e91e9cae74cd7feb9300563d40361b2b17dd2 (tags/v2.17.2^0) Git 2.17.2
1a7fd1fb2998002da6e9ff2ee46e1bdd25ee8404 (tags/v2.17.2~1) fsck: detect submodule paths starting with dash
a124133e1e6ab5c7a9fef6d0e6bcb084e3455b46 (tags/v2.17.2~2) fsck: detect submodule urls starting with dash
e43aab778c72250e11eb00e31dc6be90072a1637 (tags/v2.17.2~3) Sync with 2.16.5
27d05d1a1a62273aa3749f4d0ab8a126ef11ff66 (tags/v2.16.5^0) Git 2.16.5

底线:使用最适合你特定目的的那一种。2哪一种取决于你的目的是什么。

qpgpyjmq

qpgpyjmq2#

在Git 2.25(2020年第一季度)中,你可以看到一个关于"git name-rev"功能的图解。
请参见第一版电子文件第一版、第一版电子文件第二版、第一版电子文件第三版(2019年12月9日)和第一版电子文件第四版、第一版电子文件第五版、第一版电子文件第六版、第一版电子文件第七版、第一版电子文件第八版、第一版电子文件第九版、第一版电子文件第十版、第一版电子文件第十一版、第一版电子文件第十二版、第一版电子文件第十三版(2019年11月12日)。
参见René Scharfe (``)commit c3794d4(2019年11月12日)。
(由Junio C Hamano -- gitster --合并至commit f3c520e,2019年12月25日)

t6120:添加测试以涵盖'git name-rev's name_rev()中的内部条件

签署人:塞德尔·加博尔
name_rev()函数的'builtin/name-rev.c'中,有一个循环迭代给定提交的所有父提交,循环体如下所示:

if (parent_number > 1) {
    if (generation > 0)
        // branch #1
        new_name = ...
    else
        // branch #2
        new_name = ...
    name_rev(parent, new_name, ...);
} else {
    // branch #3
    name_rev(...);
}

测试套件中未适当涵盖这些条件。
就纯粹的测试覆盖率而言,它们都在"t6120-describe.sh"中执行了几次。
然而,它们并不直接影响命令的输出,因为测试脚本中使用的存储库包含了几个分支和标签,它们指向提交DAG的中间位置,从而为要命名的提交提供了一个更好的名称。
这可以隐藏错误:例如,通过将第一个递归name_rev()调用的'new_name'参数替换为'tip_name'(有效地使分支#1#2都成为noop)'git name-rev --all'在Git存储库中显示了数千个假名称,但整个测试套件仍然成功通过。
在本系列后期补丁的早期版本中,我设法(一次!)把所有三个分支都搞砸了,但是测试套件仍然通过了。
因此,添加一个新的测试用例,在以下历史上运行:

A--------------master
 \            /
  \----------M2
   \        /
    \---M1-C
     \ /
      B

并将提交命名为"B"以确保所有三个分支对于确定"B"的名称都是至关重要的:

  • 只有一个引用,所以所有的名字都基于"master",没有任何来自其他引用的干扰。
  • 每次name_rev()跟随合并提交的第二个父提交时,它都会在名称后面追加"^2"。在开始处跟随'master的第二个父提交可以确保从' master '到' B '的祖先路径上的所有提交都具有与第一个name_rev()调用的原始' tip_name '不同的基本名称。

目前,虽然name_rev()是递归的,但这并不重要,但是在本系列后面的部分中消除递归之后,有必要正确地涵盖所有三个分支。

  • 跟随"M2"的第二个父分支确保分支#2(即"generation = 0")影响"B"的名称。
  • 跟随非合并提交"C"的唯一父提交确保分支#3影响"B"的名称,并且它递增"generation."
  • 来自"C"的"generation"是1,因此跟随"M1"的第二个父分支确保分支#1影响"B"的名称。

在Git 2.25(Q1 2020)中,"git name-rev"的目标是避免递归调用。

name-rev:消除name_rev()中的递归

签署人:塞德尔·加博尔

    • name_rev()函数会递归地调用它作为参数获得的提交的每个感兴趣的父提交,因此,如果它耗尽了可用的堆栈空间,它可能会在处理深度历史时出现segfault**。

例如,在gcc、gecko-dev、llvm和WebKit存储库中运行'git name-rev--all'和'git name-rev HEAD~100000'会在我的机器上导致segfault('ulimit -s'报告堆栈大小限制为8192kB),现在Linux repo中也会出现以前的segfault(它在v5.3-rc4和-rc5之间达到了必要的深度)。
通过将感兴趣的父级插入到LIFO 'prio_queue' 1中并迭代直到队列变为空,来消除递归。
注意,父提交必须以相反的顺序添加到LIFO 'prio_queue',因此它们的相对顺序在处理期间被保留,即第一个父提交应该首先从队列中出来,因为否则合并历史2上的性能会受到很大影响.
"t6120-describe.sh"中的stacksize-limited测试"name-rev works in a deep repo"证明了此问题和预期故障。
现在递归已经完成,因此翻转它以期待成功。
同样消失的还有dmesg条目,这些条目记录了测试套件每次执行时分段"git name-rev"进程的分段错误。
注意,这稍微改变了'git name-rev --all'输出中的行顺序,通常在git.git中每35行交换两行,在linux.git中每150行交换两行。
这在实践中应该不重要,因为无论如何输出总是无序的。
此修补程序最好使用"--ignore-all-space"查看。
1.此修补程序的早期版本使用"commit_list",导致"linux.git"中的"git name-rev --all"性能下降约15%,可能是因为每次插入和删除时的内存分配和释放。
使用LIFO 'prio_queue'基本上对性能没有影响。
1.我们更喜欢较短的名字,例如'v0.1~234'比'v0.1^2~5'更受欢迎,这意味着通常跟在合并的第一个父代后面会得到其祖先的最佳名称。

因此,当我们跟随merge的其余父提交,到达一个已经命名的提交时,我们通常会发现我们无法给这个提交一个更好的名字,因此我们不必再次访问它的任何祖先。
还有,如果我们首先跟随merge的第N个父节点,那么它所有祖先的名称都将包括相应的"^N".
这些不是那些提交的最佳名称,所以当我们在合并的第一个父提交之后到达一个已经命名的提交时,我们必须更新那个提交的名称以及它所有祖先的名称。
因此,我们将不得不多次访问许多提交,从而导致速度显著减慢。
随着Git 2.26(2020年第一季度)的发布,"git name-rev"的内存占用和性能都得到了提升。
这为了解git name-rev的功能提供了额外的帮助。
请参见第一版第30页(2020年2月5日)和第一版第31页、第一版第32页、第一版第33页、第一版第34页、第一版第35页、第一版第36页、第一版第37页、第一版第38页、第一版第39页(2020年2月4日)。
参见Martin Ågren (``)commit 3e2feb0(2020年2月5日)。
(由Junio C Hamano -- gitster --合并到commit 0460c10,2020年2月17日)

name-rev:应用前排序提示名称

签署人:勒内·沙夫
name_ref()会为每个ref调用,并检查它是否是所引用提交的更好名称。
如果是这样的话,它会记住它,并检查基于它的名字是否也适合它的祖先。
这是按照for_each_ref()强加给我们的顺序完成的。
这可能不是最佳选择。
如果碰巧首先遇到了坏名字(如is_better_name()所定义的),那么从它们派生的名字可能会扩散到很多提交中,只是后来被更好的名字所取代。
首先设置好的名称可以避免这种情况。
is_better_name()首选标记、短距离和旧引用。
距离是我们需要为每个候选提交计算的度量,但其他两个属性不依赖于提交的关系。
按引用排序应该比我们目前使用的基本上随机的顺序产生更好的性能。
首先应用旧的引用也有助于减少返工,因为旧的提交比新的提交有更少的祖先。
因此,首先将名称的所有详细信息添加到tip表中,然后将它们排序为prefer标记和较旧的引用,然后按此顺序应用它们。以下是hyperfine在之前的Linux repo中测量的性能:

Benchmark #1: ./git -C ../linux/ [`git name-rev --all`](https://git-scm.com/docs/git-name-rev#Documentation/git-name-rev.txt---all)
Time (mean ± σ):     851.1 ms ±   4.5 ms    [User: 806.7 ms, System: 44.4 ms]
Range (min … max):   845.9 ms … 859.5 ms    10 runs

...使用此修补程序:

Benchmark #1: ./git -C ../linux/ [`git name-rev --all`](https://git-scm.com/docs/git-name-rev#Documentation/git-name-rev.txt---all)
Time (mean ± σ):     736.2 ms ±   8.7 ms    [User: 688.4 ms, System: 47.5 ms]
Range (min … max):   726.0 ms … 755.2 ms    10 runs

在Git 2.35(2022年第一季度)中,"git name-rev"(man)经过了调整,使输出内容更短,更容易理解
参见Elijah Newren ( newren )commit 3656f84(2021年12月4日)。
(2021年12月21日由Junio C Hamano -- gitster --合并到commit 3f9d505中)

name-rev:与下列合并相比,我更喜欢较短的名称

签署人:伊莱贾·纽伦
确认人:埃瓦尔·阿恩菲约德·比贾马森
确认人:Johannes Schindelin
name-rev具有MERGE_TRAVERSAL_WEIGHT,表示遍历合并的第二个或更晚的父节点应该比遍历第一个父节点花费65535倍,按照ac076c2("name-rev:修复非最短描述",2007年8月27日,Git v1.5.3-rc7--merge)。
这个权重的意义在于喜欢这样的名字

v2.32.0~1471^2

而不是像

v2.32.0~43^2~15^2~11^2~20^2~31^2

它们是git.git中用于同一提交的两个同等有效的名称。
注意,第一个遍历跟随1472个父遍历,而第二个遍历仅跟随125个父遍历。
对所有遍历进行同等加权显然会更喜欢第二个名字,因为它的父遍历更少,但人类不会遍历提交,他们倾向于更容易理解具有更少片段的名字。
前者只有两个段(~1471^2),这一事实使得它比后者简单得多,后者有六个段(~43^2~15等)。
由于name-rev的意思是"找到适合人类消化的符号名称",我们更喜欢更少的片段。
但是,在name-rev中实现的特定规则实际上更倾向于

v2.33.0-rc0~11^2~1

超过

v2.33.0-rc0~20^2

因为两者都恰好具有一个第二父遍历,并且它使平局决胜器具有最短数目的总父遍历。
对于人类消费来说,更少的部分比啤酒花的数量更重要,所以我们宁愿看到后者少一个部分。
is_better_name()中包含生成并使用新的effective_distance()计算,以便我们更喜欢打印名称中的更少段,而不是为获得答案而执行的更少总父遍历。
警告:在Git 2.40(2023年第一季度)中,"git name-rev"(man)有一个启发式更新。
参见Elijah Newren ( newren )commit b2182a8(2023年2月9日)。
(由Junio C Hamano -- gitster --合并到commit 5fc6d00,2023年2月22日)

name-rev:通过删除taggerdate修复名称变通方法

签署人:伊莱贾·纽伦
提交7550424("name-rev:在考虑最佳名字时包含标签日期",2016 - 04 - 22,Git v2.9.0-rc0--merge(列于batch #9中)介绍了在选择最佳名字的标准中使用标签日期的想法。
当时,linux.git中的某个提交--即aed06b9cfcab--被命名为name-rev

v4.6-rc1~9^2~792

虽然是正确的,但不是最理想的。

一些调查发现,调整MERGE_TRAVERSAL_WEIGHT以降低该值可能会给予其他答案,例如

v3.13-rc7~9^2~14^2~42

v3.13~5^2~4^2~2^2~1^2~42

一个涉及查看标签日期的手动解决方案产生了

v3.13-rc1~65^2^2~42

这样就好多了。
该变通方案随后在name-rev中实现。
不幸的是,taggerdate启发式算法会导致bug。
有人向我指出了一个私有存储库中的案例,其中name-rev报告了表单的名称

v2022.10.02~86

当用户期望看到其中一个表单时

v2022.10.01~2

(我对真实的测试用例中的名称和编号做了一些修改。)
正如您可能猜到的,v2022.10.01是在v2022.10.02之后创建的(晚了几个小时),尽管它指向一个更早的提交。
尽管这种情况即使在相关的存储库中也不常见,但它并不是该存储库中唯一有问题的标记集。
taggerdate逻辑导致问题。
此外,事实证明,这种taggerdate启发式甚至不再有帮助。
由于修复了3656f84中的命名逻辑(“name-rev:prefer short names over following merges”,2021-12-04,Git v2.35.0-rc 0--merge listed in batch #4),我们得到了改进的名字,而没有标签日期启发式。
对于linux.git中的初始提交,没有taggerdate启发式的现代Git仍然提供相同的最优答案,即:

v3.13-rc1~65^2^2~42

因此,标签日期不再提供好处,它正在造成问题。
干脆把它扔掉。
但是,请注意,“taggerdate”作为一个变量,目前用于存储taggerdate之外的内容。
自从commit ef1e740(“name-rev:favour descripting with tags and use committer date to tiebreak”,2017-03-29,Git v2. 14. 0-rc 0--merge listed in batch #4),它被用于存储提交者日期,并在那里被用作一个回退的决胜器(相对于覆盖有效距离计算的主要标准)。
我们不希望删除该备用附加断路器,因此在此更改中不会删除“taggerdate“的所有示例。

bis0qfac

bis0qfac3#

文件很清楚。
它只使用人类可读的符号,如分支名和标记名,来查找/生成引用给定提交的表达式。
例如,在我的存储库中:

$ git name-rev 91faf1b82d2dcedf8098a6e571ef379b29a44f51
91faf1b82d2dcedf8098a6e571ef379b29a44f51 develop^2~2

我提供了一个没有被标记或直接指向分支的提交。正如你所看到的,这个提交包含在分支develop中。要从develop到达这个提交,你必须使用第二个父提交和两个返回提交。
我建议:试着在不同的提交上使用这个命令,看看结果。

2ic8powd

2ic8powd4#

在本修复的示例部分,您可以看到另一个使用Git 2.36(Q2 2022)的git name-rev的示例:
"git name-rev --stdin"(man)的行为与通常的--stdin完全不同。
开始将其重命名为--annotate-stdin的过程。
参见commit a258571commit 34ae3b7(2022年1月5日)和John Cai ( john-cai )
(由Junio C Hamano -- gitster --合并至commit d9976b1,2022年2月9日)

name-rev:弃用--标准输入,而使用--注解标准输入

签署人:"蔡约翰"
引入一个--annotate-stdin,其功能等同于--stdin。
--stdin在其他子命令中的行为与--stdin不同,例如pack-objects,它每行使用一个参数。
由于--stdin可能是一个容易混淆和误导的名称,因此将其重命名为--annotate-stdin。
此变更向--stdin添加了一个警告,警告其将来将被删除。
git name-rev现在在其手册页中包括:

--annotate-stdin

git name-rev现在在其手册页中包括:
全部。
例如:

$ cat sample.txt

An abbreviated revision 2ae0a9cb82 will not be substituted.
The full name after substitution is 2ae0a9cb8298185a94e5998086f380a355dd8907,
while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad

$ git name-rev --annotate-stdin <sample.txt

An abbreviated revision 2ae0a9cb82 will not be substituted.
The full name after substitution is 2ae0a9cb8298185a94e5998086f380a355dd8907 (master),
while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad

$ git name-rev --name-only --annotate-stdin <sample.txt

An abbreviated revision 2ae0a9cb82 will not be substituted.
The full name after substitution is master,
while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad

--stdin

不推荐使用此选项,而应使用"git name-rev --annotate-stdin"。
它们在功能上等同。
Git 2.36(2022年第二季度)也为"name-rev --annotate-stdin"添加了收尾工作。
参见John Cai ( john-cai )commit d271892(2022年2月15日)。
(由Junio C Hamano -- gitster --合并到commit 294f296,2022年2月25日)

name-rev:将概要中的--stdin替换为--annotate-stdin

签署人:蔡约翰
34ae3b7("name-rev:弃用--stdin而支持--annotate-stdin ",2022 - 01 - 05,Git v2.36.0--batch #2中列出的merge)添加了--annotate-stdin以取代--stdin作为更清晰的标志名称。
由于--stdin将被弃用,因此我们应该在git name-rev -h "(man)"的输出中替换--stdin
在Git 2.36(Q2 2022)中,"git name-rev"(man)学习了在设置用于解释修订版本的搜索提交的下限时使用世代号,而不是提交时间。
参见commit 2e8ea40(2022年3月11日),作者为Jacob Keller ( jacob-keller )
(由Junio C Hamano -- gitster --合并至commit 94cb657,2022年3月23日)

name-rev:使用世代号(如果可用)

签署人:雅各布·凯勒
如果线性历史序列中的提交具有非monotonically递增的提交时间戳,则git name-revman)可能无法正确命名该提交。
发生这种情况是因为name-rev使用提交日期的启发式来避免搜索向下标签,这会导致比命名提交更早的提交。
这是为了避免在更大的存储库上工作。
这个启发式算法影响了git name-rev,并通过扩展影响了构建在name-rev之上的git describe --contains(man)。
此外,如果使用--all--annotate-stdin,则不启用试探法,因为无论如何都必须分析完整历史。
如果用户看到--annotate-stdin可以工作,而普通的name-rev不能工作,这会导致一些混乱。
如果仓库有提交图,我们可以使用世代号来代替提交日期。
这基本上是相同的检查,只是代编号使其精确,其中提交日期启发式可能由于时钟错误而不正确。

相关问题