多人数開発で Git を使う場合の環境構築

こんにちは、インフラやってる sotarok です。最近、社内でも「sotarok は そーたろっくと読む」という誤解が広がっていましたので改めて自己紹介しますと、sotarok と書いて「そーたろー」または「そーたろー・けー」と読みます。ロックしてないのでよろしくお願いします。
今日は、Git の話です。

GREE ではずっと Subversion を使っているという話を、以前開発環境の話をしたときに少し触れたことがあります。Subversion での運用方法も、GREE では割と面白い運用をしているのでその話もどこかでしたいのですが、まあ、それは今回は置いておきましょう。どこかで聞いてください。
GREE もその昔 CVS から Subversion に移ったのですが、時代は流れるもので、いよいよ Git 化という流れがきています。Subversion と Git の違いを今更あえて挙げることもないとは思いますが、やはり、ローカルにリポジトリがあるということや速くて賢いマージが非常にメリットとなっていて、ブランチを活用して他の人と共通のモジュールを開発するときでも気兼ねなく手元のコードを変更していけるということが一番大きいと思います。

一方で、会社で開発をすすめる際には、

  • 個別に SSH で push / pull し合うのはきついので、そうはいっても中央集約的なサーバがほしくなる
  • 柔軟性(自由度)がありすぎて、運用ルールを決めないとチーム開発で力を発揮できない

後者に関しては、最近では Git Flow などのツールも出てきて、一般的な運用ルールをツール化しようという動きもありますが、このあたりはその会社やチームのこれまでの開発の進め方によって最適なモデルがあると思います。これは、また別の機会に話題にしたいと思います。

今日は、運用の話は一旦おいておいて、とりあえず、大人数で開発をすすめるための Git サーバを構築する際に、よくある色々な細かい環境構築の話をしようと思います。Git サーバ (gitosis) 自体の構築の話は、他所でも多く語られているため流します。ここで扱うのは、「構築ました」の次の「色々細かい要望あるけど、どうしよう」の段階の話です。

gitosis での環境構築

Git のサーバを立てるとなると、いくつか選択肢はありますが、GREE では、私が個人的にも何度か構築経験のある gitosis を使いました(*1)。 最近は普通に aptitude とかで入ります。

gitosis の構築

gitosis の構築は簡単です。squeeze であれば aptitude で 0.2 がインストールされます。ちなみに、Git 自体は 1.7.2 を使用しています。

$ sudo aptitude install gitosis

あとは gitosis-init して管理者の公開鍵を登録するだけです。

$ sudo -H -u gitosis gitosis-init < /path/to/id_rsa.pub

こうすると、 /srv/gitosis に、gitosis のためのディレクトリが作成され、管理者がここから gitosis-admin.git というリポジトリを clone できるようになります。この gitosis-admin.git を使って Git リポジトリの管理を行います。ちなみに、ここにある repositories ディレクトリが、ホスティングする Git リポジトリが格納されるディレクトリになりますが、ここには bare リポジトリしか格納されないので、なにか編集するには必ず clone する必要があります。

$ sudo ls /srv/gitosis
drwxr-xr-x .
drwxr-xr-x ..
lrwxrwxrwx .gitosis.conf -> /srv/gitosis/repositories/gitosis-admin.git/gitosis.conf
drwx------ .ssh
lrwxrwxrwx git -> /srv/gitosis/repositories
drwxr-xr-x gitosis
drwxr-xr-x repositories

gitosis 自体の構築の話や、リポジトリの管理方法は、他のブログやウェブサイトを参考にしてください。

メール通知の設定

よく Subversion サーバ等で、コミットにフックして開発者のMLにメールを流す、といったことがされていますが、Git でもやはりそのあたりをしたくなります。Git の場合は、push にフックします。
メール通知のためのスクリプトは、Git に添付されています。 /usr/share/doc/git/contrib/hooks/post-receive-email がソレです。これを使うだけでメール通知できます。試しに、gitosis-admin.git のコミットにフックしてメール通知できるようにしましょう。

$ sudo cp /srv/gitosis/repositories/gitosis-admin.git/hooks/post-receive.sample /srv/gitosis/repositories/gitosis-admin.git/hooks/post-receive
$ sudo vim /srv/gitosis/repositories/gitosis-admin.git/hooks/post-receive

#. /usr/share/doc/git-core/contrib/hooks/post-receive-email
↓コメントを外す。また、git-core ではなくて git に変更。
/usr/share/doc/git/contrib/hooks/post-receive-email

$ sudo chmod a+x /usr/share/doc/git/contrib/hooks/post-receive-email

送信元アドレスなどは、gitosis ユーザの .gitconfig に設定しておくと変更することができます。設定項目は、以下の通りです。

  • hooks.mailinglist : コミットフックのメーリングリストのアドレス
  • hooks.announcelist : tag 切ったときに通知されるメーリングリストのアドレス
  • hooks.emailprefix : subject の先頭につけるもの
  • hooks.envelopesender : 送信者アドレス
  • hooks.showrev : 後述

例えば、次のように設定します。

$ sudo su - gitosis
% git config --global --init
% git config --global hooks.mailinglist git-ml@example.com
% git config --global hooks.announcelist git-ml@example.com
% git config --global hooks.envelopesender git@example.com
% git config --global hooks.showrev "git show -C %s; echo"

% git config --list --global
hooks.mailinglist=git-ml@example.com
hooks.announcelist=git-ml@example.com
hooks.envelopesender=git@example.com
hooks.showrev=git show -C %s; echo

メール通知の commit log に diff を含める

デフォルトのメール通知では、push された時に含まれるコミット (ローカルで複数のコミットがある場合、複数のコミットログがあるため) の一覧が本文に含まれますが、ここに diff も表示したいと思う場合があります。
hooks.showrev では、メール本文生成時に、コミットログをなめる部分で使われるコマンドを指定することができます。デフォルトでは、「git rev-list --pretty --stdin $revspec」だけで、単にコミットログが出力されます。

例 (*2):

 Log -----------------------------------------------------------------
commit 8d79a2f8894c3765ab709a8f30b30055ec50c470
Author: Sotaro KARASAWA 
Date:   Wed Jan 26 01:06:22 2011 +0900

   test: fixed accesable in php 5

commit 3a6e46c13021e7f389e243580ddb12fa2d789fd9
Author: Sotaro KARASAWA 
Date:   Wed Jan 26 00:36:39 2011 +0900

   test: fixed for php 5

前項のように、

hooks.showrev=git show -C %s; echo

と設定すると、コミットごとに diff を出力することができます。

例:

 Log -----------------------------------------------------------------
commit 8d79a2f8894c3765ab709a8f30b30055ec50c470
Author: Sotaro KARASAWA 
Date:   Wed Jan 26 01:06:22 2011 +0900

    test: fixed accesable in php 5

diff --git a/test/Plugin/Cachemanager/Plugin_Cachemanager_Localfile_Test.php b/test/Plugin/Cachemanager/Plugin_Cachemanager_Localfile_Test.php
index b6b8dfd..15ebc80 100644
--- a/test/Plugin/Cachemanager/Plugin_Cachemanager_Localfile_Test.php
+++ b/test/Plugin/Cachemanager/Plugin_Cachemanager_Localfile_Test.php
@@ -10,8 +10,9 @@
  */
 class Ethna_Plugin_Cachemanager_Localfile_Test extends Ethna_UnitTestBase
 {
-    var $ctl;
-    var $cm;
+    public $ctl;
+    public $cm;
+    public $cm_ref;
 
     function setUp()
     {
@@ -31,6 +32,7 @@ class Ethna_Plugin_Cachemanager_Localfile_Test extends Ethna_UnitTestBase
 
         $plugin = $ctl->getPlugin();
         $this->cm = $plugin->getPlugin('Cachemanager', 'Localfile');
+
     }
... 以下略

hooks テンプレート

上記の設定で、gitosis-admin.git に push があったときにメール通知されるようにはできました。
しかし、新しいリポジトリが追加された場合はどうしましょう。新しいリポジトリがつくられるたびに Git のサーバに入り hooks/post-receive を編集するなどという運用は嫌ですね。Git では、新規にリポジトリを作成したり、clone したときに適用されるディレクトリの skel をつくっておくことができるようになっています。このディレクトリは、以下です。

/usr/share/git-core/templates/

ということで、ここの hooks ディレクトリに、先ほど gitosis-admin.git に置いた post-receive スクリプトを置いておきましょう。これで、新規にリポジトリを追加した際も、自動的にhook スクリプトの設定がされます。

$ sudo cp /srv/gitosis/repositories/gitosis-admin.git/hooks/post-receive /usr/share/git-core/templates/hooks

gitweb

ブラウザからリポジトリの一覧を見たい!という要望があったため、gitweb で見られるようにしています。

$ sudo aptitude install gitweb

gitweb に関する設定は簡単です。リポジトリの含まれるディレクトリを指定するだけです。あとはApacheのVirtualHostで何かアクセスできるようにしておくと良いでしょう。ドキュメントルートは /usr/share/gitweb/ です。

$ sudo vim /etc/gitweb.conf
$projectroot = "/var/cache/git";
↓変更
$projectroot = "/srv/gitosis/repositories";

$ sudo vim /etc/apache2/conf.d/gitweb
適当に VirtualHost を設定

ちなみに gitweb の見た目を GitHub 風のスタイリッシュなデザインにする theme がお気に入りです。

pre-commit hook どうしよう問題

pre-commit hook とは、コミット直前に実行されるフックスクリプトで、通常、コミットしようとしているファイルに問題がないかをチェックするため等に使われます。PHPで言えば、例えば php -l で PHP の文法のミスが無いかをチェックしたり、といった具合ですね。
Subversion のときは、Subversion サーバ1つに pre-commit hook を仕込んでおけばすべてのコミットに hook して、文法の間違いを含むファイルのコミットを防ぐことができました。また、このサーバの hook script を更新すれば、新たなルールの追加などに対応することできました。Git では各々の開発環境でコミットが実行されます。push のときにすべてのコミットオブジェクトをチェックして弾くというのも手ですが (pre-receiveとかで)、そもそもコミットの時点で言ってくれよ!という感じですので、各環境の pre-commit で防ぎたいですね。

となると、問題は以下の2つになります。

  • どうやって pre-commit を設定させるか
  • pre-commit の hook スクリプト自体の更新をどう管理するか

どうやって pre-commit を設定させるか

pre-commit を設定させるには、各自 clone したディレクトリの .git/hooks/pre-commit を設定しなければなりません。これは各自の開発環境の hooks テンプレート (先述の) に配置しておきます (このテンプレートは、init だけでなく、clone をしたときにも .git ディレクトリ以下に設置されます)。
ということで、各自の開発環境の /usr/share/git-core/templates/hooks/pre-commit にファイルを配置しておくことにします。
多くの場合、開発環境のセットアップなどはソコソコ自動化されてると思いますので、全員がセットアップ時に必ず実行するようなスクリプトなどで、このファイルを設置するようにしておけば良いでしょう。GREE は共通開発環境を apt で入れるので、git のパッケージの postinst で設定しています。

このファイルの中身は、具体的には、次のようになっています。git-hooks-pre-commit が、pre-commit hook の実体です。

#!/bin/bash

/path/to/gree/misc/hooks/git-hooks-pre-commit
php_status=$?
if test $php_status -ne 0
then
    echo " git pre-commit hook error"
    echo " please fix the error and retry commit"
    cat <

pre-commit の hook スクリプト自体の更新をどう管理するか

pre-commit hook は、そうは言っても色々変更がかかります。環境のセットアップ時に入れたスクリプトを更新したときに、全員に apt で更新しろと通達してやってもらうのはちょっと無理があります。
そこで、pre-commit として配置させるスクリプトと、チェックを行うスクリプト本体は管理を別にしておきます。
pre-commit は前項の通り環境セットアップ時に設置されればOKで、チェックを行うスクリプトは、よく更新されるリポジトリ (共通で使っているディレクトリ構成, ここでは /path/to/gree) の中で管理し、pre-commit hook からは、それを呼び出すだけの実装にしました。

実際のフック処理は、git-hooks-pre-commit に記述しておき、成功したら 0、失敗したらそれ以外のステータスで終了することにしておきます。hook でチェックするルールが増えたら、このスクリプトを更新しておきます。 /path/to/gree 自体は、メインの開発リポジトリになっていて、これは色々な人によって日々更新されるため、ここにコミットしておけば自然と更新される、という手はずです。

フックスクリプト本体は、以下のようなスクリプトです。とりあえず PHP の文法チェックだけ行うようになっていますが、このファイルの実装次第でいろんなモノをチェックできますね。

#!/usr/bin/php
 /dev/null', $output, $return);
$against = $return == 0 ? 'HEAD' : '4b825dc642cb6eb9a060e54bf8d69288fbee4904';

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

$filename_pattern = '/\.php$/';
$ignore_file_patterns = array(
    '/skel\..+\.php$/',
);
$exit_status = 0;

foreach ($output as $file) {
    if (!preg_match($filename_pattern, $file)) {
        // don't check files that aren't PHP
        continue;
    }
    foreach ($ignore_file_patterns as $ignore_file_pattern) {
        // ignore
        if (preg_match($ignore_file_pattern, $file)) {
            continue 2;
        }
    }
    if (!file_exists($file)) {
        // delete file-
        continue;
    }

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

exit($exit_status);

運用の話

Git で運用をしようと思うと、いくつかの課題が上がります。

  • ブランチの運用どうするか

    • ローカルブランチは勝手に切ればいいけど、勝手な名前づけてブランチ切りまくって push/pull されまくると厳しい
  • リリース・デプロイとの関連

    • 誰のリポジトリのどこをリリースして良いバージョンとするか。リリースブランチの作り方などをどうするか。
  • Gitサーバを介さない個人間での push/pull は許容するのか

などなど。

このあたりは、これまで、チームでどのような運用がなされてきたか、によって色々異なると想います。Git は使うけど、全体としては Subversion を git-svn で使いながら(つまり、ローカルでの作業ができる、という恩恵に限られるが、それだけでもよくなる、という考え方ですね)、という運用方法等も考えられますね。
GREE でのチームごとの運用とリリース・デプロイに関しては、長くなってしまうので次の機会にします。

まとめ

色々細かい話がありましたが、

  • gitosis の設定
  • 共通環境で Git 使う場合のメーリングリスト通知設定
  • ブラウザで見たい問題
  • pre-commit hook 問題

の話をしました (超アラカルトですね)。

実際に始まってしまえば、あとは個々のトラブルシューティングというか、ケーススタディになってくると思います。慣れ親しんだツールからやり方がかわると誰でも、よくわからないうえにトラブってそれ自体がストレスになってしまいますが、それを乗り越えれば天国が待っている、ということもあるかもしれません。

ケーススタディ的な話もまた別の機会にしたいと思います。

そんなことで、是非、Git サーバたてて、チームで Git で開発してみてくださいね。

*1: Gitorious という、GitHub のクローンであるオープンソースプロジェクトもありますが、こちらは複雑かつ重いので今回はやめておきました

*2: さすがに社内のログを掲載はできないので、ethna のログ拝借 :p

Author: sotarok

自己紹介自己紹介自己紹介自己紹介自己紹介 自己紹介自己紹介自己紹介自己紹介自己紹介 自己紹介自己紹介自己紹介自己紹介自己紹介 自己紹介自己紹介自己紹介自己紹介自己紹介 自己紹介自己紹介自己紹介自己紹介自己紹介