肉とビールとパンケーキ by @sotarok

少し大人になった「肉とご飯と甘いもの」

エラーのあるファイルはコミットしない(Subversion編)

git編はこちら

先日はgitでやったんだけどせっかくだからsvnのコミットにもフックしてみた.

svnのほうで苦労したのは,フックのタイミングにしても,ローカルにファイルの実体のあるgitと違って,フックをかけるサーバと実体のあるクライアントマシンが別ってところで,コミットするタイミングで初めてサーバにファイルの実体が来るところ.
で,色々探ってみたものの,コミットされるファイルが一時的におかれる場所があったりするわけではないので,以下の要領でテストを走らせてみたりする*1

  • svnlook changed で変更されたファイル一覧を取得
  • svnlook cat で変更されたファイルの中身を取り出し,一時ファイルに保存
  • その一時ファイルに対して php -l
  • 一時ファイルを削除する
  • ファイルにエラーがあったら,コミットをうけつけず,エラーを表示して終了


流れをみただけで面倒くさそうでとってもうんざり...w

で,いったとおり,gitとの違いは,ファイルの実体がリポジトリのあるマシンと同じ場所にあるわけではない・・・というところで.
svnlookコマンドでは,そのコミットを「トランザクション」という単位で扱っているらしく,まぁこのへんはhelpとか見ればわるか.

PHP の Lint 動かすファイル

実体は以下のファイルです.これを,SVNENVのhooksとかに設置(たとえば,リポジトリ foo の /var/svn/foo/hooks/phplint とか).

#!/usr/bin/env php
<?php

$exitsatus = 0;

$txn = escapeshellarg($argv[1]);
$repo = escapeshellarg($argv[2]);
$cmd = "/usr/bin/svnlook changed -t $txn $repo";
exec($cmd, $changed_files, $return);

if (count($changed_files) > 15) {
    exit(0);
}

$result = array();
foreach ($changed_files as $file) {
    $filename = preg_replace("/^\s*[a-z]+\s+/i", "",  $file);

    if (substr($filename, -4) == ".php") {
        $tmp_filename = "/tmp/svn-{$argv[1]}-" . str_replace("/", "--dir--", $filename);

        $esc_filename = escapeshellarg($filename);
        $cmd = "/usr/bin/svnlook cat -t $txn $repo $esc_filename > $tmp_filename";

        // get contents of the file and out to tmp file
        exec($cmd, $cat_output, $return);

        // run lint
        exec("php -l " . escapeshellarg($tmp_filename) . " 2>/dev/null", $lint_output, $return);
        if ($return) {
            $result[] = str_replace(
                array("/tmp/svn-{$argv[1]}-", "--dir--", "\n"),-
                array(pack("c", 0x1B) . "[1;41m", "/", pack("c", 0x1B) . "[0m \n"),
                trim(implode("\n", $lint_output)) . "\n");
            unset($lint_output);
            $exitsatus = 1;
        }

        unset($cat_output);
        unlink($tmp_filename);
    }
}

fwrite(STDERR, implode("\n", $result));
exit($exitsatus);

SVNENV/hooks/pre-commit を編集

以下の1行を追加*2

/var/svn/foo/hooks/phplint $TXN $REPOS || exit 1

あ,上のほうで,

REPOS="$1"
TXN="$2"

という行,これがコメントアウトされていたら,これはコメントを削除しておく.

コミットしてみる

無駄に色とかつけてみたw 自分の確認環境(CentOS 5.2)以外での動作はしらない..

問題点とか

ファイルが大量にコミットされたときに 生成→テスト→削除 を繰り返してると死んでしまう気がしたから,ファイル数が15以上のときはチェックしていない.

・・・のだけど,削除されたファイルとか,ディレクトリとかを判断していないので,大量のファイル削除+エラーのあるファイル1つとか,そういうコミットのされかたをしたときすり抜けちゃう.

まぁスクリプト自体改良の余地がありまくりだからそのへんはよしなに(ぉ


あとマージしたときとかどうなんの,って話もあるけど,どうなるかはためしてない...

参考にした

*1:もっと良い方法があったら教えてください m(__)m

*2:あ,先日のシェルではifでexit status判定したりとかしてたけど,||でいけちゃうんですな.あぁUnixの基礎知識・・・