SVN pre-commit hook for avoiding changes to tags subdirectories
Asked Answered
B

11

40

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 found this link: SVN::Hooks::DenyChanges , but I can't seem to compile things.

Bleier answered 21/1, 2009 at 7:59 Comment(0)
I
39

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!

Inarticulate answered 15/3, 2009 at 22:58 Comment(5)
You are right. I did not think about multiple projects in the same repository as this is a setup I don't use myself.Cyprio
Cool thanks for the verification, and the script! It's by far the most elegant solution to the problem.Inarticulate
This script ist incomplete, it neither handles deletes nor additions or prop changes.Vargueno
I had to change the grep match to grep "^U\W.*tags\/" because my svnlook output had no leading slash on the repository path.Ranchod
This is not fully correct. You should be preventing users from adding, deleting files in the tag as well, not just modifying.Cheston
C
17

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.

Cyprio answered 23/2, 2009 at 1:16 Comment(0)
B
7

Here is my windows batch file pre-commit hook. If the user is an administrator the other checks will be skipped. It checks if the commit message is empty, and if the commit is to a tag. Note: findstr is a nerfed alternative to grep on other platforms.

The way it checks if the commit is to a tag, it first checks if svnlook changed contains "tags/". It then checks if svnlook changed matches "^A.tags/[^/]/$", which means that it will check if you are adding a new folder under tags/.

Users are allowed to create new projects. The pre-commit hook allows a user to create the folders trunk/ tags/ and branches/. Users are not allowed to delete the folders trunk/ tags/ and branches/. This will work for a single or multi-project repository.

 @echo off
 rem This pre-commit hook will block commits with no log messages and blocks commits on tags.
 rem Users may create tags, but not modify them.
 rem If the user is an Administrator the commit will succeed.

 rem Specify the username of the repository administrator
 rem commits by this user are not checked for comments or tags
 rem Recommended to change the Administrator only when an admin commit is neccessary
 rem then reset the Administrator after the admin commit is complete
 rem this way the admin user is only an administrator when neccessary
 set Administrator=Administrator

 setlocal

 rem Subversion sends through the path to the repository and transaction id.
 set REPOS=%1%
 set TXN=%2%

 :Main
 rem check if the user is an Administrator
 svnlook author %REPOS% -t %TXN% | findstr /r "^%Administrator%$" >nul
 if %errorlevel%==0 (exit 0)

 rem Check if the commit has an empty log message
 svnlook log %REPOS% -t %TXN% | findstr . > nul
 if %errorlevel% gtr 0 (goto CommentError)

 rem Block deletion of branches and trunk
 svnlook changed %REPOS% -t %TXN% | findstr /r "^D.*trunk/$ ^D.*branches/$" >nul
 if %errorlevel%==0 (goto DeleteBranchTrunkError)

 rem Check if the commit is to a tag
 svnlook changed %REPOS% -t %TXN% | findstr /r "^.*tags/" >nul
 if %errorlevel%==0 (goto TagCommit)
 exit 0

 :DeleteBranchTrunkError
 echo. 1>&2
 echo Trunk/Branch Delete Error: 1>&2
 echo     Only an Administrator may delete the branches or the trunk. 1>&2
 echo Commit details: 1>&2
 svnlook changed %REPOS% -t %TXN% 1>&2
 exit 1

 :TagCommit
 rem Check if the commit is creating a subdirectory under tags/ (tags/v1.0.0.1)
 svnlook changed %REPOS% -t %TXN% | findstr /r "^A.*tags/[^/]*/$" >nul
 if %errorlevel% gtr 0 (goto CheckCreatingTags)
 exit 0

 :CheckCreatingTags
 rem Check if the commit is creating a tags/ directory
 svnlook changed %REPOS% -t %TXN% | findstr /r "^A.*tags/$" >nul
 if %errorlevel% == 0 (exit 0)
 goto TagsCommitError

 :CommentError
 echo. 1>&2
 echo Comment Error: 1>&2
 echo     Your commit has been blocked because you didn't enter a comment. 1>&2
 echo     Write a log message describing your changes and try again. 1>&2
 exit 1

 :TagsCommitError
 echo. 1>&2
 echo %cd% 1>&2
 echo Tags Commit Error: 1>&2
 echo     Your commit to a tag has been blocked. 1>&2
 echo     You are only allowed to create tags. 1>&2
 echo     Tags may only be modified by an Administrator. 1>&2
 echo Commit details: 1>&2
 svnlook changed %REPOS% -t %TXN% 1>&2
 exit 1
Bainbrudge answered 23/8, 2010 at 17:46 Comment(1)
A nicely polished, ready-to-run script that covers the question asked and more--well done!Lambdacism
B
6

This anwser is a lot after date, but I discovered the --copy-info parameter for the svnlook changed command.

The output of this command adds a '+' in the third column, so you know it is a copy. You can check for commits to the tags directory, and only allow commits with a '+' present.

I've added some output in my blog post.

Bibeau answered 20/8, 2010 at 7:17 Comment(0)
D
4

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:

  • I'm new to Python, so it most likely could be written better
  • It has only been tested on Windows 2003 with Python 2.5 and Subversion 1.4.

Requirements:

  • Subversion
  • Python
  • Subversion bindings for Python

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))
Deoxyribonuclease answered 30/1, 2009 at 23:30 Comment(0)
V
4

Most of the previously written scripts are incomplete because several cases are not covered. This is my script:

contains_tags_dir=`$SVNLOOK changed --copy-info -t "$TXN" "$REPOS" | head -1 | egrep "+\/tags\/.*$" | wc -l | sed "s/ //g"`

if [ $contains_tags_dir -gt 0 ]
then
  tags_dir_creation=`$SVNLOOK changed --copy-info -t "$TXN" "$REPOS" | head -1 | egrep "^A       .+\/tags\/$" | wc -l | sed "s/ //g"`
  if [ $tags_dir_creation -ne 1 ]
  then
    initial_add=`$SVNLOOK changed --copy-info -t "$TXN" "$REPOS" | head -1 | egrep "^A \+ .+\/tags\/.+\/$" | wc -l | sed "s/ //g"`
    if [ $initial_add -ne 1 ]
    then
      echo "Tags cannot be changed!" 1>&2
      exit 1
    fi
  fi
fi

It might seem complicated but you have to make sure that you are in /tags and you are allowed create /tags if it does not exist and all subsequent folders. Any other change is blocked. Almost none of the previous scripts cover all cases described in the Subversion book for svnlook changed ....

Vargueno answered 17/11, 2011 at 8:46 Comment(4)
What are req to use this hook? I am willing ti implement it for blocking comit to specified dir ... but I dont understand where I need to edit it to change checked dir ... maybe there is some app that can generate such script by taking repo dir as parameter ... ?Encyst
No, you have to place the hook in the the hooks dir of the server hosting the repo. Read the subversion book about that.Vargueno
Yes, I understand that ; Sorry for bad formulated question :/ My Eng is not so good after all for programming specifics :( The Idea of question is: How to specify other repo dir than tags? For example, branch or ahead or whatever ... but instead of hard-code the dir in script - pass it as parameter?Encyst
You can't acutally unless you define an array of blockable dirs and iterate that through in the script above. Your script has only two params passed. No more, no less.Vargueno
G
3

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
Grote answered 19/7, 2011 at 15:38 Comment(2)
Can I change tags into branches or other sub-directory of svn://<root>/tags ? For example, <root>/<some_direcotry>/<some_other_dir> ?Encyst
For some reason this didn't work for me. I tried this and it works good. $SVNLOOK changed -t "$TXN" "$REPOS" | grep "^[AUD_]. .*/tags/.*/." && /bin/echo 'Cannot update tags!' 1>&2 && exit 2Vibration
W
1

My version only allows creating and deleting tags. This should handle all the special cases (like adding files, changing properties, etc.).

#!/bin/sh

REPOS="$1"
TXN="$2"
SVNLOOK=/usr/local/bin/svnlook

output_error_and_exit() {
    echo "$1" >&2
    exit 1
}

changed_tags=$( $SVNLOOK changed -t "$TXN" "$REPOS" | grep "[ /]tags/." )

if [ "$changed_tags" ]
then 
    echo "$changed_tags" | egrep -v "^[AD] +(.*/)?tags/[^/]+/$" && output_error_and_exit "Modification of tags is not allowed."
fi 

exit 0
Watcher answered 11/11, 2013 at 14:42 Comment(0)
C
1

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 :

  • Disallow commits on tags (file addition/suppression/updates)
  • Don't prevent creation of tags

--------- 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.

Crease answered 11/7, 2014 at 18:25 Comment(0)
U
1

If you are using JIRA, you can use the add-on named Commit Policy to protect paths in your repository without writing custom hooks.

How? Use the condition named Changed files must match a pattern.

It has an regular expression type argument that must match for every file in a commit, otherwise the commit is rejected. So, in your case you should use a regex that means "does not start with the prefix /tags/".

(You can implement many other smart checks with the same plugin.)

Disclaimer: I'm a developer working on this paid add-on.

Underneath answered 29/9, 2015 at 9:26 Comment(0)
A
0

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
Afb answered 18/12, 2015 at 15:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.