Enforcer
I've written a script called enforcer that can be used in a Subversion repository as a pre-commit hook in order to validate code before it is committed into the repository. This allows the repository administrator to enforce all sorts of evil rules. Doing this from scratch is pretty painful, so the script allows you to write hooks into specific commit events such as "this line was added/removed," "this file was modified/added/removed," etc.
From the doc string:
Enforcer is a utility which can be used in a Subversion pre-commit hook script to enforce various requirements which a repository administrator would like to impose on data coming into the repository.
A couple of example scenarios:
-
In a Java project I work on, we use log4j extensively. Use of System.out.println() bypasses the control that we get from log4j, so we would like to discourage the addition of println calls in our code.
We want to deny any commits that add a println into the code. The world being full of exceptions, we do need a way to allow some uses of println, so we will allow it if the line of code that calls println ends in a comment that says it is ok:
System.out.println("No log4j here"); // (authorized)
We also do not (presently) want to refuse a commit to a file which already has a println in it. There are too many already in the code and a given developer may not have time to fix them up before commiting an unrelated change to a file.
-
The above project uses WebObjects, and you can enable debugging in a WebObjects component by turning on the WODebug flag in the component WOD file. That is great for debugging, but massively bloats the log files when the application is deployed.
We want to disable any commit of a file enabling WODebug, regardless of whether the committer made the change or not; these have to be cleaned up before any successful commit.
What this script does is it uses svnlook to peek into the transaction is progress. As it sifts through the transaction, it calls out to a set of hooks which allow the repository administrator to examine what is going on and decide whether it is acceptable. Hooks may be written (in Python) into a configuration file. If the hook raises an exception, enforcer will exit with an error status (and presumably the commit will be denied by th pre-commit hook). The following hooks are available:
- verify_file_added(filename)
- called when a file is added.
- verify_file_removed(filename)
- called when a file is removed.
- verify_file_copied(destination_filename, source_filename)
- called when a file is copied.
- verify_file_modified(filename)
- called when a file is modified.
- verify_line_added(filename, line)
- called for each line that is added to a file.
(verify_file_modified() will have been called on the file beforehand)- verify_line_removed(filename, line)
- called for each line that is removed from a file.
(verify_file_modified() will have been called on the file beforehand)- verify_property_line_added(filename, property, line)
- called for each line that is added to a property on a file.
- verify_property_line_removed(filename, property, line)
- called for each line that is removed from a property on a file.
In addition, these functions are available to be called from within a hook routine:
- open_file(filename)
- Returns an open file-like object from which the data of the given file (as available in the transaction being processed) can be read.
In our example scenarios, we can deny the addition of println calls by hooking into verify_line_added(): if the file is a Java file, and the added line calls println, raise an exception.
Similarly, we can deny the commit of any WOD file enabling WODebug by hooking into verify_file_modified(): open the file using open_file(), then raise if WODebug is enabled anywhere in the file.
Note that verify_file_modified() is called once per modified file, whereas verify_line_added() and verify_line_removed() may each be called zero or many times for each modified file, depending on the change. This makes verify_file_modified() appropriate for checking the entire file and the other two appropriate for checking specific changes to files.
These example scenarios are implemented in the provided example configuration file enforcer.conf.
When writing hooks, it is usually easier to test the hooks on commited transactions already in the repository, rather than installing the hook and making commits to test the them. Enforcer allows you to specify either a transaction ID (for use in a hook script) or a revision number (for testing). You can then, for example, find a revision that you would like to have blocked (or not) and test your hooks against that revision.