Git hook to detect existing tag case insensitive
Finding existing Git tags

The files in a Git repository's meta storage,
such as the content of ./refs/tags, ./packed-refs,
are not API.
It's best to interact with Git through commands.

You can get the list of tags with the git tag -l command.
This will include packed refs as well.

Use early returns

The hook collects counts of matching tags, then matching packed refs,
and finally it decides if it should fail based on the collected counts,
and if the operation is not a delete.
It's usually better to not delay actions when you have enough information to take them.

For example,
very early in the process_ref function you can already know if it's a delete operation.
It would be better to return 0 right there,
to avoid any further unnecessary processing.

The same goes for checking the counts.
If the first count in tags is non-zero,
you can already fail without further processing.

Use grep -q instead of grep -c and checking for non-zero

When grep -q finds a match,
it stops further processing.
grep -c continues until the end of the input to collect all counts.
That's not necessary in your example.

Use more strict pattern matching

Using grep -ic $refname to find a matching ref in the list of refs may leave room for false positives.
For example if there is a tag v1.1,
and then you try to push v1, it will fail.
This is a bit tortured example,
because obviously you won't release v1 after v1.1,
but it's good to be aware of this point.

You could make the matching more strict by adding a $ to anchor the end.
If both the beginning and the end can be anchored,
then you could use the -x flag of grep to match entire lines.
Now that would be rock-solid strict.

Always quote parameters

Even though you know that $refname will never contain unsafe characters,
it's a good habit to always surround with double-quotes when used as command arguments, for example in grep -ic "$refname".

Use $(...) instead of `...`

`...` is obsolete, use $(...) instead.

Alternative implementation

Putting the above together (and some more),
I'd implement the hook like this:

#!/bin/sh

is_delete() {
    [ "$1" = "0000000000000000000000000000000000000000" ]
}

tag_exists() {
    git tag -l | grep -qix "$1"
}

fail() {
    echo "push failed"
    echo "tag already exists: $1"
    echo "please contact dev team"
    exit 1
}

process_ref() {
    target="$2"   # target hash. all 0s for a delete command
    refname="$3"  # the full ref name

    is_delete "$target" &amp;&amp; return

    case $refname in
        refs/tags/*)
            tagname=${refname#refs/tags/}
            tag_exists "$tagname" &amp;&amp; fail "$refname"
    esac

    return 0
}

while read REF; do process_ref $REF; done