Is there anybody who has clear instructions on how to add a pre-commit hook that avoids changes to tags subdirectories?
I already searched the internet quite a bit.
I don't have enough reputation to "comment" on Raim's answer above, but his worked great, with one exception, his grep pattern is wrong.
I simply used the below as my pre-commit hook (I didn't have an existing one, you'd need to merge in that case):
#!/bin/sh
REPOS="$1"
TXN="$2"
SVNLOOK=/opt/local/bin/svnlook
# Committing to tags is not allowed
$SVNLOOK changed -t "$TXN" "$REPOS" | grep "^U\W.*\/tags\/" && /bin/echo "Cannot commit to tags!" 1>&2 && exit 1
# All checks passed, so allow the commit.
exit 0
The only problem with Raim's grep pattern is that it only matched "tags" if it was at the "root" of your repo. Since I have several projects in my repo, the script as he wrote it allowed commits on tag branches.
Also, be sure to chmod +x as indicated, otherwise you'll think it worked b/c the commit failed, but it failed b/c it couldn't exec the pre-commit hook, not because the hook worked.
This was really great, thanks Raim. Much better and lighter weight than all other suggestions as it has no dependencies!
The accepted answer prevents updating files in a tag but doesn't prevent adding files to a tag. The following version handles both:
#!/bin/sh
REPOS="$1"
TXN="$2"
SVNLOOK="/home/staging/thirdparty/subversion-1.6.17/bin/svnlook"
# Committing to tags is not allowed
$SVNLOOK changed -t "$TXN" "$REPOS" --copy-info| grep -v "^ " | grep -P '^[AU] \w+/tags/' && /bin/echo "Cannot update tags!" 1>&2 && exit 1
# All checks passed, so allow the commit.
exit 0
Since the 1st answer didn't prevent add/suppr files, and prevented new tags creation, and many other where incomplete or buggy, I reworked it
Here is my pre-commit hook : Goals are :
--------- file "pre-commit" (put in repositories hooks folder) ---------
#!/bin/sh
REPOS="$1"
TXN="$2"
SVNLOOK=/usr/bin/svnlook
#Logs
#$SVNLOOK changed -t "$TXN" "$REPOS" > /tmp/changes
#echo "$TXN" > /tmp/txn
#echo "$REPOS" > /tmp/repos
# Committing to tags is not allowed
# Forbidden changes are Update/Add/Delete. /W = non alphanum char Redirect is necessary to get the error message, since regular output is lost.
# BUT, we must allow tag creation / suppression
$SVNLOOK changed -t "$TXN" "$REPOS" | /bin/grep "^A\W.*tags\/[0-9._-]*\/." && /bin/echo "Commit to tags are NOT allowed ! (Admin custom rule)" 1>&2 && exit 101
$SVNLOOK changed -t "$TXN" "$REPOS" | /bin/grep "^U\W.*tags\/[0-9._-]*\/." && /bin/echo "Commit to tags are NOT allowed ! (Admin custom rule)" 1>&2 && exit 102
$SVNLOOK changed -t "$TXN" "$REPOS" | /bin/grep "^D\W.*tags\/[0-9._-]*\/." && /bin/echo "Commit to tags are NOT allowed ! (Admin custom rule)" 1>&2 && exit 104
# All checks passed, so allow the commit.
exit 0;
--------- end of file "pre-commit" ---------
Also, I made 2 shell scripts to copy my hook in every project of my svn : One to set a repo read only :
--------- script "setOneRepoTagsReadOnly.sh" ---------
#!/bin/sh
cd /var/svn/repos/svn
zeFileName=$1/hooks/pre-commit
/bin/cp ./CUSTOM_HOOKS/pre-commit $zeFileName
chown www-data:www-data $zeFileName
chmod +x $zeFileName
--------- end of file "setOneRepoTagsReadOnly.sh" ---------
And one calling it for every repo, to make all of my repos read only :
--------- file "makeTagsReadOnly.sh" ---------
#!/bin/shs/svn
#Lists all repos, and adds the pre-commit hook to protect tags on each of them
find /var/svn/repos/svn/ -maxdepth 1 -mindepth 1 -type d -execdir '/var/svn/repos/svn/setOneRepoTagsReadOnly.sh' \{\} \;
--------- end of file "makeTagsReadOnly.sh" ---------
I execute thoses scripts directly from the svn "root" (/var/svn/repos/svn, in my case). Btw, a cron task could be set to automatically modify new repos by executing thoses scripts daily
Hope it helps.
The listed answers are great but none did exactly what I need. I want to allow creating tags easily, but once they are created they should be fully read-only.
I also want to prevent the stupid situation where if you do this:
svn copy myrepo/trunk myrepo/tags/newrelease
All is well the first time. But the second time, if the tag already exists, you will end up with myrepo/tags/newrelease/trunk
.
My pre-commit hook will look for any pre-existing SVN directory matching (repo)/tags/(tag)/
and fail if it's found:
$SVNLOOK tree -N --full-paths "$REPOS" "`$SVNLOOK changed -t "$TXN" "$REPOS" \
| sed 's/[A-Z][[:space:]]*\([^/]*\)\/tags\/\([^/]*\)\/.*/\1\/tags\/\2\//' \
| head -n 1`" \
&& echo "Tag already exists, commit rejected." >&2 \
&& exit 1
Here is a short shell script to prevent committing to tags after they have been created:
#!/bin/sh
REPOS="$1"
TXN="$2"
SVNLOOK=/usr/bin/svnlook
# Committing to tags is not allowed
$SVNLOOK changed -t "$TXN" "$REPOS" | grep "^U\W*tags" && /bin/echo "Cannot commit to tags!" 1>&2 && exit 1
# All checks passed, so allow the commit.
exit 0
Save this at hooks/pre-commit
for your Subversion repository and make it executable with chmod +x
.
Pretty late to the party, however I wrote a python pre-commit hook for work which is based off the log-police.py script on http://subversion.tigris.org/.
This script should do what you want, however it also checks that a log message exists, though that should be easy to remove from the script.
Some caveats:
Requirements:
Finally, the code:
#!/usr/bin/env python
#
# pre-commit.py:
#
# Performs the following:
# - Makes sure the author has entered in a log message.
# - Make sure author is only creating a tag, or if deleting a tag, author is a specific user
#
# Script based on http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/log-police.py
#
# usage: pre-commit.py -t TXN_NAME REPOS
# E.g. in pre-commit.bat (under Windows)
# python.exe {common_hooks_dir}\pre_commit.py -t %2 %1
#
import os
import sys
import getopt
try:
my_getopt = getopt.gnu_getopt
except AttributeError:
my_getopt = getopt.getopt
import re
import svn
import svn.fs
import svn.repos
import svn.core
#
# Check Tags functionality
#
def check_for_tags(txn):
txn_root = svn.fs.svn_fs_txn_root(txn)
changed_paths = svn.fs.paths_changed(txn_root)
for path, change in changed_paths.iteritems():
if is_path_within_a_tag(path): # else go to next path
if is_path_a_tag(path):
if (change.change_kind == svn.fs.path_change_delete):
if not is_txn_author_allowed_to_delete(txn):
sys.stderr.write("\nOnly an administrator can delete a tag.\n\nContact your Subversion Administrator for details.")
return False
elif (change.change_kind != svn.fs.path_change_add):
sys.stderr.write("\nUnable to modify " + path + ".\n\nIt is within a tag and tags are read-only.\n\nContact your Subversion Administrator for details.")
return False
# else user is adding a tag, so accept this change
else:
sys.stderr.write("\nUnable to modify " + path + ".\n\nIt is within a tag and tags are read-only.\n\nContact your Subversion Administrator for details.")
return False
return True
def is_path_within_a_tag(path):
return re.search('(?i)\/tags\/', path)
def is_path_a_tag(path):
return re.search('(?i)\/tags\/[^\/]+\/?$', path)
def is_txn_author_allowed_to_delete(txn):
author = get_txn_property(txn, 'svn:author')
return (author == 'bob.smith')
#
# Check log message functionality
#
def check_log_message(txn):
log_message = get_txn_property(txn, "svn:log")
if log_message is None or log_message.strip() == "":
sys.stderr.write("\nCannot enter in empty commit message.\n")
return False
else:
return True
def get_txn_property(txn, prop_name):
return svn.fs.svn_fs_txn_prop(txn, prop_name)
def usage_and_exit(error_msg=None):
import os.path
stream = error_msg and sys.stderr or sys.stdout
if error_msg:
stream.write("ERROR: %s\n\n" % error_msg)
stream.write("USAGE: %s -t TXN_NAME REPOS\n"
% (os.path.basename(sys.argv[0])))
sys.exit(error_msg and 1 or 0)
def main(ignored_pool, argv):
repos_path = None
txn_name = None
try:
opts, args = my_getopt(argv[1:], 't:h?', ["help"])
except:
usage_and_exit("problem processing arguments / options.")
for opt, value in opts:
if opt == '--help' or opt == '-h' or opt == '-?':
usage_and_exit()
elif opt == '-t':
txn_name = value
else:
usage_and_exit("unknown option '%s'." % opt)
if txn_name is None:
usage_and_exit("must provide -t argument")
if len(args) != 1:
usage_and_exit("only one argument allowed (the repository).")
repos_path = svn.core.svn_path_canonicalize(args[0])
fs = svn.repos.svn_repos_fs(svn.repos.svn_repos_open(repos_path))
txn = svn.fs.svn_fs_open_txn(fs, txn_name)
if check_log_message(txn) and check_for_tags(txn):
sys.exit(0)
else:
sys.exit(1)
if __name__ == '__main__':
sys.exit(svn.core.run_app(main, sys.argv))