Subversion hook php framework
Subversion provides very powerful hook system which allows extending it with custom logic. Hook is a command line script which is executed on selected repository event receiving metadata as command line arguments and providing results within output and exit code.
With a help of hook script you can validate commit log message or even submitted changes before saving them into repository and notify user if there are any errors.
If you have php on your server with a help of special PHP class your very powerful subversion hook might look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/usr/bin/php <?php require '/var/svn/common-hooks/CommitTests.php'; putenv('PATH=/usr/bin'); new CommitTests($argv[1], $argv[2], array( 'LogMessageLength' => array(3), 'TabIndents' => array(array('php', 'phtml')), 'TrailingSpaces' => array(array('php', 'phtml', 'ini')), 'SvnProperties' => array(array( 'php' => array('svn:keywords=Revision', 'svn:eol-style=LF'), 'phtml' => array('svn:keywords=Revision', 'svn:eol-style=LF'), 'html' => array('svn:eol-style=LF'), )), )); |
This script have following pre-commit validation rules:
- Minimum log message length is 3 characters
- Tab indents are not allowed in *.php and *.phtml files
- Trailing spaces are not allowed in *.php, *.phtml and *.ini files
- *.php and *.phtml files should have following svn tags set: svn:keywords=Revision, svn:eol-style=LF
- *.html files should have svn tag svn:eol-style=LF
Download CommitTests.php file.
Thanks so much, this works great! Following your example, it was very easy to edit your tests and add my own. I added a test that verifies PHP syntax is correct before committing (for certain file extensions only), and also added some logic that will copy the committed files to a specific location for further testing (only if all other tests pass. I wanted to run this copy action in the postcommit hook, but the CommitTests class doesn’t seem to work properly in the postcommit hook. Specifically, it doesn’t seem to know what files were committed).
Here’s the method to check syntax:
***********************
class CommitTests
{
[...snip...]
//path to php binary
public static $PHP = “/usr/local/php-cgi/bin/php”;
/**
* Tests if the committed files pass PHP syntax checking
*
* @param string $msg error messages placeholder
* @param array $filetypes array of file types which should be tested
* @return bool
*/
protected function _testPHPSyntax(&$msg, array $filetypes=array()){
$result = true;
$files = $this->_getChangedFiles($filetypes);
$tempDir = sys_get_temp_dir();
foreach ($files as $file => $extension) {
$content = $this->_getFileContent($file);
$tempfile = tempnam($tempDir, “stax_”);
file_put_contents($tempfile, $content);
$tempfile = realpath($tempfile); //sort out the formatting of the filename
$output = shell_exec(self::$PHP.’ -l “‘.$tempfile.’”‘);
//try to find a parse error text and chop it off
$syntaxErrorMsg = preg_replace(“/Errors parsing.*$/”, “”, $output, -1, $count);
if ($count > 0) { //found errors
$result = false;
$syntaxErrorMsg = str_replace($tempfile,$file,$syntaxErrorMsg); //replace temp filename with real filename
$msg .= “\t[$file] PHP Syntax error in file. Message: $syntaxErrorMsg\n”;
}
unlink($tempfile);
}
return $result;
}
[...snip...]
}
***********************
And run it with:
new CommitTests($argv[1], $argv[2], array(
‘PHPSyntax’=>array(array(‘php’)), //array contains file extensions to check
….etc….
));
***********************
Thanks so much for sharing! This framework saved me a lot of time.
Adam
Np Adam, thanks for your comment. I am going to update class soon with more tests and probably add post-commit and other hooks support, but than it’s not (pre)CommitTest imho, but something more.
Btw for checking php files for syntax errors (lint) you could use php_check_syntax() function which would make lint cross-platform and more universal (no need to hardcode php binary path).
Thanks for the reply Aleksandr… I’m looking forward to the updates! You’re right about using php_check_syntax() and that was the way I had initially developed the test, but I quickly found out that the function was removed in php 5.0.5. The php manual says to use “php -l …” instead, so the extra grunt work was needed =\
There was one other change I had to make to CommitTests.php because of our environment. “svnlook” is not part of the user’s path… so the change adds a path to svnlook:
public static $SVNLOOK = “/usr/local/subversion/bin/svnlook”;
then each command would use the variable, i.e.:
$cmd = self::$SVNLOOK.” log -t ‘{$this->_transaction}’ ‘{$this->_repository}’”;
Maybe the $SVNLOOK variable could just default to “svnlook” without the path, then the full path could be added only if needed…? Just an idea.
Thanks again for sharing and updating… it’s much appreciated!
Adam