Monday, September 13, 2010

Subversion - Managing exclusions in commit hooks

Subversion is now a widely used version control system. It is easy to setup, supports branching and merging almost effortlessly and has a few good client tools (like TortoiseSvn) and plugins for major IDEs that make life very easy for users.
Repositories
Subversion stores content in what are termed as repositories. One can visualize a repository as the base folder inside which various code lines exist. These can be trunk (the main code line), branches and tags (a tag usually corresponds to a release).
Hooks
Subversion also has a concept of hooks which are programs (usually scripts) that can be invoked at specific events. There are hooks for pre-commit, commit and post-commit events. Hooks can be used for a variety of purposes e.g. checking for a proper commit comment, running static analysis tools and fail commits on violations (this is usually better done in CI but sometimes might need to be done in a hook), code format checking etc.


Activating and Deactivating hooks for specific artifacts
Hooks exist at the level of a repository. What this means is that if a pre-commit hook exists, it will be invoked on each commit transaction to any code line within the repository. Sometimes though, certain code lines or projects or files need to be excluded from the checks built into the hooks. In other words, one might need to "activate" or "deactivate" hooks for certain commits. This "activation" or "deactivation" is not available out of the box in SVN and needs to be coded and built in. Here's an example of how one can achieve the same. The example below uses bash shell script but can be in any language.


Let's take an example of a pre-commit hook that needs to be deactivated for a few branches, one project and one file in a specific branch with a certain word in its name. A pre-commit hook is invoked as part of a commit transaction just before the transaction executes and becomes a revision. If the hook executes successfully and returns 1, the transaction commits and a new revision gets created. If it aborts or returns 0, the transaction is rolled back by the SVN server.
A pre-commit hook is called by the SVN server with two arguments, the first is the absolute path to the repository and the second is the transaction id of the commit transaction in question. With this information in hand, the script can be made to find the changes that are part of a transaction using the svnlook command. The command for the same is:
svnlook changed -t $2 $1 ($1 is the repos path and $2 is the transaction id)

With this information now available, the hook can be made to ignore certain files, projects etc from its processing.
The example here uses a flat text file where each exclusion is listed in a line. An example exclusion file could be:
------------------------------exclusions.txt------------------------------------
branch1
branch2
Project1
branch3/(.?)*FileC.java
--------------------------------------------------------------------------------------------------


The hook can then be written as:
--------------------------------------pre-commit--------------------------------------------
#!/bin/bash
REPOS=$1
TXN=$2
exclusionFile="exclusions.txt"

isExcluded()
{
    exclude=0

    path=${1}
    while read line
    do
        regex=([a-z][A-Z][0-9])*${line}
        if [[ ${path} =~ ${regex} ]]
        then
            exclude=1    
        fi
    done < ${exclusionFile}
    return ${exclude}
}
CHANGED=`${SVNLOOK} changed -t "${TXN}" "${REPOS}" | /bin/grep -v ^D | /bin/awk '{print $2}`  
# (ignoring deletes)
for artifact in ${CHANGED}
do
    isExcluded(${artifact})
    if [ $? -eq 0 ]
    then
        #do processing here
    fi
done
------------------------------------------------------------------------------------------------
The benefit of taking this approach is that exclusions can be configured and maintained easily without any change to the hook itself.

No comments: