外部MantisサーバーとSVNの連携

外部にあるMantisサーバーと、WindowsマシンからアクセスできるSubversionサーバーを連動する仕組みを作ってみました。

概要

MantisにはSVNと連携する機能があります。ただし通常はSVNのコミットトリガーを利用して、SVNとMantisが同じサーバー上にあることを前提としています。

そこでローカルネットワークにあるSVNサーバーを定期的にポーリングして、外部のMantisサーバーに連携する仕組みを作ってみました。

SVNからコミット情報の取得

SVNには基本的にsvnコマンドのlogを利用して取得します。

http://d.hatena.ne.jp/akiraneko/20080211/
MantisとSubversionとの連携

この辺の記事をベースにしています。

Mantisへの連動

通常MantisサーバーからSVNにデータを取りにいく流れになりますが、インターネット上のMantisサーバーから、ローカルネットワーク上のSVNなのでこの手が利用できません。

今回はローカルネットワーク上の別マシンからSVNサーバーに情報を取得して、MantisサーバーにPOSTで送信するようにしました。

<?php

// 設定
$svn_url_top = 'http://192.168.0.100/svn/projectsA';
$svn_url_project = 'http://192.168.0.100/svn/projectsA/trunk';
$mantis_url = 'http://www.example.jp/mantis/core/svncheckin.php';
$trac_url = 'http://192.168.0.100/trac/projectsA/changeset/';
$svn_path = '"C:\Program Files\Subversion\bin\svn"';

// 処理リビジョン番号取得
$no = file_get_contents("cnt.txt") - 0;
echo "no = $no<br />\n";

// 最新リビジョン取得
$exec = "$svn_path log --incremental -v -r head $svn_url_top";
$out = exec( $exec,$output );
$last = substr( $output[1], 1, 10 ) - 0;
echo "最新リビジョン<br />\n";
var_dump($output);

// 処理と最新を比べる
if( $last < $no ){
	// すでに最新なので完了
	exit;
}

// 処理リビジョンのログ取得
$output = null;
$exec = "$svn_path log --incremental -v -r $no $svn_url_project";
$out = exec( $exec,$output );
echo "処理リビジョン<br />\n";
var_dump($output);

// TracのURLを付与
$output[] = $trac_url . $no;

// 配列の連結と、UTF-8に変換してPOST形式に変換
$output = implode( "\n", $output );
$output = mb_convert_encoding($output, "UTF-8", "sjis-win");
$post = "comment=" . urlencode( $output );

// データ送信
$res = do_post_request($mantis_url, $post );
echo "送信結果<br />\n";
var_dump($res);

// リビジョンカウントアップ
$no++;
$file = fopen("cnt.txt", "w");
fwrite($file, $no);
fclose( $file );

// POST送信関数
function do_post_request($url, $data, $optional_headers = null) 
{ 
   $params = array('http' => array(
                'method' => 'POST',
                'content' => $data
             ));
   if ($optional_headers !== null) {
      $params['http']['header'] = $optional_headers;
   }
   $ctx = stream_context_create($params);
   $fp = @fopen($url, 'rb', false, $ctx);
   if (!$fp) {
      throw new Exception("Problem with $url, $php_errormsg");
   }
   $response = @stream_get_contents($fp);
   if ($response === false) {
      throw new Exception("Problem reading data from $url,
      $php_errormsg");
   }
   return $response;
}

いきなりコードですが、SVNコマンドを利用して最新リビジョン番号を取得。最新が増えていたらコメントを取得。

最後にMantisへPOSTしてあげています。
また、コメントの最後にTRACのチェンジログのURLをつけてあげています。TRAC使っているんだったらMantisいらないじゃんって思うかもしれませんが、チケット管理はMantisでTRACSVNのログを見るビュアーとして利用しています、、、

Mantis側

上記のsvncheckin.phpが新規で作成したものになります。セキュリティーの関係でローカル以外からは実行できないようになっているので、もろもろはずします。

<?php
# Mantis - a php based bugtracking system

# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
# Copyright (C) 2002 - 2007  Mantis Team   - mantisbt-dev@lists.sourceforge.net

# Mantis is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Mantis is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Mantis.  If not, see <http://www.gnu.org/licenses/>.
	# See the README and LICENSE files for details

	# --------------------------------------------------------
	# $Id: checkin.php,v 1.5.2.1 2007-10-13 22:35:16 giallu Exp $
	# --------------------------------------------------------
	global $g_bypass_headers;
	$g_bypass_headers = 1;
	require_once( dirname( dirname( __FILE__ ) ) . DIRECTORY_SEPARATOR . 'core.php' );


	# Make sure this script doesn't run via the webserver
	# @@@ This is a hack to detect php-cgi, there must be a better way.
	if ( isset( $_SERVER['SERVER_PORT'] ) ) {
//		echo "checkin.php is not allowed to run through the webserver.\n";
//		exit( 1 );
	}

	# Check that the username is set and exists
	$t_username = config_get( 'source_control_account' );
	if ( is_blank( $t_username ) || ( user_get_id_by_name( $t_username ) === false ) ) {
//		echo "Invalid source control account ('$t_username').\n";
//		exit( 1 );
	}

//	if ( !defined( "STDIN" ) ) {
//		define("STDIN", fopen('php://stdin','r'));
//	}

	# Detect references to issues + concat all lines to have the comment log.
	$t_commit_regexp = config_get( 'source_control_regexp' );
    $t_commit_fixed_regexp = config_get( 'source_control_fixed_regexp' );

	$t_comment = $_POST['comment'];
	$t_issues = array();
	$t_fixed_issues = array();
//	while ( ( $t_line = fgets( STDIN, 1024 ) ) ) {
//		$t_comment .= $t_line;

	if ( preg_match_all( $t_commit_regexp, $t_comment, $t_matches ) ) {
		for ( $i = 0; $i < count( $t_matches[0] ); ++$i ) {
			$t_issues[] = $t_matches[1][$i];
		}
	}

	if ( preg_match_all( $t_commit_fixed_regexp, $t_comment, $t_matches) ) {
		for ( $i = 0; $i < count( $t_matches[0] ); ++$i ) {
			$t_fixed_issues[] = $t_matches[1][$i];
		}
	}

	# If no issues found, then no work to do.
	if ( ( count( $t_issues ) == 0 ) && ( count( $t_fixed_issues ) == 0 ) ) {
//		echo "Comment does not reference any issues.\n";
//		exit(0);
	}

	# Login as source control user
	if ( !auth_attempt_script_login( $t_username ) ) {
//		echo "Unable to login\n";
//		exit( 1 );
	}

	# history parameters are reserved for future use.
	$t_history_old_value = '';
	$t_history_new_value = '';

	# add note to each bug only once
	$t_issues = array_unique( $t_issues );
	$t_fixed_issues = array_unique( $t_fixed_issues );

	# Call the custom function to register the checkin on each issue.
	foreach ( $t_issues as $t_issue_id ) {
		if ( !in_array( $t_issue_id, $t_fixed_issues ) ) {
			helper_call_custom_function( 'checkin', array( $t_issue_id, $t_comment, $t_history_old_value, $t_history_new_value, false ) );
		}
	}

	foreach ( $t_fixed_issues as $t_issue_id ) {
		helper_call_custom_function( 'checkin', array( $t_issue_id, $t_comment, $t_history_old_value, $t_history_new_value, true ) );
	}

	exit( 0 );
?>

とりあえず元のcheckin.phpからチェック部分をはずして、標準入力からの取得をPOSTからの取得に変更しているだけです。

このままだとセキュリティー的に厳しいですので、もう少し対策してください!
私の場合にはBASIC認証をかけて、IPアドレス制限をかけているのでここは適当に済ませています。

自動実行

上記まででコミットされた状態からチェック用のPHPを実行するとMantisに反映されるところまで完成しました。
定期的に実行されるように設定します。

今回はWindows上で実行します!
開発機は通常SVNにアクセスできる場所にありますもんね

実行は標準のタスクを利用します。

ほとんどの人がタスクは細かい設定ができないと思っていると思いますが、実は毎分も実行できます!
上記のように一日一回、0時0分にタスクが実行するように設定します。

詳細を選択

上記のような設定をします。
ポイントはタスクを繰り返し実行で継続時間を24時間にする。
間隔は1分にしていますが、本当は3分とか5分ぐらいで十分だと思います。

上記の設定によって、一日1回0時から24時間の間1分間隔で実行します。
ものすごくわかりにくいですねー

実行スクリプト

PHPを直接実行するのではなく、バッチファイルをかませて実行したいと思います。

check.bat

c:
cd c:\cron\svn

C:\xampp\php\php check.php > c:\cron\svn\out.txt

こんな感じで内部はそのままPHPで実行しています。

ウインドウを表示しないで実行

上記のままだと一瞬ダイアログが表示されますので、されないように一工夫。

SvnCheck.vbs

Dim WShell
Set WShell = WSCript.CreateObject("WScript.Shell")
WShell.Run "c:\cron\svn\check.bat",0

上記のようにVBスクリプトで実行するとウインドウが表示されなくなります。

感想

かなり便利です。チケットドリブンで開発しているので、Mantisでチケット登録。誰かが自分にアサインを変更。作業後にSVNをコミット。コミットログからMantisに連動。実装済みのチケットをチェックしてTRACSVNチェンジログをみて作業内容の確認+動作確認。確認後にチケットを完了に落とす!

上記のような感じで開発していけます