今回は、対象の構成管理がMercurialであるか否かに関わり無く、Mercurialを併用する ことで開発効率を向上させる手法を説明します。
題材としてフリーソフト/オープンソースソフトを取り上げますが、ここで説明する手法は、「 Mercurial以外の構成管理ツールの使用を指定されている開発」といった、( 残念ながら)良くある シチュエーションでも適用可能です。
なお本稿では、フリーソフト/オープンソースソフトの総称として、以降FLOSS (Free/Libre and Open Source Software)の略称を使用します。
No Patch, No Life
世間的にFLOSSが認知されて随分時間も経ちましたし、その数も相当なものになりました。
その気になれば、CVSやSubversionで運用されているリポジトリにアクセスして、最新成果をいち早く試してみることも、これまでの開発過程の記録を参照することも容易にできます。
しかし、折角ソースコードが手元にあっても、CVSやSubversionのような中央集約形式の構成管理ツールの場合、いざこれらFLOSSのソースに独自の変更を加えようとすると、以下のような問題が発生します。
コミット権限が無いので、自分の作業を記録できない
その場限りの変更なら問題ありませんが、継続的に変更作業を実施する=ある程度の規模の変更を行う場合、「 変更」そのものの構成管理ができないのは致命的です。
新しい版が出た際に、独自の変更分の反映が面倒
FLOSSの場合、比較的短期間でバグが修正された新しい版がリリースされることが多々あります。
折角新しい版で品質が向上していても、独自変更部分は構成管理されていませんので、新しい版に対して適用するのは非常に面倒です。
結局のところ、こと構成管理に限って言えば、圧倒的に利用者の立場が弱い といえます。
「独自の変更」という言い方をすると:
今時のFLOSSは、特に改変しなくても十分使えるでしょ?
と考える方もいることでしょう。
しかし、「 独自の変更」は必ずしも特定用途(e.g.:特殊な環境・デバイス・設定での使用等)向けの専売特許ではありません。折角のソースファイル公開なのですから:
プログラミングの学習の際の、コメント付与や改変実施
問題解析のための、ログ出力追加
といったことをやらない手はありません。
リポジトリのハイブリッド化
まずはCVSなりSubversionを使用して、対象の成果物を「チェックアウト」し、続いて成果物をチェックアウトしたディレクトリをMercurialのリポジトリとするために、"hg init"を実行します。
これでこのディレクトリは、Mercurialのリポジトリでありながら、他の構成管理ツール(CVS等)の管理下にもあることになります。一種のハイブリッド(混成)リポジトリと言っても良いでしょう。
しかし、この時点ではまだ単に「Mercurialのリポジトリである」というだけで、Mercurial側では何も管理していない状態ですので、チェックアウトした成果物を初期バージョンとしてMercurialに登録します。
まずはCVSやSubversionの管理情報格納ファイルを無視するために、以下のような".hgignore"ファイルを記述します。
syntax: re
# CVS との併用の場合
^CVS/
/CVS/
# Subversion との併用の場合
^\.svn/
/\.svn/
".hgignore"ファイルは、ワーキングディレクトリの最上位ディレクトリに配置します(それ以外の場所では機能しません) 。「 ワーキングディレクトリの最上位ディレクトリ」は"hg root"が表示するディレクトリです。
上記以外にも、以下のような設定が有用でしょう。
syntax: glob
# Emacs のバックアップファイル
*~
# C/C++ のオブジェクトファイル
*.o
# Java のクラスファイル
*.class
"syntax: re"や"syntax: glob"は、指定した無視対象パターンの文法を指定するもので、"re"は正規表現(regular expression)の、"glob"はシェル形式のパターンマッチングを意味します。
後は"hg add"によってファイルを登録するのですが、今回のようなケースでは、"-A"オプション付きの"hg commit"を使用するのが便利です。
"hg commit"に"-A"オプションを指定することで、以下の点で無指定の場合と振る舞いが変化します。
未知のファイルの追加:
"hg status"で"?"表示されるファイルに対して、"hg add"が指定されたものとして振舞います。
見失ったファイルの除外:
"hg status"で"!"表示されるファイルに対して、"hg remove"が指定されたものとして振舞います。
最後に、今後の対象FLOSSの更新時に備えて、更新専用のブランチを作成します。
コマンド1
% hg branch base-source
% hg tag initial-source
%
"base-source"は名前付きブランチのブランチ名、"initial-source"はブランチ元となるリビジョンに対するタグ名ですので、Mercurialが許す範囲で任意のものを指定して構いません。
ブランチ名を指定して"hg branch"コマンドを実行した場合、それは:
次に "hg commit"されるチェンジセットを使って、指定した名前のブランチを作成せよ
という指示を意味します。つまり、CVSでの"tag -b"コマンド等と違い、"hg branch"コマンド実行時点ではブランチは作成されません。
また、"hg tag"コマンドは、タグ付け操作そのものに新規チェンジセットを必要とします("hg tag"実行後に"hg log"出力を見てみましょう) 。
逆にそのことを利用して、"hg branch"+"hg tag"を実行することで、最も低コスト且つ運用に支障無くブランチを作成することができます(もちろんタグ付け無しでブランチを作成することも可能です) 。
ワーキングディレクトリを"hg update"で最初のソース取り込み時点に戻したなら、独自変更への事前準備は完了です。念のため、ワーキングディレクトリの状態が"default"ブランチであることも確認しておきましょう。
コマンド2
# ワーキングディレクトリの更新
% hg update initial-source
xx files updated ......
# 引数なし "hg branch" でブランチ確認
% hg branch
default
%
これで対象FLOSSのソース取り込みは完了です。独自の変更を実施し成果を都度"hg commit"、という通常の開発と同じワークフローに戻ります。
図1 独自変更の実施
継続的な独自変更の適用
先述したように、比較的短期間でバグが修正されたり新しい機能が追加されるのがFLOSSの大きな特徴です。
その特徴は一方で、最新版に対する独自変更の適用実施が度々発生することを意味します。
ここでは、独自変更対象のFLOSSが新しい版をリリースした際に、独自変更を適用する手順を説明します。
なお、以下の手順は全てハイブリッドリポジトリ上のもので、独自変更に関する操作は全て"hg commit"済みと仮定します。
まずは、"hg update"を実行することで、"base-source "ブランチに移動し、Mercurial以外の構成管理ツールで最新の成果に更新します。例えば、CVSなら"cvs update"を実行します。
ファイルの変更以外に、新規に増えたファイル・不要になって削除されたファイルの取り込みも、先に説明した"hg commit-A"コマンドを使用することで、最新版のソースとしてまとめてチェンジセットを作成することができます。
コマンド 3
% hg update -C base-source
:
% cvs update ...
:
% hg commit -A
:
%
これまでは「タグ付け」を除外すれば枝別れが無かったのですが、この操作により、「 最新版」( "base-source")と「独自変更」( "default")とが完全に枝分かれしたことになります。
図2 FLOSS最新版の取り込み
後は、「 独自変更」と「最新版」のマージを行えば、「 最新版」への「独自変更」適用が完了します。
コマンド4
# 「独自変更」側に戻る
% hg update -C default
:
# 最新ソースとのマージ
% hg merge base-source
:
% hg commit
:
%
これ以後は、対象とするFLOSSの更新の都度、同じ操作を繰り返すことになります。
図2 最新版とのマージ
Mercurial Queueによる変更管理
前述した説明では、独自変更が固定的なチェンジセットとして記録される手法でしたが、実はMercurialにはMercurial Queue(MQ)と呼ばれる拡張機能があり、これまでに説明したのと同様の効果を、より便利に得ることができます。
MQは非常に多機能ですので、ここでは簡単な使用方法についての説明に留めます。詳細に関しては、オフィシャルサイトの解説ページ や、Bryan O'Sullivan氏によるMQ解説ページ を参照してください。特に、変更そのものの構成管理に関しては、"hg qinit -c"や"hg qcommit"のヘルプを参照してください。
重要: MQはDOS改行設定下では適切に動作しない模様ですので、改行設定には注意してください。
初期化
MQはMercurialのイクステンション(extension)として提供される機能ですので、有効にするには以下の記述を設定ファイルに追加する必要があります。
[extensions]
hgext.mq=
"hg showconfig"コマンドの出力に"extensions.hgext.mq="行が表示されないようでしたら、記述を追加した設定ファイルの場所等を確認してください。
上記設定が完了したなら、MQ機能を使用するリポジトリで"hg qinit"を実行します。
次に、未コミットの変更(ファイル追加・削除を含む)が無いことを確認した上で、"hg qnew"を実行します。この時、変更内容を端的に表すような名前を指定してください。ここでは仮に"use_network"とします。
以上で独自改変の準備は整いました。手順に間違いが無ければ、以下のような出力が得られるはずです。
コマンド5
% hg qinit
% hg qnew use_network
% hg qseries
use_network
% hg qapplied
use_network
%
変更の実施
この時点で"hg parents"等を実行してみると、作成した覚えの無い以下のようなチェンジセットが目に付くことでしょう。
コマンド6
% hg parents
changeset: 2:3547823edb73
tag: qtip
tag: use_network
tag: tip
tag: qbase
parent: 0:87d13763f6e4
user: FUJIWARA Katsunori
date: Tue Jul 06 14:10:26 2008 +0900
summary: [mq]: use_network
%
このチェンジセットは、直前の"hg qnew"によって作成されたものです。"hg log -p"等で変更内容を出力させてみると、何も変更していない ことがわかります。
それでは、早速「独自変更」を実施しましょう。但し、MQを使用する場合、"hg commit"ではなく"hg qrefresh "を実施します。
"hg qrefresh"を実施したなら、再度"hg parents"出力を確認してみましょう。
コマンド7
% hg parents
changeset: 2:083ec762dc76
tag: qtip
tag: use_network
tag: tip
tag: qbase
parent: 0:87d13763f6e4
user: FUJIWARA Katsunori
date: Tue Jul 06 14:18:11 2008 +0900
summary: [mq]: use_network
%
概ね同じなのですが、微妙に違いますね?
更に"hg log -p"等で変更内容を出力させてみると、"hg qrefresh"前の変更内容に相当する出力がある点で、明らかに先程表示させたチェンジセットとは異なります。
実は、"hg qnew"で作成されたチェンジセットは、"hg qrefresh"を実行する度に再構築されるのです。
適用対象の更新
独自変更の対象となるFLOSSの最新版の取り込みの際には、それに先立って"hg qpop"を実行します("hg qrefresh"で全ての変更が取り込まれていることを確認してください) 。
"hg qpop"実行により、"hg tip"出力には見覚えのあるチェンジセットが表示されることでしょう。つまりMercurialの構成管理上は、先程の独自変更は無かったこと になっているのです。
それでは折角の独自変更作業が無駄になってしまったのか?
いいえ、独自変更作業の成果はきちんと残っています。
まずは、先の実行では同じだった"hg qseries"と"hg qapplied"の出力が、"hg qpop"実行後は違うものとなっていることを確認してください。
コマンド8
% hg qseries
use_network
% hg qapplied
%
これは以下の意味を持っています。
"use_network"という変更作業の内容は、MQとして記録している(=qseries出力)が、作業領域への適用(=qapplied出力)はされていない
記録が残っているということで、安心してFLOSSの最新版取り込みを行ってください。
変更の再適用
FLOSSの最新版の取り込みが完了したなら、独自変更を再適用しましょう。
"hg qpush"を実行してみてください。
コマンド9
% hg qpush
applying use_network
Now at: use_network
%
もし、FLOSSの最新版が、あなたの独自拡張と衝突するような変更を含んでいた場合、以下のようなメッセージが表示されます。
コマンド10
% hg qpush
Hunk #1 FAILED at 0
1 out of 1 hunk FAILED -- saving rejects to file hello.txt.rej
patch failed, unable to continue (try -v)
patch failed, rejects left in working dir
Errors during apply, please fix and refresh use_network
%
この場合は、最新版に適用可能なように変更を実施して、改めて"hg qrefresh"すれば良いのです。
以後は、"hg qpop"⇒最新版取り込み⇒"hg qpush"(⇒更新+"hg qrefresh")の繰り返しになります。
おわりに
6回にわたって、Mercurialを使った「分散リポジトリ」の活用方法について説明させて頂いた訳ですが、いかがでしたでしょか?
CVSやSubversionといった中央集約形式の構成管理ツールの利用経験があると、最初は何となく面倒な印象のある分散リポジトリですが、ちょっとした発想の切り替えをするだけで、非常に自由度の高い運用を可能にしてくれるのが分散リポジトリの利点です。
Mercurialには、連載において説明したもの以外にも、日常的な作業をより便利にするための様々な機能が提供されていますし、実装言語がPythonであることから、「 必要であれば作る(or変更する) 」という方法も選択可能です。
より詳細を知りたい方は、是非ともオフィシャルWikiページ や、Bryan O'Sullivan氏による解説ページ を参照してみてください。