ereOn
ereOn

Reputation: 55826

Is there a way to check that a git tag matches the content of the corresponding commit?

In the company I work for, some projects have an project.info file which contains the current version of the program/library/whatever.

Actually, when someone wants to tag a version, he must first ensure that the project.info file (which is versionned) is up-to-date and contains the same version than the name of the tag he is about to create. No need to say that this is error prone.

We work on a clients-server workflow for git (all commits go to the same central repository) so I wonder: is there a way (a hook perhaps ?) to make this central repository refuse tags for which the project.info does not match ?

What should I look for to get started ?

Thank you very much.

Upvotes: 2

Views: 4533

Answers (2)

ereOn
ereOn

Reputation: 55826

Thanks to all the advices here, I succeeded.

Here is my final update hook script:

#!/bin/sh
#
# An example hook script to blocks unannotated tags from entering.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# To enable this hook, rename this file to "update".
#
# Config
# ------
# hooks.allowunannotated
#   This boolean sets whether unannotated tags will be allowed into the
#   repository.  By default they won't be.
# hooks.allowdeletetag
#   This boolean sets whether deleting tags will be allowed in the
#   repository.  By default they won't be.
# hooks.allowmodifytag
#   This boolean sets whether a tag may be modified after creation. By default
#   it won't be.
# hooks.allowdeletebranch
#   This boolean sets whether deleting branches will be allowed in the
#   repository.  By default they won't be.
# hooks.denycreatebranch
#   This boolean sets whether remotely creating branches will be denied
#   in the repository.  By default this is allowed.
#

# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"

# --- Safety check
if [ -z "$GIT_DIR" ]; then
    echo "Don't run this script from the command line." >&2
    echo " (if you want, you could supply GIT_DIR then run" >&2
    echo "  $0 <ref> <oldrev> <newrev>)" >&2
    exit 1
fi

if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
    echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
    exit 1
fi

# --- Config
allowunannotated=$(git config --bool hooks.allowunannotated)
allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
denycreatebranch=$(git config --bool hooks.denycreatebranch)
allowdeletetag=$(git config --bool hooks.allowdeletetag)
allowmodifytag=$(git config --bool hooks.allowmodifytag)
allowwildtag=$(git config --bool hooks.allowwildtag)
allowunmatchedtag=$(git config --bool hooks.allowunmatchedtag)

# check for no description
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
case "$projectdesc" in
"Unnamed repository"* | "")
    echo "*** Project description file hasn't been set" >&2
    exit 1
    ;;
esac

# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero="0000000000000000000000000000000000000000"
if [ "$newrev" = "$zero" ]; then
    newrev_type=delete
else
    newrev_type=$(git cat-file -t $newrev)
fi

case "$refname","$newrev_type" in
    refs/tags/*,commit)
        # un-annotated tag
        short_refname=${refname##refs/tags/}
        if [ "$allowunannotated" != "true" ]; then
            echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
            echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
            exit 1
        fi
        ;;
    refs/tags/*,delete)
        # delete tag
        if [ "$allowdeletetag" != "true" ]; then
            echo "*** Deleting a tag is not allowed in this repository" >&2
            exit 1
        fi
        ;;
    refs/tags/*,tag)
        # annotated tag
        if [ "$allowwildtag" != "true" ] && ./hooks/check_tag -r $refname > /dev/null 2>&1
        then
            echo "*** Tag '$refname' does not match the naming constraints." >&2
            echo "*** Tags must follow the 'x.y-z' pattern, where x, y, and z are numeric characters." >&2
            exit 1
        fi
        if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
        then
            echo "*** Tag '$refname' already exists." >&2
            echo "*** Modifying a tag is not allowed in this repository." >&2
            exit 1
        fi
        if [ "$allowunmatchedtag" != "true" ] 
        then
            project_version=`./hooks/extract_project_version $newrev 2>/dev/null`

            if [ "$project_version" == "" ]
            then
                # We dont output anything in case of success
                #echo "*** Project does not contain a project.info file. No tag match performed."
                :
            elif [ "$project_version" == "error" ]
            then
                # The project contains an invalid project.info: we accept the tag but warn about it.
                echo "*** Project contains an invalid project.info file. No tag match performed."
            else
                tag_version=${refname##refs/tags/}

                if [ "$project_version" != "$tag_version" ]
                then
                    echo "*** Tag and project version do not match: $tag_version != $project_version"
                    echo "*** Please check your project.info file."
                    exit 1
                fi
            fi
        fi
        ;;
    refs/heads/*,commit)
        # branch
        if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
            echo "*** Creating a branch is not allowed in this repository" >&2
            exit 1
        fi
        ;;
    refs/heads/*,delete)
        # delete branch
        if [ "$allowdeletebranch" != "true" ]; then
            echo "*** Deleting a branch is not allowed in this repository" >&2
            exit 1
        fi
        ;;
    refs/remotes/*,commit)
        # tracking branch
        ;;
    refs/remotes/*,delete)
        # delete tracking branch
        if [ "$allowdeletebranch" != "true" ]; then
            echo "*** Deleting a tracking branch is not allowed in this repository" >&2
            exit 1
        fi
        ;;
    *)
        # Anything else (is there anything else?)
        echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
        exit 1
        ;;
esac

# --- Finished
exit 0

And here is my extract_project_version script:

#!/bin/bash

rev=$1

if [ "$rev" == "" ]
then
    echo "Missing revision parameter." >&2
    exit 1
fi

tmpdir=/tmp/$$.extract_project_version

mkdir -p $tmpdir
git archive $rev | tar -x -C $tmpdir

if [ -e "$tmpdir/project.info" ]
then
    echo $tmpdir/project.info
fi

rm -rf $tmpdir

And now it works perfectly :)

Upvotes: 3

BenC
BenC

Reputation: 8976

Server-side

If you plan on using hooks, a post-receive hook (server refs updated) could create a commit with a change on project.info if it has been forgotten, but the initial tagging commit would not have the right information in the file...

If you want to validate this before updating the refs, the problem is that pre-receive/update hooks do not actually have the information required to check the validity of project.info (they get the name of the ref being updated, the old object name stored in the ref and the new objectname to be stored in the ref).

You can find some examples of hooks here and some information there.

Some other Stack Overflow topics on this subject :

Client-side

This would not ensure anything since clients could decide not to apply the proper method. Still, a post-commit hook could probably do the trick.

Upvotes: 0

Related Questions