如何以编程方式确定Git检出是否为标签,如果是,则是哪个标签名称。

59
在Unix或GNU脚本环境中(例如Linux发行版,Cygwin,OSX),最佳方法是如何确定当前checkout是否为Git标签。如果是标签,如何确定标签名称?
其中一种使用此技术的方法是自动标记发布(例如像Subversion中的svnversion所做的操作)。
请参阅我关于编程检测Git分支的相关问题。

请查看 Git 仓库中的 GIT-VERSION-GEN 脚本(以及在 Makefile 中的使用):http://git.kernel.org/?p=git/git.git;a=blob;f=GIT-VERSION-GEN;hb=HEAD - Jakub Narębski
@jhs:我已经点赞了Greg Hewgill的回答。我已经对基于git name-rev的回答进行了反对,因为它可能会返回例如“some-tag〜5”的结果;以及对基于git loggit tag -l组合的回答进行了反对,因为它既难看又低效。 - Jakub Narębski
6个回答

80
你的问题的解决方案是使用。
git describe --exact-match HEAD

(这个命令将只考虑已注释的标签,但你应该使用已注释并且可能甚至是已签名的标签来标记发布。)

如果您想考虑所有标签,包括轻量级标签(通常用于本地标记),您可以使用--tags选项:

git describe --exact-match --tags HEAD

但我认为你在这里遇到了"XY问题",因为你问的是关于可能解决问题的方法,而不是询问问题本身...它可以有更好的解决方案。

解决你的问题的方法是看一下Git在GIT-VERSION-GEN脚本中如何实现,并查看它如何在Makefile中使用。


7
Jakub,恕我冒昧,你对我的发布流程的建议和你对我真正问题的猜测都不相关。不过,谢谢你提供的技术解决方案。 - JasonSmith
1
@jhs:关于 git describe --exact-match HEADgit describe --exact-match --tags HEAD,如果您使用轻量级标签,则需要使用 --tags 选项。建议使用带有签名的标签(即标签对象)来标记发布版本。 - Jakub Narębski
@Jakub 你的评论提出了一个有趣的问题,即带注释标签与轻量级标签的价值。我来自Subversion;我的当前项目不是公开的,只有1个主要开发人员(我),偶尔会有其他开发人员浏览。但我在GitHub发布之前就学会了Git。因此,我从未学习过带注释标签的最佳实践。它们对于我的项目并不是必须的,但了解它们很有用。你能否在你的答案中解释一下两种情况(--tags-only vs.正常)的区别,以便让大家受益?谢谢! - JasonSmith
@jhs:我可以尝试这样做,但是把它作为单独的SO问题会更好,比如:“何时在git中使用轻量级标签和注释标签?”(或类似的问题)? - Jakub Narębski
非常感谢,非常有用。 - lanni654321
显示剩余2条评论

10
更好的解决方案(来自Greg Hewgill在另一个问题的答案)是:

git name-rev --name-only --tags HEAD

如果返回“undefined”,则表示您不在标签上。否则,它将返回标签名称。因此,要执行类似于我的其他答案的操作,只需一行代码即可:
git_tag=`git name-rev --name-only --tags HEAD | sed 's/^undefined$//'`

交互式 shell 的工作示例:
$ git checkout master
Already on "master"
$ git name-rev --name-only --tags HEAD
undefined
$ git checkout some-tag
Note: moving to "some-tag" which isnt a local branch
If you want to create a new branch from this checkout, you may do so
(now or later) by using -b with the checkout command again. Example:
  git checkout -b <new_branch_name>
HEAD is now at 1234567... Some comment blah blah
$ git name-rev --name-only --tags HEAD
some-tag

2
如果HEAD可以从标签(例如通过某个比当前检出分支更先进的其他分支)到达,但它本身不在标签上,则此方法将无法正常工作。您将获得类似于“some-tag〜3”的结果。 - Jakub Narębski

8

git-name-rev是脚本中首选的使用方式,因为它是git基础设施的一部分,而git-describe则是外壳的一部分。

如果HEAD指向标签,请使用此命令打印标签的名称,否则不打印任何内容。

git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null | sed -n 's/^\([^^~]\{1,\}\)\(\^0\)\{0,1\}$/\1/p'

注意将标准错误重定向到/dev/null,否则您将收到一个错误消息,内容为:

fatal: cannot describe 'SOMESHA'"

编辑:已修复sed中的正则表达式,支持轻量级标签和注释/签名标签。


6
这个操作的最佳方式是使用git describe命令:

git-describe - 显示可从提交访问的最近标签

该命令查找可从提交访问的最近标签。如果标签指向提交,则仅显示标签。否则,它会将标签名称后缀为附加提交数和最近提交的缩写对象名称。


谢谢。相比于git-describe,git-name-rev在shell脚本中实现这个特定目标似乎不那么容易使用。 - JasonSmith
我之所以这么说,是因为首先在没有参数的情况下运行 git-describe 会出错。您是否建议使用 git-describe --tags? 但更重要的是,很难确定您是否处于正确的标签上。 您需要解析输出并查找连字符。但是,如果标签名称中有连字符怎么办?因此,出于这个原因,我更喜欢使用 git name-ref --name-only --tags HEAD - JasonSmith
说得对,我想最好的选择取决于你特定的应用程序。我过去使用过 git describe 来自动标记发布版本。 - Greg Hewgill
默认情况下,git-describe 仅使用 带注释的 标签。您可以使用 --exact-match 选项仅查找直接指向提交的标签。 - Jakub Narębski
Greg,仔细想想,在考虑Jakub的建议--exact-match时,我喜欢你的解决方案。这样如果不是标签检出就会有一个错误代码。不幸的是,我可能不得不重定向stderr,但你不能赢得所有的胜利! - JasonSmith

5
您无法确定当前的检出是否为“标签”。您只能确定当前检出是带有标签的提交。
区别在于:如果有多个标记指向此提交,git 无法告诉您使用了哪一个进行检出,也无法告诉您是否使用了其中任何一个来到达这里。 Jakub在这里的回答基于git describe --exact-match(--tags)给出了所有(带注释的)标记中的“第一个”
git describe会将它们排序:
  • 首先是带注释的标记
    • 按年龄从小到大排序
  • 然后是轻量级标记
    • 按标记名称排序(这意味着如果以ASCII编码的英文,则按字母顺序排列)
    • git 不会存储轻量级标记的元数据,因此无法实现“最新的优先”

-2
这是一个简短的 shell 脚本(在 Bash 中测试过,但不确定它是否适用于 ash 等)。它将把 git_tag 变量设置为当前检出标签的名称,如果检出未被标记,则将其留空。
git_tag=''
this_commit=`git log --pretty=format:%H%n HEAD^..`

for tag in `git tag -l`; do
  tag_commit=`git log --pretty=format:%H%n tags/$tag^..tags/$tag`
  if [ "$this_commit" = "$tag_commit" ]; then
    # This is a tagged commit, so use the tag.
    git_tag="$tag"
  fi
done

Jakub Narębski的评论:

这个解决方案可以简化为循环遍历所有标签,并检查它们是否指向正确的提交,即HEAD所指向的对象。使用plumbing命令,也就是用于脚本编写的命令,可以将其编写为:

this_commit=$(git rev-parse --verify HEAD)
git for-each-ref --format="%(*objectname) %(refname:short)" refs/tags/ |
while read tagged_object tagname
do
    if test "$this_commit" = "$tagged_object"
    then
        echo $tagname
    fi
done

这将打印指向当前提交的所有标签。


使用 "git rev-parse HEAD" 获取当前提交的SHA-1,无需使用 git-log 的复杂解决方案。使用 git-for-each-ref 替代“git tag -l”和“git log”的复杂解决方案(甚至不是“git show”)。使用 "git describe" 来回答原始问题。使用 GIT-VERSION-GEN 解决问题。 - Jakub Narębski

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