如何在git中找到最近的提交前的标签?

7

我正在尝试查找特定分支中提交后最近的标签。

我知道git describe会向后遍历并找到最近的标签祖先,但我该如何朝相反的方向移动。

例如:

Commit History

给定提交2,我希望返回未来标签。而git describe将返回过去的标签。

你是怎么尝试的?你在写什么样的脚本?这个脚本使用的是哪种语言/平台? - PatrickSteele
4个回答

14

试试这个命令:git describe --contains <commit 2> | cut -d'~' -f1。这将以提交次数的方式将其与标签关联,仅取~之前的部分作为标签名称(假设您的标签名称不包含~)。


@torek为什么我们要这样做?你对无注释标签有什么意见吗?而且,OP也没有说他只想要有注释的标签。 - Vampire
如果你像这样使用 git describe --contains <commit 2> | rev | cut -d'~' -f2- | rev,你甚至可以在标签名称中包含波浪符。但是,对于我来说,git 不允许在标签名称中使用波浪符。 - Vampire
1
@Vampire:默认情况下,git describe 仅使用带注释的标签。我并不完全同意 git tag 文档中的理由,但它说(引用):“带注释的标签适用于发布,而轻量级标签适用于私有或临时对象标签。因此,一些命名对象的 gitt 命令(如 git describe)默认情况下将忽略轻量级标签。” - torek
@Vampire:波浪号是禁用的(以及空格,冒号和脱字符等各种特殊情况):请参阅https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html。 - torek
1
嗯,OP一开始谈到了git describe,但没有提到要使用--tags选项运行它。所以至少值得注意一下。 - torek
显示剩余3条评论

3

提交记录的箭头指向相反的方向。提交1不指向提交2,相反,提交2指向提交1。这很重要,因为...

在您绘制的图中,您只绘制了四个提交记录,没有分支或合并。也就是说,您绘制了以下内容:

A <- B <- C <- D   <-- branch-tip

(我已经从数字转换为字母,因为有26个字母和只有十个一位数字,而且我根本没有使用所有的十个一位数字....) 如果你在提交 B,那么回到提交 A 就非常容易:箭头指向那个方向。相反地,要往另一个方向走是很难的——事实上,没有辅助就不可能。

我们需要的关键帮助是一个稍后提交的标识或定位器。例如,使用名称branch-tip,我们可以找到提交D。从 D 开始,我们可以返回 C,然后返回 B,现在我们已经追踪到了一个可用的路径。

但这个图太简单了。让我们画一个带有分支的图。由于我们知道 Git 的箭头都是向后的,所以不必画内部箭头,只需使用连接线即可,当我们水平绘制时,只能向左移动。还有,让我们加入您的标签,再加上一些其他的:

tag:past
  |
  |   tag:future
  |     | tag:distantfuture
  v     v   v
  A--B--C---D   <-- branch1
      \
       E--F     <-- branch2
       ^  ^
       |  |
       | tag:future3
       |
   tag:future2

现在你想要为提交B找到哪个未来标签,是future还是future2? 我们知道你既不想要future3也不想要,但是future2future都只有一步之遥离B
关键是它们在某个方向上只有一步:朝branch1的尖端或朝branch2的尖端。您可能希望选择一个方向,您可以通过选择一个标识符来帮助Git在第一次找到提交DF时找到最终返回B的提交。
如果您确实想要控制方向,您将需要使用git rev-list --ancestry-path查找作为B的后代但是给定分支尖端的祖先(不包括B本身并包括分支尖端)的提交。
然后,将此提交列表与Vampire's answer之类的内容组合,以便仅查看指向此祖先路径上的提交的标签。也就是说,从“包含提交B的标签列表”中删除指向不在git rev-list输出中的提交的标签。
请注意,这无法解决具有合并和分支的图形中出现的问题:
     C--D
    /    \
A--B      G--H   <-- branch-tip
    \    /
     E--F

在这里,如果有指向提交DF的标签,则两者与B等距离,但两者也在祖先路径上,导致从BH
请注意,Vampire's answer简单地使用所有标签作为定位后续提交的方式,这些提交可以返回到B,使用--contains将列表限制为最终会返回到B的标签。然后,rev-list --count方法找到它们的“距离”。但是,如果图形中存在合并,则此计数可能有些错误。例如,在这个最后的图形中,有一圈提交,“距离”从HB实际上是四个;但是从H返回到B的路径中有六个提交。只是其中两个——C-DE-F——可以同时遍历。
(注意:如果您正在使用--ancestry-path将测试限制为指向某个特定图形路径内的提交的标签,则git rev-list --count度量中的轻微缺陷不会有影响,因为相同的计数缺陷将适用于所有模糊性。只有在您没有使用--ancestry-path的情况下才会出现问题:在这种情况下,包含合并提交的标签可能产生比不包含合并提交的标签更高的计数,但实际上更接近提交B。)

1
git tag --contains <commit 2> | xargs -I {} sh -c 'echo "$(git rev-list --count <commit 2>..{}) {}"' | sort -n | head -n 1 | cut -d ' ' -f 2-

最近的位置是由提交数量决定的。如果有多个位置距离相同,则不能保证您会得到哪一个位置。如果您想要所有距离最近的位置,从管道中删除调用即可。
注意:如果在提交后分支并合并这些分支,并且在合并之后有标记,则在合并之前和之后的提交将共同贡献于提交数量和距离,此时可能无法实现最优化结果。

不是所有的 xargs 命令都有 -i:你可能想要 POSIX-y 的 -I-J-R,并添加 -n 1 使其每行运行一次。或者只需将其管道传输到 while read ... 循环中。 :-) - torek
@torek -J-R分别指什么?我在POSIX xargs man页中没有找到它们,即使是在我的xargs man页中也没有。那为什么我要加上-n 1呢?在POSIX版本中,-I已经默认做了这个操作。 - Vampire
嗯,也许它们是BSD/MacOS特有的。 -I 显然只进行了五次替换,所以 -R 设置了数量。 -I 还设置了 -x,这也很恼人,所以BSD的 -J 就像 -I 但不会出现恼人的情况。 :-) BSD man页面声称 -J 也不意味着 -n 1,虽然我不确定这是如何工作的。...啊,例子说明得很清楚,是的,-J 不意味着 -n 1。无论如何,5已经足够了,所以唯一的烦恼就是暗示的 -x - torek

0

查找最近的已注释标签:

#!/usr/bin/env python3

import fileinput
import subprocess
import sys

# Usage: script-name <commit sha1>

tag = sys.argv[1]
lightweight = True
while lightweight:
    describe = subprocess.check_output(["git", "describe", "--exclude", tag, "--contains", tag])
    tag = describe.partition(b"~")[0]
    lightweight = subprocess.check_output(["git", "cat-file", "-t", tag]) == b"commit\n"
print(tag.decode())

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接