Subversion allows you to run commands, called hook scripts, whenever actions take place in your repository. The most useful of these that I’ve found is the pre-commit hook, which allows you check a transaction for problems before it is committed.
Word Aligned has an excellent introduction on setting up pre-commit hooks and includes a small example. I use a more complex script on my own server, and thought it might be of use to somebody else. It verifies the following:
- The commit message is at least 10 characters long
- The commit message is different from the previous message (handy when hit the up arrow and accidentally try to commit again)
- Tabs are used for indentation instead of spaces (sorry, I’m a tabs guy – you can change it if you need to)
- PHP files don’t have syntax errors
- C and C++ files don’t have any digraphs or trigraphs
#!/usr/bin/python import sys # These should point to the respective commands SVNLOOK = '/usr/bin/svnlook' PHP = '/usr/bin/php' MIN_MESSAGE_LENGTH = 10 # Gets a command's output def commandOutput(command): import subprocess process = subprocess.Popen(command.split(), stdout = subprocess.PIPE) return process.communicate()[0] # 0 is stdout # Returns an array of the changed files' names def getChangedFiles(svnPath, transaction): # Run svnlook to find the files that were changed output = commandOutput('%s changed %s --transaction %s' % (SVNLOOK, svnPath, transaction)) # The output from svnlook looks like the following: # U folder/file1.cpp # A folder/file2.cpp # where U means updated and A means added def changed(fileName): return len(line) > 0 and line[0] in ('A', 'U') changedFiles = [line[4:] for line in output.split('\n') if changed(line)] # svnlook inserts an empty line, so output.split() will have an extra # line with nothing in it - ignore the last lines if they're empty while 0 == len(changedFiles[-1]): changedFiles = changedFiles[:-1] return changedFiles # Checks that the message is a minimum length def checkMessageLen(svnPath, transaction, minLength): # Run svnlook to get the message log output = commandOutput('%s log %s --transaction %s' % (SVNLOOK, svnPath, transaction)) if len(output) < minLength: sys.stderr.write('Message must be at least %d characters\n' % minLength) return 1 else: return 0 # Checks that the message is different from the last message def checkRepeatMessage(svnPath, transaction): # Run svnlook to get the message log output = commandOutput('%s log %s --transaction %s' % (SVNLOOK, svnPath, transaction)) currentMessage = output lastRevision = int(transaction.split('-')[0]) - 1 # Empty repositories have no messages if lastRevision 0: sys.stderr.write('Regex \'%s\' matched \'%s\' on line(s): %s\n' % (regex, fileName, badLines)) return matches # Checks a PHP file for syntax errors def checkPHPSyntax(svnPath, transaction, fileName): # Run svnlook to get the file contents output = commandOutput('%s cat %s %s --transaction %s' % (SVNLOOK, svnPath, fileName, transaction)) # Run PHP syntax checking on the file contents command = (PHP, '-l') process = subprocess.Popen(command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE) output = process.communicate(fileContents)[0] process.wait() if 0 != process.returncode: sys.stderr.write('Found syntax errors in \'%s\':' % fileName) # Print the offending lines that were returned from php -l for line in output: sys.stderr.write(line) return 1 # Error found return 0 # No errors svnPath = sys.argv[1] transaction = sys.argv[2] files = getChangedFiles(svnPath, transaction) errorCount = checkMessageLen(svnPath, transaction, MIN_MESSAGE_LENGTH) errorCount += checkRepeatMessage(svnPath, transaction) for file in files: # Prefer tabs for indentation # Skip .txt files if not file.endswith(".txt") and not file.endswith(".sql"): errorCount = errorCount + stringsInFile(svnPath, transaction, [' {4}'], file) # Check PHP syntax if file.endswith('.php'): errorCount += checkPHPSyntax(svnPath, transaction, file) # C and C++ files should not use digraphs or trigraphs digraph = '%s|%s|%s|%s|%s' % ('<:', '', ':>', '%:') trigraph = '\?\?[=/\'()!-]' if file.endswith(('.c', '.cpp', '.cxx', '.cc', '.C', '.h', '.hpp')): errorCount += stringsInFile(svnPath, transaction, [digraph, trigraph], file) sys.exit(errorCount)
Damn. This one is good. I will try to upload a Delphi included check here. Maybe it will help others that use space-indentation …
What’s stringsInFile() ?