1

What I'm trying to do, is create a pre-push hook that requires a tag be pointing to the same commit as the current HEAD. We're using git in a slightly unusual way, and there's no situations where the latest commit should not have a tag. I have a working implementation of this, but found that it only works with lightweight tags.

head=$(git rev-parse HEAD)
last_tag=$(git rev-parse $(git describe --tags))
if [ "$head" != "$last_tag" ]
then
    echo >&2 'Aborting push - there is no tag on the latest commit.'
    exit 1
fi

The problem I've found is that even after setting push.followTags, lightweight tags are ignored. It's important to me that tags be pushed and pulled with no extra steps, since we'll be using them pretty heavily.

To work around this issue, we can use annotated tags. The issue is that an annotated tag has it's own hash that's returned by git rev-parse. I'm unable to find a way to get the hash of the commit the tag is linked to. I've tried both

git rev-parse tagname^
git rev-parse $(git rev-parse tagname)^

Any ideas how this can be done, or if there's another better option?

iCodeSometime
  • 1,444
  • 15
  • 30

1 Answers1

1

I am going to close this as a duplicate, but the accepted answer at the duplicate is not necessarily the best way.

The best way is to use git rev-parse tagname^{} or git rev-parse tagname^{commit}.

The key difference between these is that the former follows the tag to some non-tag object—maybe a commit, but maybe a tree or blob—while the latter follows the tag to a commit. If the tag does not point to a commit (directly or indirectly), the second form produces an error message (to stderr) and a nonzero status:

hash=$(git rev-parse ${tag}^{commit}) || exit

Or, if you don't mind if $tag names a tree or blob object:

hash=$(git rev-parse ${tag}^{}) || exit

The git log method will produce no output (and exit successfully) when given a tag that points to a tree or blob:

$ git tag -a foo -m test HEAD^{tree}
$ git log foo
$ git rev-parse foo
98769d20d108a98555aafab76b0e3b84a3719779
$ git rev-parse foo^{}
f7a4925fb621cdef69d7dec49159c13cfc6aa789
$ git rev-parse foo^{commit}
error: foo^{commit}: expected commit type, but the object dereferences to tree type
foo^{commit}
error: foo^{commit}: expected commit type, but the object dereferences to tree type
fatal: ambiguous argument 'foo^{commit}': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
$ $ git tag -d foo
Deleted tag 'foo' (was 98769d20d1)

By using the ^{} or ^{commit} notation, you can choose the behavior you want.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks! This ended up not working quite like I'd expected either, because of the way it combines with git-describe. Apparently, when the tag isn't on the latest commit, describe returns something in the form of `--` which when used with `git rev-parse` always returns the current commit. I ended up using `git describe --exact-match` instead :) – iCodeSometime Jan 23 '19 at 15:44
  • @kennycoc: yes, `git describe` produces by default something that's not just an annotated tag. However, `git rev-parse` should turn that into a valid hash ID. You can append `^{}` or `^{commit}` as usual, either before or after turning the describe output into a hash. See [gitrevisions](https://git-scm.com/docs/gitrevisions). – torek Jan 23 '19 at 16:30