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

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

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

あんま知られてないけど,PHP界隈でも

っていう,アドベントカレンダーやってたみたいです.ぜんぜんしりませんでしたそしてちらっと読んだけどあまりおもしろくn(r


で,その中にちょっと面白げな記事を見つけたので,紹介.

git の pre-commit hook を使って,エラーチェック

だいたいはテスト走らせてコミットとか,実行させてからコミットとかするけど,ちょっとしたところの修正だから確認もせずにコミットしてしかもよりによってそれがエラー起こしてたりとか,まぁ,たまにありますよね.

そのために,コミット直前の pre-commit にフックして,PHP Lintを走らせ,その結果次第でコミットをなかったことにしちゃったりしませんか,という趣旨の話.


で,具体的には,

  • コミットされたファイルのPHPの文法チェックしたりする実行ファイルを用意
  • プロジェクトの,.git/hooks/pre-commit で実行させる

の2つの準備が必要でする.

コミットされたファイルの文法チェック


これは,Web Advent 2008 / Don’t Commit That Error をそのまま使ってしまいます.

#!/usr/bin/php
<?php

$output = array();
$return = 0;
exec('git rev-parse --verify HEAD 2> /dev/null', $output, $return);
$against = $return == 0 ? 'HEAD' : '4b825dc642cb6eb9a060e54bf8d69288fbee4904';

exec("git diff-index --cached --name-only {$against}", $output);

$filename_pattern = '/\.php$/';
$exit_status = 0;

foreach ($output as $file) {
    if (!preg_match($filename_pattern, $file)) {
        // don't check files that aren't PHP
        continue;
    }

    $lint_output = array();
    exec("php -l " . escapeshellarg($file), $lint_output, $return);
    if ($return == 0) {
        continue;
    }
    echo implode("\n", $lint_output), "\n";
    $exit_status = 1;
}

exit($exit_status);


ちなみに,これを,プロジェクトの .git/hooks/pre-commit にそのまま貼り付けて 「chmod -x pre-commit」 とかしてもOK.

でも,git init したときにすでに pre-commit の中にシェルスクリプト(とPerlのコード)がかかれてたりして,よくわからないからこれ全部PHPにしちゃうのもなんかな,と思ったので,今回は ~/.git/hooks/pre-commit として作成して,実行権限を付加.

プロジェクトの,.git/hooks/pre-commit を編集

を編集して,こちらも実行権限を付加.

ファイルの先頭(shebangのあと)に,

~/.git/hooks/pre-commit
if test $? -ne 0
then
    exit 1
fi

を追加.

そうすると

実行時にsyntaxエラーがあると

% git commit -m " hook test"
PHP Parse error:  syntax error, unexpected $end, expecting ',' or ';' in hoge.php on line 12

Parse error: syntax error, unexpected $end, expecting ',' or ';' in hoge.php on line 12
Errors parsing hoge.php


とか表示されてコミットされない.

今回はPHPでやったけど

PerlRubyでもできそうな感じですな.

ついでにsvnのhooksでもできそうですな.こんど仕込んでおこうと思います.


(追記)

ついでにメッセージにChangesetのURLも含めるようにしてみた

githubのお遊びついでに、forkしてみた。

オリジナルのバージョンだと、チケットclose時のコメントに、チケットIDしか表示してくれないから、それだとChangesetへ飛びにくいので、ChangesetのURLを含めるようにしてみた。


てなわけで、Subversionもつかってて、チケットはTracで管理してて、でもGithubもたまに使いたいよ!って人は*1、ChangesetのURLも表示される

を使ってみるのもいいかもしれませんよ!

にしても

gitの(っていうかgithubの)forkって楽チンだなあ。すげえ。。
subversionと概念が違うっていうのがようやくわかった気がする。

*1:結構しぼってんなw

Github のコミットにHookしてTracのチケットをクローズしたり

最近Githubをようやく使い始めたsotarokです。

普段は、Subversionを使っているし、nequalの開発も主にSubversionを使っていて、BTSというかTODO管理というかにTracを使ってる。で、Tracの便利なところはSubversionと連携してくれるから、コミットログに「fixed #123」とか書けばチケットの#123をクローズしてくれたりするところなんだけど、Githubにソースを移行して相変わらずチケット管理をTracに任せているとこのHookが使えなくて不便。

そんなわけでGithubのコミット(push)にHookしてTracのチケット更新できるようにする。

GithubPluginをインストール

コレ。

gitからもってきてsetup.py。

% git clone git://github.com/davglass/github-trac.git
% cd github-trac 

simplejsonが入ってない場合は、easy_installで入れてから。入ってるならいらないけど。

% easy_install simplejson 
% sudo python setup.py install

trac.ini いじる

[components]
github.* = enabled

[github]
apitoken = tekitounamojiretsu

apitoken のとこは適当な文字列入れる。

で、Apache再起動などさせると(mod_pythonで動かしてる場合)、管理コンソールのプラグインってとこに、

な感じの表示が。

Service Hooks に URLを設定

これはGithub側の設定。リポジトリの、Admin->Service Hooksに色々ある。
で、最初はTracのほうに入れるのかとおもって試してたけどうまくいかないから(後述)、davglass/github-trac · GitHub の説明に書いてある通り、一番上の Post-Receive URLs に、

http://trac.nequal.jp/github/tekitounamojiretsu

みたいなカンジで、tracのURL+ /github/ + apitokenに設定した文字列、を入力。

実際にコミットする

git commit -a -m "fixed #123" とかして、 git push origin master などしてgithubにpushしたタイミングで、Hookが動きますめでたしめでたし。

pullとかは試してないなあ。きっとなるんじゃないかな(てきと

Service HooksのTracんとこに入力したときはデータが来てないぽい

一応調べてみたんですが。GithubPlugin-0.4-py2.4.egg の github/github.py に

    def processCommitHook(self, req):
        self.env.log.debug("processCommitHook")
        status = self.closestatus
        if not status:
            status = 'closed'

        data = req.args.get('payload')
        self.env.log.debug("data: %s" % data) # これ足した

        if data:
            jsondata = simplejson.loads(data)

            for i in jsondata['commits']:
                self.env.log.debug("status: %s" % status)
                self.hook.process(i, status)


        if self.autofetch:
            repo = Git(self.repo)

            try:
              repo.execute(['git', 'fetch'])
            except:
              self.env.log.debug("git fetch failed!")

で、Tracのログをdebugに設定して動かしてみてたら、Post-Receive URLsに入れたほうでHookが動いたときは、JSONで色々情報が渡されてるけど、TracのほうにURLとTokenを入力しても、それはどうやらきてなかった。だから当然Hookは動かないんだけど。でもちゃんと tracurl/github/token文字列 ってURLにアクセスはきてるんだけどね。
なんでだろ?このTracって何に使うんだろう? github 側の対応ミスとか?どなたか知ってたら教えてください。

まぁいいやとりあえずPost-Receive URLsで運用しちゃお。


というかんじでした。