Saturday 3 March 2012

SubVersion BAT / batch pre-commit hook to exclude specific directories

[2012-04-27] Updated based on user feedback: thanks to Alex & Dan.

Recently, I have encountered an issue where I found that Microsoft .NET bin and obj directories were being committed to our codebase. These directories are compilation output directories used by MSBuild / Visual Studio and thus should not be added to the SubVersion codebase. In an effort to stop this, writing a pre-commit hook was the obvious answer. However, I have chosen to write it in batch / BAT.

Yes, BAT files, I know what you are going to say... Why not Perl, Python or even some BAT file that calls out to Powershell (which is my favourite scripting language)? Well, the simple answer is speed. With time constraints in place, getting Perl and Python interpreters set up on the SubVersion server with a language learning curve for our team may not be justifiable for a single pre-commit hook where a quickly written BAT file would suffice. Initial investigation suggests that getting Powershell running with a pre-commit hook BAT wrapper may prove too time costly also, so batch was chosen. This entry assumes a basic familiarity with BAT files.

OK, some interesting points of note about SubVersion pre-commit hooks. 
  • Because this is a pre-commit hook, it does not deal with revisions, it deals with transactions, which, may or may not be persisted becoming revisions, depending on the outcome of the pre-commit hook. The format of transaction IDs differs compared to revisions IDs.
  • If the pre-commit hook exits with a return code of 0, then the transaction is persisted and becomes a revision. If the pre-commit exits with a non-zero return code, the transaction will fail to be committed. There is scope here to provide a specific message which is sent back to the user, detailing why the hook failed which can be seen with each of the four echo commands below and how that command's standard output is redirected to the standard error via the >&2 code.
  • When the BAT pre-commit hook below is executed, there is no environment set - such as the system path, so when using the svnlook executable, the absolute path must be provided. Similar for other commands. Don't forget to wrap paths in double quotes if they contain spaces!
The example below prevents commits which include empty bin and obj directories and bin and obj directories which contain one or more files / folders. The core of the example is using svnlook to examine what paths have changed on the current transaction and piping these to the Windows findstr command which uses a regular expression and checks to see if bin and obj directories exist on those paths.

Finally, to add this to SubVersion for a specific repo, navitate to the repo on the file system, and under the hooks directory, create a new file called pre-commit.bat and add the script below to it.The only update you'll need to make to this script to get it working is to set the correct path to the svnlook executable in each of four locations below. Save the file. The moment this file exists and the contents are valid, the pre-commit hook is live on the repo.

:: Set the first incoming parameter to the absolute path to the repository and the second incoming parameter to the transaction ID. This is standard in all SubVerison pre-commit hooks.
SET REPOS=%1
SET TXN=%2 


:: Check if there is an attempt made to commit changes to a bin directory. This also includes an attempt to commit an empty bin directory.
"E:\csvn\bin\svnlook.exe" changed -t %TXN% %REPOS% | FINDSTR /R /I "./bin/.*"
IF %ERRORLEVEL% EQU 1 GOTO CHECK_OBJ_DIR
ECHO "It is forbidden to commit empty bin directories or to commit to existing bin directories to
SubVersion as these are generated .NET output and not part of the SubVersion version controlled codebase" >&2
EXIT 1
:: Check if there is an attempt made to commit changes to an obj directory. This also includes an attempt to commit an empty obj directory.
:CHECK_OBJ_DIR
"E:\csvn\bin\svnlook.exe" changed -t %TXN% %REPOS% | FINDSTR /R /I "./obj/.*"
IF %ERRORLEVEL% EQU 1 GOTO OK
ECHO "It is forbidden to commit empty obj directories or to commit to existing obj directories to
SubVersion as these are generated .NET output and not part of the SubVersion version controlled codebase" >&2
EXIT 1

:: All checks passed, so allow the commit.
:OK
EXIT 0