In my company, we use git as VCS, the main server run under linux but all our code is for Windows and we all develop on Windows.
The git tag system is case sensitive on linux but is case insensitive on windows. That is:
- under linux
Test/Tag
andTEST/Tag
are different tags - under windows
Test/Tag
andTEST/Tag
are the same tag
We had a lot of issues from that.
So we decided to add a git hook on server side, that is, a script that will test if a pushed tag already exists on the server case insensitively.
Here is the content of the pre-receive git hook.
#!/bin/sh
process_ref() {
target="2ドル" # target hash. 0s for a delete command
refname="3ドル" # the full ref name
#detect tags
case "$refname" in
refs/tags/*)
# use grep to count matching refs in .git/refs/tags folder (case unsensitivly)
count1=`find ./refs/tags -print | grep -ic $refname`
# use grep to count matching refs in packed-refs if present
if [ -e "./packed-refs" ]; then
count2=`grep -ic "$refname" ./packed-refs`
else
count2="0"
fi
# abort if the ref already exist and it's not a delete ref command
if ([ $count1 -ne 0 ] || [ $count2 -ne 0 ]) && [ $target != "0000000000000000000000000000000000000000" ]; then
echo "push failed"
echo "tag already exists: $refname"
echo "please contact dev team"
exit 1
fi
esac
}
# iterate thru all given refs.
while read REF; do process_ref $REF; done
1 Answer 1
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" && return
case $refname in
refs/tags/*)
tagname=${refname#refs/tags/}
tag_exists "$tagname" && fail "$refname"
esac
return 0
}
while read REF; do process_ref $REF; done
-
\$\begingroup\$ Thanks for your answer, the
is_delete
function bug me. I'm pretty sure you misseda-f
(since hash is in hexadecimal). A only0a-f
hash is unlikely for sure but.. \$\endgroup\$Orace– Orace2018年10月23日 11:12:04 +00:00Commented Oct 23, 2018 at 11:12 -
\$\begingroup\$ @Orace well spotted! (And in the end I went for a simpler option.) \$\endgroup\$janos– janos2018年10月23日 12:59:41 +00:00Commented Oct 23, 2018 at 12:59