2009年10月15日

Debianのwebwml CVSをgitで処理する (コミッタと内部処理編)

翻訳作業者編に引き続き、今度は、どうやって実装しているのかなどに興味のある方向けのお話。まずは概念図。

                  +-------------+ パッチメール送付
                  |HTTPユーザ環境| → debian-wwwメーリングリスト
                  +-------------+
                    ↑ clone/pull
git.debian.or.jp ----------------+ clone/pull +-------------+
|  CVS→gitの同期(cron)          |  →         |sshユーザ環境|
|  gitリポジトリの提供(ssh, HTTP) | ←         +-------------+
+--------------------------------+ push
    push↑ ↓pull
kmuto環境 ------------------------------+
|  gitリポジトリのクローン(ssh)          |
|  git→CVSの同期(webwml-patch-commmit) |
+---------------------------------------+

作成したコードはSubversionリポジトリ「https://svn.debian.or.jp/repos/webwml-sync/trunk」にある。

git.debian.or.jp上で行う「CVS→gitの同期」は、syncというシェルスクリプトとwebwml-git-committer.rbというRubyスクリプトが担当している。準備としては、作業ディレクトリ上に、Debian.orgのCVSリポジトリをread only(pserver)経由でwebwmlという名前で置き、同期用にGitリポジトリからクローンした作業ツリーをwebwml-gitworkという名前で同様に置く。

そして、syncスクリプトで、CVSとGit作業ツリーをまず最新に更新し、CVS内のファイルをrsyncに-uオプションを付けて同期処理をする。続いてwebwml-git-committer.rbスクリプトをGit作業ツリーに対して実行し、追加されたものを調査する。既存ファイルの更新や削除であればgitが面倒を見てくれるのだが、新規についてはこちらで指示しなければならない。git ls-filesを使えばリポジトリ管理外のものが?マークで表されるので、これを新規ファイルと見なしてgit addで追加する。ここまでできたら、git commitの-aオプションでまだステージしていない残存ファイル、つまり既存ファイルが更新されたものや削除されたものを含めて全部コミットする。最後にpushを実行してリポジトリに反映し、CVS→gitの処理は完了だ。syncはヒューリスティックに15分ごとにcron実行させている。一応安全のためにロックなども作るようにしている。

Gitリポジトリでは今のところ複雑な処理はかけていない。HTTP向けも単にリポジトリのbareを外に見せているだけ。フックとしてpost-receiveでプッシュされたコミットメールをdebian-wwwメーリングリストに送るのと、post-updateでHTTP向けにgit-update-server-infoを実行しているくらい。今後はパスのチェックやエンコーディングテストなども入れたほうがよいかとは思っている。

さて、翻訳作業者にいただいた成果は、速やかにCVSに反映したい。 プッシュされたものならプルで、パッチなら適用・コミット・プッシュして、CVSに反映したいコミット履歴ができたとしよう。Debian.orgの(Aliothにあるリポジトリにssh経由で書き込み可能な)CVSツリーをwebwml、Gitの作業ツリーをwebwml-work、パッチ準備ディレクトリにpatches、パッチ済みディレクトリにpatchedといった形で用意しておく。

ここでwebwml-patch-commitの出番となる。これはシェルスクリプトで(Rubyで最初書いていたのだけれど、外部コマンドをたくさん呼ぶならシェルスクリプトのほうが扱いやすいという判断に至った)、履歴からパッチを生成し、パッチの確認・CVSへの適用・コミットをインタラクティブに進めていくもの。

実行すると、CVSとGitの最新への更新を行った後、指定のGit履歴ハッシュ値(lastcommitというファイル、または引数で指定)をgit format-patchに-oオプションでパッチ生成ディレクトリ指定を付けて渡す。format-patchは指定の履歴「より後」のコミットを「数値-*.patch」のファイル名で作ってくれる。

後は、各パッチの処理。先頭行にパッチ作成者情報があるので、これを見てCVS→Gitの同期に使っているエージェントコミッタだったらそのパッチは無視する(「パッチ済み」に移動する)。 そうでないなら、まずはパッチをCVSに適用テスト(patch --dry-run)してみる。ここでエラーが出たら実行自体を止めるか、そのパッチは保留にして先に進むかを選ぶことになる。

正常に適用できるようなら、コミットログと「適用する」「適用しない(保留)」「パッチをページャで表示」「適用しない(適用済みに移動)」の質問を提示して(実際には「Apply? [y/n/p/s]」と簡略化してるけど)、処理をコミッタが決める。普通は「適用する」を選ぶことになるわけだが、これでCVSにパッチが適用され、Gitに入れたコミットログをそのまま使ってCVSをコミットする。適用したパッチは「パッチ済み」ディレクトリに移動。Gitで行われたファイルの追加/削除については、git whatchangedを使って5列目がAあるいはDかどうかを調べて判断している。

「パッチ済み」のものは同時にその履歴ハッシュ値をlastcommitに書き込むようにしている。こうすることで、次回webwml-patch-commitを実行するときにどこまで処理していたかを確認に回る必要がなくなるわけだ。

仕組みとしてはだいたいこれでおしまい。日本語限定というわけではなく、変数targetlangをいじればほかの言語にも流用できるようにしてある。万が一のときを考えて、foolproof的な仕組みはいくつか入れているし、今後も追加したいと考えているけど、シェルスクリプトだとそろそろ厳しいなぁと感じるのも事実。数日で作ったにしてはちゃんと機能しているし、コミッタとしての作業はずっと楽になったし、翻訳作業者も広く募れる体制になったし、とりあえずは良かった、Git万歳ということで。

Debianのwebwml CVSをgitで処理する (翻訳作業者編)

Debian.orgのWebサイトのコンテンツ「webwml」の管理は、いろいろなしがらみから未だにCVSを採用している。Subversionなどの別のSCMにしようという提案は何度か行われてはいるものの、作業には管理チームの手助けが必要なほかに、各翻訳作業者(世界各国各言語)がそのSCMになじめるかという問題があり、なかなか実行には踏み切れていないというのが現状だ。

しかし、翻訳やコミットをしている立場からすると、CVSはつくづく扱いづらい。何をするにもサーバに問い合わせるから遅く、コミッタ権限を持っていないとパッチなりファイルなりを送り付ける以外何もできない。コミッタ側もそのパッチ処理がだるいので億劫になる。

ということで、CVSと同期したGitリポジトリを作って翻訳作業者に公開し、コミットされたものをCVSに半自動でコミッタ(私)が順次処理できるような仕組みを作ってみた。Gitは分散環境に適したSCMで、リポジトリへの書き込み権限を持たない作業者でもローカルなコンテンツ管理を行える(たとえばネットワークから切断された環境でも履歴やコミットを利用できる)。

翻訳作業者がDebian JP Projectのメンバなら(sshで入ることのできるアカウントを持っているなら)、次の書式でツリーをクローンできる(gitスイートはgit-coreパッケージに入っている)。

$ git clone ユーザ名@git.debian.or.jp:/git/webwml.git

englishとjapaneseを格納したwebwmlディレクトリができるので、このjapaneseのほうで作業していけばよいわけだ。翻訳では先頭行の#use wml::debian::translation-checkの値を英語版のCVSリビジョンに合わせる必要があるが、これはenglish下の同名ファイルの最終行に書かれているはず。たとえば翻訳追従系の作業なら、1つのファイルの作業が終わるたびに、

$ git add ファイル名パス
$ git commit -m "sync with 原文リビジョン"

を繰り返していく。変更内容を読み返したいなら、「git diff」や「git log」などを使う。詳細については参考文献を参照いただきたい。 ひととおり作業に区切りができたらこれをgit.debian.or.jpにプッシュする。

$ git push

これで、これまでに行ったコミットがgit.debian.or.jpに送られ、Debian JP Projectのdebian-wwwメーリングリストにコミットログが流れて、後はCVSコミッタの処理待ちとなる。本当はレビューのプロセスも入れたいところだけど(たとえばドラフト用のブランチ切って、レビューアがレビューしてmasterへマージ、とするとか)、今のところはアジャイルに即コミット、間違いがあれば後で更新という形で進めている。 逆に作業ツリーの中身をgit.debian.or.jpのものと同期するには、次のようにpullすればいい。

$ git pull

Debian JP Projectメンバでない場合、つまり直接クローンやコミットをできない場合は、Web経由でクローンし(プッシュはできない)、パッチを適宜debian-wwwメーリングリストに送付いただくことになる。Web経由でのクローンの書式は次のとおり。webwml-gitという作業ツリーができる。

$ git clone http://git.debian.or.jp/git/webwml-git.git

プッシュができないだけで、作業ツリーの編集やコミットは自由にできる。同期は先と同様に「git pull」。 行った変更からパッチを生成するには、git format-patch(またはgit-emailパッケージのgit send-email)を使う。git format-patchを次のように実行すると、git.debian.or.jpのほうにまだマージされていない部分についてのパッチファイル(*.patch)がコミットごとに用意されるので、このパッチファイル群を添付などで送ればいい。メール本文の中に入れるとエンコーディングが変わるなどしてコミッタ側で扱いづらいので、添付ファイルのほうが望ましい。

$ git format-patch origin

なお、debian-wwwメーリングリストはsubscribeしているメールアドレスでないと投稿はできないようになっているので注意されたい。

ここまでの内容で、翻訳作業者はひととおりのことができるようになるはず。 翻訳作成・更新待ちのものについては、Debian.org Web Japanese translation statusなども参照するとよいだろう。



/

Git初心者にお勧めできる入門書。Gitのエッセンスはすべてここに込められている。



/

奇しくも同名になってしまったものの、こちらはGit開発者濱野氏ご自身による1冊。『入門git』では省略している内部構造や各種のコマンド、フックについても詳細に解説している。『入門Git』を『入門git』と並べて持っておきたい。