This post originated from an RSS feed registered with Python Buzz
by Thomas Guest.
Original Post: A Subversion Pre-Commit Hook
Feed Title: Word Aligned: Category Python
Feed URL: http://feeds.feedburner.com/WordAlignedCategoryPython
Feed Description: Dynamic languages in general. Python in particular. The adventures of a space sensitive programmer.
Subversion’s hook scripts provide a powerful and
flexible way to associate actions with repository events. For example,
the pre-commit hook allows you to check – and possibly abort – a
transaction before it actually gets committed. This entry describes
how to install and test a simple Python hook script to prohibit
tabs from C++ files.
Creating and Installing a Hook Script
Your Subversion repository already has some template hook scripts. For
example, the pre-commit template is in
PATH_TO_REPOS/hooks/pre-commit.tmpl. These templates contain
instructions on what the hook script does and what parameters it can expect.
Here, then, is the most direct route to creating and activating a
pre-commit hook:
su - svn # As user svn
cd PATH_TO_REPOS/hooks # Change to the hooks directory
cp pre-commit.tmpl pre-commit # Create a pre-commit script
emacs pre-commit # Edit to taste
chmod u+x pre-commit # It needs to be executable
And that’s it.
You haven’t actually tested your new hook script so it probably
doesn’t do what you meant it to. Either you can sit back and wait for
users to complain or you can think of a way to test your hook before
you install it.
Find a way to test your hook script on the live
repository before you actually install it.
In the case of a pre-commit hook the second strategy is quite
attractive. Pre-commit hooks are typically used to enforce checks on a
transaction before it gets committed and becomes a new
repository revision. They are often introduced when a faulty revision gets
into the repository and someone says: “Couldn’t Subversion have stopped this
from happening?”
With a little ingenuity you can test how the pre-commit hook would have
responded to such a faulty transaction which, in future, we would like
to prohibit.
Svnlook
Svnlook is the Subversion administrator’s friend. It can be used
on the Subversion server to examine the repository without changing
it. A hook script typically uses svnlook to examine a repository
event and take appropriate action. Thus, for example, to see what
files were changed by revision 1234, we run:
svnlook changed PATH_TO_REPOS --revision 1234
To find what files might be changed by transaction 1234-1 (if
nothing goes wrong) the command is similar:
Examining transactions using svnlook is tricky since transactions
are transient. When a transaction becomes a revision you can no longer
look at it.
The Pre-commit Hook
The pre-commit hook gives you an opportunity to catch the
transaction just before it becomes a revision. Subversion
passes this hook two parameters:
the path to the root of the repository
the transaction identifier
The pre-commit can fail the transaction by printing an informative
message to standard error and returning non-zero. A return code of
zero allows the transaction to complete successfully.
For testing we can add an extra switch, --revision, to our test
pre-commit hook. This switch is to indicate that the second parameter
is in fact a revision number. Now we can system-test our hook script
on exisiting repository revisions, and confirm that it does indeed
return non-zero for the bad ones and zero for the good ones.
test-pre-commit PATH_TO_REPOS --revision 1234
An Example Pre-commit Hook
Here, then, is a pre-commit hook to ban the TAB character from C++
source files. Test it using the --revision option. Command line
help is available using --help.
It uses two different flavours of svnlook to examine the repository:
svnlook changed – to find which files were changed by a transaction/revision
svnlook cat – to find the contents of a file in a transaction/revision
pre-commit
#!/bin/env python" Example Subversion pre-commit hook. "defcommand_output(cmd):" Capture a command's standard output. "importsubprocessreturnsubprocess.Popen(cmd.split(),stdout=subprocess.PIPE).communicate()[0]deffiles_changed(look_cmd):""" List the files added or updated by this transaction.
"svnlook changed" gives output like:
U trunk/file1.cpp
A trunk/file2.cpp
"""deffilename(line):returnline[4:]defadded_or_updated(line):returnlineandline[0]in("A","U")return[filename(line)forlineincommand_output(look_cmd%"changed").split("\n")ifadded_or_updated(line)]deffile_contents(filename,look_cmd):" Return a file's contents for this transaction. "returncommand_output("%s %s"%(look_cmd%"cat",filename))defcontains_tabs(filename,look_cmd):" Return True if this version of the file contains tabs. "return"\t"infile_contents(filename,look_cmd)defcheck_cpp_files_for_tabs(look_cmd):" Check C++ files in this transaction are tab-free. "defis_cpp_file(fname):importosreturnos.path.splitext(fname)[1]in".cpp .cxx .h".split()cpp_files_with_tabs=[ffforffinfiles_changed(look_cmd)ifis_cpp_file(ff)andnotcontains_tabs(ff,look_cmd)]iflen(cpp_files_with_tabs)>0:sys.stderr.write("The following files contain tabs:\n %s\n"%"\n ".join(cpp_files_with_tabs))returnlen(cpp_files_with_tabs)defmain():usage="""usage: %prog REPOS TXN
Run pre-commit options on a repository transaction."""fromoptparseimportOptionParserparser=OptionParser(usage=usage)parser.add_option("-r","--revision",help="Test mode. TXN actually refers to a revision.",action="store_true",default=False)errors=0try:(opts,(repos,txn_or_rvn))=parser.parse_args()look_opt=("--transaction","--revision")[opts.revision]look_cmd="svnlook %s %s %s %s"%("%s",repos,look_opt,txn_or_rvn)errors+=check_cpp_files_for_tabs(look_cmd)except:parser.print_help()errors+=1returnerrorsif__name__=="__main__":importsyssys.exit(main())