views:

298

answers:

4

In a Unix or GNU scripting environment (e.g. a Linux distro, Cygwin, OSX), what is the best way to determine whether the current checkout is a Git tag. If it is a tag, how can I determine the tag name?

One use of this technique would be automatically labeling a release (like svnversion would do with Subversion).

See my related question about programmatically detecting the Git branch.

A: 

Here is a brief shell script (tested in Bash, not confirmed if it works on ash, etc.). It will set the git_tag variable to the name of the currently checked-out tag, or leave it blank if the checkout is not tagged.

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


Comment by Jakub Narębski:

This solution reduces to looping over all tags, and checking if they point to corrent commit, i.e. object pointed by HEAD. Using plumbing commands, i.e. commands meant for scripting, this can be written as:

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

This would print all tags that point to current commit.

jhs
Use "git rev-parse HEAD" to get SHA-1 of current commit, no need for complicated solution with git-log. Use git-for-each-ref instead of complicated solution with "git tag -l" and "git log" (and not even "git show"). Use "git describe" to answer otiginal question. Use GIT-VERSION-GEN to solve problem.
Jakub Narębski
A: 

A better solution (from Greg Hewgill's answer in the other question) would be:

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

If it returns "undefined" then you are not on a tag. Otherwise it returns the tag name. So a one-liner to do something like my other answer would be:

git_tag=`git name-rev --name-only --tags HEAD | sed 's/^undefined$//'`

Interactive shell example of how it works:

$ 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
jhs
This would not work correctly if HEAD is **reachable** from tag (e.g. via some other branch which is in advance to currently checked out branch), but it isn't on a tag itself. You would get result like: "some-tag~3"
Jakub Narębski
Also, to be nitpicky, there could be a tag named 'undefined'...
Jakub Narębski
+3  A: 

The best way to do this is to use the git describe command:

git-describe - Show the most recent tag that is reachable from a commit

The command finds the most recent tag that is reachable from a commit. If the tag points to the commit, then only the tag is shown. Otherwise, it suffixes the tag name with the number of additional commits on top of the tagged object and the abbreviated object name of the most recent commit.

Greg Hewgill
Thanks. git-describe wouldn't seem to be as easy to use in a shell script for this specific objective compared to git-name-rev.
jhs
I say that because, firstly git-describe errors out when run on a branch with no arguments. Would you recommend `git-describe --tags`? But more importantly it is not trivial to tell whether you are on a proper tag or not. You would have to parse the output and look for hyphens. But what if the tag name has hypens in it? For that reason I prefer `git name-ref --name-only --tags HEAD`
jhs
Fair enough, I suppose the best option depends on your particular application. I've used `git describe` in the past for automatically labeling a release.
Greg Hewgill
By default git-describe uses only **annotated** tags. And you can use `--exact-match` option to find only tags directly pointing at a commit.
Jakub Narębski
Greg, on second thought I like your solution when considering Jakub's suggestion of `--exact-match`. This way there is an error code if it's not a tag checkout. Unfortunately I may have to redirect stderr but you can't win them all!
jhs
+2  A: 

The solution to your question is to use

git describe --exact-match HEAD

(which would consider only annotated tags, but you should use annotated and probably even signed tags for tagging releases).

If you want to consider all tags, also lightweight tags (which are usually used for local tagging), you can use --tags option:

git describe --exact-match --tags HEAD


But I think you have "XY problem" here, in that you are asking question about possible solution to the problem, rather than asking question about a problem... which can have better solution.

The solution to your problem is to take a look how Git does it in GIT-VERSION-GEN script, and how it uses it in its Makefile.

Jakub Narębski
Jakub, with all due respect, your recommendations about my release process, and your speculation about my true problem, are both irrelevant. Thank you for your technical solution, however.
jhs
Unfortunately, your solution does not work for me. `git describe --exact-match HEAD` => `fatal: no tag exactly matches '...'. I believe you need to add `--tags` to the parameters.
jhs
@jhs: Wbout `git describe --exact-match HEAD` vs `git describe --exact-match --tags HEAD`: The `--tags` option would be needed only if you use lightweight tags. It is recommended to use signed tags (which are annotated tags, i.e. tag objects) to tag releases.
Jakub Narębski
@Jakub your comment brings up an interesting point, the value of annotated tags vs. lightweight tags. I came from Subversion; my current project is not public, has 1 primary developer (me) + occasional glances from other developers. But I learned Git before GitHub came out. So I never learned best-practices for annotated tags. They're not quite necessary for my project but useful to know about. Would you please explain both situations (--tags-only vs. normal) in your answer for the benefit of everybody and I'll accept it? Thanks!
jhs
@jhs: I can try to do that, but wouldn't it be better to have this as separate SO question, perhaps: "When to use lightweight and when annotated tags in git?" (or something like that)?
Jakub Narębski
Yes that is a valid point.
jhs