如何列出所有标签,这些标签来自于提交,但没有包含在任何分支中?

3

我们有很多仓库,其中许多提交不包含在任何分支中,但仅因为标记而保持存活。我想列出所有这样的标记。还没有找到如何实现此目标的方法。是否有任何想法可以帮忙?

1个回答

4

标签并非来自提交,而只是指向提交。但是你的问题确实有答案——只是措辞有点奇怪。更准确的措辞将引导我们得出答案:

对于每个标签,如何测试标签所标识的提交是否包含在任何分支中?

因此,我们想要操作每个标签并进行一些测试。有一些命令可以枚举每个标签。对于脚本编写,git for-each-ref是最好的工具1。因此,我们从以下命令开始:

git for-each-ref refs/tags

打印出所有标签(以其标准输出)和每个标签目标的额外信息:
$ git for-each-ref refs/tags
04c6e9e9ca34226db095bbaa1218030f99f0b7c6 commit refs/tags/a
d5aef6e4d58cfe1549adef5b436f3ace984e8c86 tag    refs/tags/b

例如,a 标签直接跳转到一个提交,即轻量标签;b 标签跳转到一个注释标签对象。
这还不是一个解决方案,但它正在为我们做出贡献。下一步,我们想做的是找出注释标签的目标是否是提交对象,如果是,则找到提交对象的哈希值。原来 git for-each-ref 可以使用 --format 指令 %(*objecttype)%(*objectname) 来实现这一点。令人恼火的是,当标签是轻量标签时,这些 %(*...) 指令将产生 空值,这需要一些巧妙的处理:
git for-each-ref \
    --format='%(refname) %(objecttype) %(objectname) %(*objecttype) %(*objectname)' \
    refs/tags

为了方便发布,我将其分成多行; 在脚本中,我们可以只有一个长长的行而没有反斜杠换行序列。

它的输出结果是一系列每行三列或五列的内容。前三列是参考名称、对象类型(可能是“标签”)、初始标签哈希ID,如果最后两列存在,则是目标类型和最终目标ID。我们需要将这些输入到一个Shell脚本中:

git for-each-ref \
    --format='%(refname) %(objecttype) %(objectname) %(*objecttype) %(*objectname)' \
    refs/tags |
    while read name dtype dobj itype iobj; do
        ...
    done

现在,在 ... 段落内,我们实现了测试: 直接或间接对象是否为提交,如果是,是否可以被任何分支名称访问?

“对象是否为提交”的测试非常简单。但首先,让我们使用间接对象和名称(如果存在),否则使用直接对象和名称:

    if [ $dtype = tag ]; then
        otype=$itype obj=$iobj
    else
        otype=$dtype obj=$dobj
    fi

现在我们将跳过非提交对象:

    [ $otype == commit ] || continue

最后,我们将测试对象的哈希 ID 是否可从某个分支名称访问:
    n=$(git for-each-ref refs/heads --contains $obj | wc -l)

for-each-ref 打印每个到达给定对象的分支名称(以及像往常一样的其他数据,用于 for-each-ref)。我们不关心实际名称,只关心是否有任何名称,因此让我们计算内部 for-each-ref 打印了多少行。如果是零,则此标签保留此提交,因此让我们打印标签:
    if [ $n -eq 0 ]; then
        echo "tag $name keeps $obj alive"
    fi

当我们运行整段代码时,存在一个小缺陷:例如,它会打印出 tag refs/tags/a。通过在初始的 --format 中使用 %(refname:short),我们可以解决这个问题,并获得 tag a。因此,最终的脚本如下:
git for-each-ref --format='%(refname:short) %(objecttype) %(objectname) %(*objecttype) %(*objectname)' refs/tags |
while read name dtype dobj itype iobj; do
    if [ $dtype = tag ]; then
        otype=$itype obj=$iobj
    else
        otype=$dtype obj=$dobj
    fi
    [ $otype == commit ] || continue
    n=$(git for-each-ref refs/heads --contains $obj | wc -l)
    if [ $n -eq 0 ]; then
        echo "tag $name keeps $obj alive"
    fi
done

(我已将此添加到 GitHub 这里。该脚本可能需要改进,以便采用Git选项等,但是目前我并不那么在意。它也非常慢,可以通过编写其他不同于非常简单的Shell脚本的内容来改善,但请参见上述备注。)


1最佳是其中一件难以衡量的事情,但至少发现它是最好的。


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