Friday, April 26, 2013

Windows Subversion Server Hook Scripts in Batch

We run all windows servers over here and that includes the server I get to run Subversion on. This is a less than ideal situation as even the best Windows Subversion servers are still locked down to http instead of the native svn protocol. It's not as bad as it sounds though, svn1.7 is actually really fast, even over http and even with a stupidly large repository. The problem I've really come up against though is hook scripts. There is literally one example hook script for Windows, and all it really does is force comments. Valuable, yes, but this is not all we can do. If you've found this page I'm sure you've already seen it, it goes like this:


@echo off          
setlocal
set REPOS=%1  
set TXN=%2

svnlook log %REPOS% -t %TXN% | findstr ...... > nul  
if %errorlevel% gtr 0 (goto err) else exit 0  

:err  
echo ---------------------------------------------- 1>&2   
echo Please add a log message to your commit.       1>&2  
echo ---------------------------------------------- 1>&2 
exit 1


In fact this actually checks for a commit message of at least six characters using the bastardized version of regex in the findstr utility. Batch is archaic, and I have to admit I am a complete novice with it, but it doesn't work like any language I've ever learned. Where batch really breaks for me is I want my pre-commit hook to not only force comments, but block anyone but me from committing to the trunk. So I use svnlook changed to see what's being changed. it looks something like this:

A   branch/vendors/deli/
A   branch/vendors/deli/chips.txt
D   branch/trunk/test.txt
A   trunk/vendors/deli/sandwich.txt
A   trunk/vendors/deli/pickle.txt
U   trunk/vendors/baker/bagel.txt
AU  trunk/vendors/baker/croissant.txt
UU  trunk/vendors/baker/pretzel.txt
D   trunk/vendors/baker/baguette.txt

The way I figure it, if I see any character in the first two positions, then two spaces, then the word 'trunk' it should be blocked but findstr can either do it's horrible idea of regex, or match a string literal, and there is no escape sequence to match spaces in findstr. What I need is some real regex power. I tried GNU's grep but found that that either I am inept (which is likely) or that regex doesn't work like one would expect. I got to ^..\s\s[at][nr][tu] to match up to the 'tru' in trunk, but could not figure out how to match 'n' and honestly why [at] to match 't'? I give up on that, then I discovered a more powerful tool GNU's sed With this I was able to write a simple, easy to follow pattern that worked, and worked as I expected: ^..\s\strunk. I could now figure out when someone was attempting to commit to the trunk, and with the gotos in batch able to skip this check if it was me. The hook I ultimately came up with looks like this, with added comments to help out the other beginners out there:

@echo off  
::turns off output other than what you echo, ie, doesn't show any
::actual commands you have running
setlocal
::in my reading this is by default, but just in case, prevents 
::variables in this script and any scripts that call this script
::encapsulated from each other  
set REPOS=%1  
::the first argument svn sends to the hoot is the repo path, you
::reference arguments in the order they are received with the %#
::syntax
set TXN=%2
::the second is the transaction, if we were in post-commit this 
::would be the revision
svnlook author -t %TXN% %REPOS% | findstr michael > nul
::svnlook has a lot of subcommands author should just give me 
::the username of whos doing the commit. findstr works okay for 
::this basic task and the pipe (|) makes the svnlook output the 
::input for the findstr command
if %errorlevel% equ 0 (goto commentCheck)
::this is hacky, but the only way I could find to do thing. I 
::attempted to put any line matching 'michael' into the nul 
::handle, if there were no lines found an error code of 1 is 
::generated if it succeeds then no error code is thrown (0), and 
::I use the goto command to skip to check if there is a commit 
::message
svnlook changed -t %TXN% %REPOS% | sed -n /^..\s\strunk/p > temptest.txt
::I use my actual regex here with the sed command and since we
::can't have variables in batch like we are used to, cache the
::output in a file
for /f %%i in ("temptest.txt") do set size=%%~zi
::read in the size of that file (0 means no matches)
if %size% gtr 0 ( goto authErr ) else ( goto commentCheck )
::if trunk is found the file has a size, goto takes me to give a
::not authorized message, otherwise check the comment
exit 1
::sanity check that will block the commit if something wasn't 
::accounted for.
:commentCheck
:: this is a lable and what goto commands are looking for
svnlook log %REPOS% -t %TXN% | findstr ...... > nul  
if %errorlevel% gtr 0 (goto err) else exit 0 
::again findstr is good enough, just checking for 6 characters of
::message, if there are at least 6 then exit with no error (0) 
::which allows the commit
:err  
echo ---------------------------------------------- 1>&2   
echo Please add a log message to your commit.       1>&2  
echo ---------------------------------------------- 1>&2  
exit 1
::the basic message and exit code that will block the commit 
::displaying whats in the &2 handle
:authErr
echo ---------------------------------------------- 1>&2
echo not authorized to make commits to trunk.       1>&2
echo ---------------------------------------------- 1>&2  
exit 1
::same thing,m already discovered you didn't have access and 
::attempted to commit to the trunk, you get this message and your
::commit is blocked.

A quick note on the grep and sed install: even if you use their installer they will not put their bin path in the PATH environment variable, so the this script will fail unless you add it yourself. Just look up some instructions on how to modify PATH in your version of Windows, and add the path to the bin folder wherever you put it when you ran the grep and/or sed installer.