monotoneを使ってみる

ここではオープンソースの分散バージョン管理ソフトウェアmonotoneの紹介をします。Version 1.0対応。

monotoneとは

monotoneの特徴

monotoneはオープンソースのバージョン管理ソフトウェアです。 公式サイトはhttp://www.monotone.ca/です。

次のような特徴があります。

分散バージョン管理
分散バージョン管理システムです。分散バージョン管理についてはあとで説明します。
単一ファイルのリポジトリ
sqliteのデータベースをリポジトリとして扱うのでリポジトリはファイル一つに収められます。 そのせいもあってmonotoneではリポジトリのことをデータベースとも呼んでいます。
国際化対応
格納するデータはUTF-8で管理されるため日本語なども問題なく扱えます。
使いやすいコマンド
svnなどに似てわかりやすいコマンド体系になっています。 ドキュメントがわかりやすいところもいい点です。
スクリプトによる拡張
設定ファイルがLuaスクリプトになっており、複雑な設定を書いたりカスタムコマンドの追加もできるようになっています。
自動化コマンドによる自動化
ユーザが直接使う通常コマンドの他に、他のプログラムから呼ばれることを意図した自動化コマンドを持っています。 通常コマンドと違いプログラムからパースしやすい出力がされるのでライブラリなどを使わずにmonotoneを操作する他のプログラムを作ることができます。

分散バージョン管理って?

Subversionなど集中型のバージョン管理はリポジトリが一つだけ(たいていはどこかのサーバに)あり、各人がそのリポジトリとやりとりすることで成果物が共有されます。 一方monotoneなどの分散型では、各人がリポジトリを持ちそこに成果を入れていきます。共有は各リポジトリを適宜同期することで行なわれます。

分散型のいいところは、

いつでもコミットできる
マシン毎にリポジトリを持ってるので、外出先でネットワークがつながらないノートPCでの作業などもローカルのリポジトリにコミットしておいてあとで同期することができます。
気兼ねなくコミットできる
個人のリポジトリを持つことになるので、元のリポジトリが誰の物であっても気兼ねなく自分のリポジトリにコミットできます。 集中型だと自分の書き込み権限がないとか俺カスタマイズだから共有するようなものじゃないとか理由でコミットできないことがありますが、分散型では自分だけのリポジトリに入れておくことができます。

一方分散型の欠点は、

分岐が発生する
あるリビジョンの子として別なリポジトリで入ったリビジョンが複数出来ることがあり、同期した時には分岐になってしまいます。 集中型だと集中管理されているので分岐が発生しないようにできるのですが、分散型だとあとで同期を取る形式なので分岐の発生は避けられません。
リビジョン番号がわかりづらい
集中型だとコミットされた時にリビジョン番号を連番で増やしていくことができますが、分散型だとやっぱりあとで同期するので連番にはできません。 衝突するわけにもいかないので「a2e9e2035cc3a53a3507a190f890f70675014101」といったハッシュをリビジョン番号(というよりリビジョンID)にしますが、長い上に見た目的に法則性もないので人間には扱いづらくなってしまっています。

こんな感じで利点・欠点はあるものの、気軽にいじりやすい性質はオープンソースソフトウェアでは特に有効でしょう。

用語

このページで使う知っておいてほしい用語をちょっくら説明。

リポジトリ
バージョン管理情報やら実際のファイル内容などを入れておく場所のこと。monotoneではデータベースとも呼ぶ。
データベース
monotoneではリポジトリ、またはリポジトリファイルのこと。リポジトリとしてsqliteのデータベースを使うのでデータベースと呼んだりする。このチュートリアルではほぼリポジトリと呼ぶが、どっちも同じと思っていい。
リビジョン
バージョン管理の単位。あるバージョンのこと。
コミット
新しいバージョンをリポジトリにつっこむこと。
ブランチ
バージョンツリーにつけられる名前。プロジェクト名のようにも使われる。

インストールとセットアップ

Windowsにインストール

公式サイトにインストーラがあるのでそれを使いましょう。cygwinを使い慣れている人ならcygwinのインストーラからもインストールすることができます。

MacOSXにインストール

公式サイトのインストーラパッケージは頻繁には更新されていません。MacPortsではすぐに最新版が対応されるのでMacPortsで入れましょう。

MacPortsを使える状態で

sudo ports install monotone

とすれば入ります。依存しているboostのインストールにだいぶ時間がかかるので時間がある時にやるといいでしょう。

その他のUnix系OSにインストール

ディストリビューション毎のパッケージマネージャを使って入れましょう。公式サイトに各ディストリビューション用のパッケージについてリンクが張られているのでそこを確認してください。

古いバージョンしか無かったり使っているディストリ用のパッケージが無かった場合はソースから入れることになります。依存しているライブラリは多くが各ディストリにパッケージが用意されているのでそれ程苦労はしないと思います。

x86 Linux用のバイナリは公式に上がっているので、使える場合はそれを使うのも良いでしょう。ただしバイナリのみなのでmanなどはインストールされません。

起動確認

mtn version

と打って

$ mtn version
monotone 1.0 (base revision: unknown)

こんな感じの出力がされれば問題ありません。ここから分かる通りmonotoneのコマンド名はmtnです。

入れたバージョンと違ったり起動できなかったらPATHを確認してください。

環境変数の設定

インストールしたての状態でも動きはしますが、いくつかの環境変数を設定しておくと快適に使えるようになります。

###PATH環境変数

PATHを通しておいた方が便利でしょう。 Windows版のインストーラではAdd monotone to your plathというチェックがあるのでこれを入れておくと自動で設定してくれます。

###CHARSET環境変数

入出力の文字コードを設定します。日本語を使いたい時は設定する必要があります。 環境や好みにもよるんですが、Windowsではcp932((-ShiftJISのことだと思ってかまいません-))、OSXではutf-8、それ以外では環境に合わせてutf-8やeuc-jpを指定しておけばいいでしょう。

内部的には文字列はUTF-8に変換されて記録されているのであとで変更しても大丈夫です。設定しない場合はasciiと認識されます。

###EDITOR環境変数

チェンジログなどを入力するときに起動するエディタを指定します。 好きなエディタの実行ファイル名を指定すればいいでしょう。

注意点として、Windowsでもパス区切りには「\」でなく「/」を使ってください。たとえばメモ帳を設定するには

C:/Windows/System32/notepad.exe

と指定してください。区切りに\を使うとファイルがみつからないというエラーが出てしまいます。

###MTN_MERGE環境変数

ファイルの手動でのマージを行なう時に起動するエディタを指定します。標準では以下のいずれかを指定できます。

設定していなければ、上記のいずれかが使えないか上から順に試してみて使えたらそれを、だめならEDITORで設定したエディタを起動します。 WindowsだとTortoiseSVNやTortoiseGitを入れておくとTortoiseMergeが使われることになりそうです。

これ以外のものを設定したい場合はluaスクリプトを書く必要があります。詳細は省きますがドキュメントのDefault hooksを参考に書きましょう。

とりあえずは何も指定しないでおいてあとで変なのが立ち上がって困った場合はあらためて設定を見直すでいいと思います。

リポジトリとキーの作成

ここから本格的にmtnの使い方に入ります。最初はセットアップから。

リポジトリの作成

まずバージョン管理データを入れるリポジトリを作成します。

mtn db init --db my.mtn

上記のコマンドでmy.mtnというファイルが作られます。これがリポジトリです。

db initはサブコマンドです。これはリポジトリを初期化するサブコマンドで、指定した名前のリポジトリを新しく作ってくれます。dbはデータベースの略ですが、monotoneはリポジトリのことをデータベースと呼ぶためリポジトリ自体を操作するコマンドはdbで始まります。

--db my.mtnはリポジトリファイル指定オプションです。ここではmy.mtnというファイルを指定しています。–dbは略して-dでもかまいません。

リポジトリのファイル名はなんでもいいのですが、拡張子は.mtnか.dbが推奨されています。古くは.dbだったのですが、今は.mtnの方がわかりやすくてよいでしょう。

これでリポジトリが出来たので、基本的にはここにいろいろなデータを格納します。必要なら他のリポジトリを作ったり、いらなくなったらリポジトリを消したりしてもかまいません。リポジトリ自体のバックアップがとりたい時にはここで作ったファイルをバックアップしておくだけで大丈夫です。

キーの作成

作業を始める前にキーも作成しておきます。

monotoneではリポジトリに何か変更を入れるときにいちいち署名をつけるのですが、その署名鍵をキーといいます。 またリポジトリを同期するのに使うアカウントのような役割も果たします。

mtn genkey foobar@example.com

こんな感じでキーを作成できます。「foobar@example.com」のところは自分の名前にします。名前はメールアドレス形式が推奨されています。実際のメールアドレスの必要はないのですが、メールアドレスにすると悩まなくていいかもしれません。

genkeyコマンドを使うとパスワードの設定を求められます。このパスワードを忘れるとキーを作り直しになるので気をつけてください。

$ mtn genkey foobar@example.com
enter passphrase for key ID [foobar@example.com] (...):
confirm passphrase for key ID [foobar@example.com] (...):
mtn: generating key-pair 'foobar@example.com'
mtn: storing key-pair foobar@example.com in 'C:/Users/kumaryu/AppData/Roaming/monotone/keys/'
mtn: warning: 'C:/Users/kumaryu/AppData/Roaming/monotone/keys/foobar@example.com.be3ded446c06da297884a2773114614a200aa367' will be accessible to all users of this computer
mtn: storing public key foobar@example.com in ''
mtn: key 'foobar@example.com' has hash 'be3ded446c06da297884a2773114614a200aa367'

上手くいくとこんな感じでごちゃごちゃとメッセージが表示されます。途中にパス名が書かれてますが、キーはそこに「foobar@example.com.~」といった名前で保存されています1。保存されたキーは他人に奪われるとなりすましをうける可能性があるのでなるべく他人に見えない場所に起きましょう。また、使ってるキーファイルの紛失も作り直しになり多少面倒なことになります。リポジトリとキーファイルは忘れずバックアップをとっておきましょう。

作った後のキーファイルのパスワードを変えたい時はpassphraseコマンドを使います。

mtn passphrase foobar@example.com

古いパスワードを聞かれたあと新しいパスワード入力になります。

ワークスペースでの作業

ワークスペースの作成

リポジトリは各バージョンなどを格納する場所ですが、ワークスペースはその名の通り実際に作業する場所です。

ワークスペースで追加や編集したファイルをリポジトリにコミットしていくのが基本的な作業の流れです。

ワークスペースにしたい適当なディレクトリを作ります。

mkdir hoge

setupコマンドでワークスペースにします。

mtn setup --db my.mtn --key foobar@example.com --branch com.example.hoge hoge

mtn setupがコマンド、--db my.mtnがリポジトリ指定、--key foobar@example.comがキー指定、--branch com.example.hogeがブランチ名指定、hogeがディレクトリ指定です。

setupコマンドは指定したディレクトリを指定した設定での作業場所にするコマンドです。エラーがなければhogeディレクトリ内に_MTNディレクトリが出来ます。_MTNの中にmonotoneが使う設定などが記録されます。

--db my.mtnはリポジトリ指定です。新しいワークスペースでの作業をここで指定したリポジトリに記録していくことを表します。–dbは省略して-dでもかまいません。

--key foobar@example.comのキー指定は、新しいワークスペースでの作業はここで指定したキーの人がやりましたと記録していくことを表します。–keyは略して-kとすることもできます。

--branch com.example.hogeのブランチ名指定は、リポジトリ内にcom.example.hogeブランチとして記録していくことを表します。–branchも-bと略せます。

いずれもあとで変更することもできます。指定を間違った時はhogeディレクトリごと消すかhoge/_MTNディレクトリを消せばやり直せます。

ブランチはプロジェクト名と考えるとそれほど間違いありません。一つのリポジトリには複数のバージョンツリーを入れることができますが、その識別にブランチを使います。 普通ブランチって言うとプロジェクトの別バージョン的な物を言うんじゃね?と思った人もいるでしょう。monotoneのブランチはそれも表します。単にリビジョンにつけるグループ名でしかありません。

ブランチの名前はJavaのパッケージ名のような名前が推奨されています。「ドメイン名を逆にしたもの.プロジェクト名.ブランチ名」といった感じになります。com.example.hogeだとドメイン名がexample.comでプロジェクト名がhogeです。後ろのブランチ名は紛らわしいですが普通の意味で言うブランチ(サブプロジェクト名みたいなもの)です。とくに指定してないので省略します。hogeのfugaバージョンを作りたくなった場合はブランチ名をくっつけてcom.example.hoge.fugaとかいう名前になります。

ただの名前なのでこのフォーマットにのっとる必要はないのですが、他のプロジェクトとかぶると大変めんどうなことになるので区別できる名前としてこの方法で名前をつけるのがいいでしょう。

ワークスペースが出来たらあとはここでファイルを追加していったりするだけです。まずは普通に作業しましょう。

チェックアウト

setupでは新しいプロジェクト用のワークスペースを作りましたが、既にリポジトリ内にあるブランチをワークスペースに書き出すにはcheckoutコマンドを使います。

mtn checkout --db my.mtn --key foobar@example.com --branch com.example.hoge fuga

指定はsetupとほぼ同じですが、これでcom.example.hogeの最新リビジョンをfugaディレクトリに書き出します。fugaディレクトリはワークスペースになっているのでそのまま作業可能です。

最新リビジョンでなく、特定のリビジョンを選択してワークスペースに書き出すには–revisionまたは-rオプションで指定します。

mtn checkout -d my.mtn -k foobar@example.com -b com.example.hoge -r a3cf58d9c62cca23c7cb533338a2ba2279d69c64 piyo

a3cf5~リビジョンをpiyoディレクトリに書き出します。リビジョン指定には選択子(セレクタ;後述)もつかえます。

リポジトリに入っているブランチを見るにはls branchesコマンドを使います。

$ mtn ls branches -d my.mtn
com.example.hoge

ファイルの追加

ファイルはワークスペース内に普通に作ればいいのですが、monotoneに追加しないとバージョン管理はされません。addコマンドで追加できます。

mtn add hallo_hoge.c

ファイル名を指定するだけです。複数のファイルを指定することもできます。

$ mtn add hallo_hoge.c
mtn: adding 'hallo_hoge.c' to workspace manifest

成功するとワークスペースのマニフェストに追加したとメッセージが出ます。マニフェストとは構成ファイル一覧です。 この時点ではまだワークスペースの追加されただけでリポジトリには格納されません。あとでコミットをする必要があります。

サブディレクトリにあるファイルも追加できます。

$ mtn add foo/bar.c
mtn: adding 'foo' to workspace manifest
mtn: adding 'foo/bar.c' to workspace manifest

この場合fooディレクトリ自体を追加したあとに中のfoo/bar.cを追加しています。 ここでわかるとおりディレクトリ自体も管理対象になります。

ディレクトリを直接指定して追加することもできます。

$ mtn add foo
mtn: warning: non-recursive add: Files in the directory 'foo' will not be added automatically.
mtn: adding 'foo' to workspace manifest

ディレクトリの中にファイルがある場合でも自動で追加はされません。–recursiveまたは-Rオプションをつけるとディレクトリ内のファイルを再帰的に追加します。

$ mtn add -R foo
mtn: skipping 'foo', already accounted for in workspace
mtn: adding 'foo/bar.c' to workspace manifest

既に追加されたファイルはスキップします。

.aや.swp、~で終わるファイルなど一部のファイルは通常バージョン管理されないものとして自動的にスキップされます。 強制的に追加したい場合は–no-respect-ignoreオプションを使います。

$ mtn add foo/bar.a
mtn: skipping ignorable file 'foo/bar.a'

$ mtn add --no-respect-ignore foo/bar.a
mtn: adding 'foo/bar.a' to workspace manifest

ワークスペース内に実ファイルは存在するものの、まだ追加されていないファイルはlist unknownまたはls unknownで一覧できます。

$ mtn ls unknown
foo/bar.c
hoge.c

ls unknownで出てくるファイルやディレクトリを全部一気に追加するにはadd –unknownが使えます。

$ mtn add --unknown
mtn: skipping ignorable file 'foo/bar.a'
mtn: adding 'foo/bar.c' to workspace manifest
mtn: adding 'hoge.c' to workspace manifest

ファイルの削除

既に管理下にあるファイルを削除するにはdropコマンドまたはrmコマンドを使います。

mtn drop hello.c

または

mtn rm hello.c

どちらも動作は同じです。これもファイル名には複数のファイルやワールドカードを指定できます。

$ mtn drop hello.c
mtn: dropping 'hello.c' from workspace manifest

ファイルの削除も、追加と同じくコミットするまでリポジトリには反映されませんが、ワークスペース内のファイルは削除されます。 ただし以前のバージョンから変更がないファイルは実際に削除されますが、変更したファイルや追加してまだコミットしていないファイルをdropに指定した場合は管理から外れるだけで実ファイルの削除は行なわれません。

ディレクトリの削除もできますが、空でないディレクトリは削除できません。中のファイルもろとも削除する場合は–recursiveか-Rオプションを使います。

$ mtn drop foo
mtn: misuse: cannot remove 'foo/', it is not empty

$ mtn drop -R foo
mtn: dropping 'foo/bar.c' from workspace manifest
mtn: dropping 'foo/bar.a' from workspace manifest
mtn: dropping 'foo' from workspace manifest

ワークスペースの管理内には入っているが実ファイルがないものはlist missingかls missingで一覧できます。

$ mtn ls missing
hoge.c

実ファイルだけがない物を一気に管理から外すにはdrop –missingを使います。

$ mtn drop --missing
mtn: dropping 'hoge.c' from workspace manifest

逆にファイルだけがない物を復活させるにはrevert –missingを使います。

$ mtn revert --missing
mtn: reverting hello.c

revertについてはあとでもう少し詳しく紹介します。

ファイルの移動・名前の変更

追加したファイルの名前変更や移動を行なうにはrenameコマンドまたはmvコマンドを使います。

$ mtn rename hello.c hoge.c
mtn: renaming 'hello.c' to 'hoge.c' in workspace manifest

hello.cが変更元、hoge.cが変更後の名前です。

移動もできます。

$ mtn rename hello.c foo/hello.c
mtn: adding 'foo' to workspace manifest
mtn: renaming 'hello.c' to 'foo/hello.c' in workspace manifest

hello.cをfoo/hello.cに移動しました。fooディレクトリが追加されてなければ自動で追加されます。

移動先がディレクトリの場合はファイル名は省略できます。

$ mtn rename hello.c foo/
mtn: skipping 'foo', already accounted for in workspace
mtn: renaming 'hello.c' to 'foo/hello.c' in workspace manifest

ファイルの移動や名前変更もやはりコミットするまでリポジトリには反映されません。

ファイルの変更と復帰

追加されているファイルの編集には何も操作は必要ありません。ワークスペース内のファイルを編集するだけで認識されます。

編集したファイルを以前コミットした状態に戻すにはrevertコマンドを使います。

$ mtn revert hello.c
mtn: reverting hello.c

追加したファイル、削除したファイル、移動したファイルもrevertで元リビジョンの状態まで戻すことができます。

ファイル名指定は複数できますが、ディレクトリを指定すると、そのディレクトリ以下のファイル全てが復帰対象になります。 ワークスペースのトップレベルディレクトリを指定すると全てのファイルが巻き戻るので気をつけてください。

上で紹介したように、ワークスペース内の実ファイルだけが消えてるファイルは–missingオプションを指定することで元リビジョンの状態で復活できます。

$ mtn revert --missing
mtn: reverting hello.c

変更点の確認

ワークスペースのまだコミットされてない変更を見るにはstatusコマンドを使います。

$ mtn status
----------------------------------------------------------------------
Revision: 87c1f1fea1ec4f7391f8c0a7469be38460ed0de7
Parent:   a2e9e2035cc3a53a3507a190f890f70675014101
Author:   foobar@example.com
Date:     2011/04/21 9:14:33
Branch:   com.example.hoge

Changes against parent a2e9e2035cc3a53a3507a190f890f70675014101

added    foo
added    foo/bar.c
patched  hello.c

ディレクトリfooとファイルfoo/bar.cが追加され、hello.cが変更されたことが表示されています。

変更されたファイル一覧を出すにはlist changedかls changedも使えます。

$ mtn ls changed
foo
foo/bar.c
hello.c

一覧が出るだけなので普段使うにはstatusの方がわかりやすいでしょう。

具体的な変更点を表示したい場合はdiffを使います。

$ mtn diff
#
# old_revision [a2e9e2035cc3a53a3507a190f890f70675014101]
#
# add_dir "foo"
#
# add_file "foo/bar.c"
#  content [e242ed3bffccdf271b7fbaf34ed72d089537b42f]
#
# patch "hello.c"
#  from [fb9d91f064dbebe633ad4ff33aa1b9ea96a5e5a0]
#    to [2fd1f8818e258058ccf0050676f948799f803a40]
#
============================================================
--- /dev/null
+++ foo/bar.c   e242ed3bffccdf271b7fbaf34ed72d089537b42f
@@ -0,0 +1 @@
+bar
============================================================
--- hello.c     fb9d91f064dbebe633ad4ff33aa1b9ea96a5e5a0
+++ hello.c     2fd1f8818e258058ccf0050676f948799f803a40
@@ -1,4 +1,4 @@
-/* hallo_hoge.c */
+/* hello.c */
 #include <stdio.h>
 int main(int argc, char** argv)

削除されたファイルや追加されたファイルは内容が全て表示されるので長くなりがちです。 特定のファイルだけを指定して変更点を表示することもできます。

$ mtn diff hello.c
#
# old_revision [a2e9e2035cc3a53a3507a190f890f70675014101]
#
# patch "hello.c"
#  from [fb9d91f064dbebe633ad4ff33aa1b9ea96a5e5a0]
#    to [2fd1f8818e258058ccf0050676f948799f803a40]
#
============================================================
--- hello.c     fb9d91f064dbebe633ad4ff33aa1b9ea96a5e5a0
+++ hello.c     2fd1f8818e258058ccf0050676f948799f803a40
@@ -1,4 +1,4 @@
-/* hallo_hoge.c */
+/* hello.c */
 #include <stdio.h>
 int main(int argc, char** argv)

コミットと更新

ワークスペースの変更をリポジトリに入れるにはcommitまたはciコマンドを使います。

mtn commit

または

mtn ci

どちらも意味は同じです。

実行すると環境変数EDITORで指定したエディタが立ち上がります。

変更ログを書いて保存しエディタを閉じるとコミットされます。

hello.cのコメントがファイル名と一致していなかったので修正した。

*** REMOVE THIS LINE TO CANCEL THE COMMIT ***
-- Enter a description of this change above --
-- You may edit the fields below            --
Branch:    com.example.hoge
Author:    foobar@example.com
Date:      2011-04-22T01:30:20

-- Modifications below this line are ignored --
Changes against parent a2e9e2035cc3a53a3507a190f890f70675014101

patched  hello.c

変更ログを書く場所は「** REMOVE THIS LINE TO CANCEL THE COMMIT **」という行より上です。

** REMOVE THIS LINE TO CANCEL THE COMMIT **」の行を削除するとコミット操作のキャンセルできます。

$ mtn commit
enter passphrase for key ID [foobar@example.com] (be3ded44...):
mtn: beginning commit on branch 'com.example.hoge'
mtn: committed revision a3cf58d9c62cca23c7cb533338a2ba2279d69c64

キーのパスワード入力を求められますので入れるとコミットされます。ssh_agentが起動していれば2回目以降はしばらくパスワード入力は省略されます。

ここで表示されてるa3cf58d9c62cca23c7cb533338a2ba2279d69c64が今コミットしたリビジョンのリビジョンIDです。

あるワークスペースでコミットした分を他の同じブランチを参照してるワークスペースに反映させるにはupdateコマンドを使います。

$ mtn update
mtn: updating along branch 'com.example.hoge'
mtn: selected update target a3cf58d9c62cca23c7cb533338a2ba2279d69c64
mtn: [left]  9c5ec80b49fca83e87911fe8d53311e2e4325a15
mtn: [right] a3cf58d9c62cca23c7cb533338a2ba2279d69c64
mtn: updating 'hello.c'
mtn: updated to base revision a3cf58d9c62cca23c7cb533338a2ba2279d69c64

ワークスペース内のファイルが更新されました。

更新の際にワークスペース内のコミットしていない変更とupdateで行なわれる変更が競合した場合はMTN_MERGE環境変数で指定した手動マージプログラムが起動しますので、編集して保存してください。

ログの確認

コミットのログを確認するにはlogコマンドを使います。

$ mtn log
o   ----------------------------------------------------------------------
|   Revision: a3cf58d9c62cca23c7cb533338a2ba2279d69c64
|   Parent:   a2e9e2035cc3a53a3507a190f890f70675014101
|   Author:   foobar@example.com
|   Date:     2011/04/22 10:32:55
|   Branch:   com.example.hoge
|   
|   Changelog: 
|   
|   hello.cのコメントがファイル名と一致していなかったので修正した。
|   
|   Changes against parent a2e9e2035cc3a53a3507a190f890f70675014101
|   
|     patched  hello.c
o     ----------------------------------------------------------------------
|\    Revision: a2e9e2035cc3a53a3507a190f890f70675014101
| |   Parent:   21a501aaf0eb2240d3c591228987ec2c41498b0f
| |   Parent:   a0a4ec6698a9c64c39d0042b8a9ad46ab76561a9
| |   Author:   foobar@example.com
| |   Date:     2011/04/12 9:09:50
| |   Branch:   com.example.hoge
| |   
| |   Changelog: 
| |   
| |   merge of '21a501aaf0eb2240d3c591228987ec2c41498b0f'
| |        and 'a0a4ec6698a9c64c39d0042b8a9ad46ab76561a9'
| |   
| |   Changes against parent 21a501aaf0eb2240d3c591228987ec2c41498b0f
| |   
| |     patched  hello.c
| |   
| |   Changes against parent a0a4ec6698a9c64c39d0042b8a9ad46ab76561a9
| |   
| |     renamed  hallo_hoge.c
| |          to  hello.c
(中略)
o   ----------------------------------------------------------------------
  Revision: 4d0e889f0054bfd21bc1bb44f32a3810214f8923
  Author:   foobar@example.com
  Date:     2011/04/06 18:17:31
  Branch:   com.example.hoge
  
  Changelog: 
  
  ためしにHallo Hoge!を表示するだけのプログラムを作った。
  
  Changes
  
    added    
    added    hallo_hoge.c

今までのログを全て表示します。

多くの場合、全部表示する必要はないのでオプションを指定して絞ります。よく使うのは最新のいくつかだけを表示する–lastオプションです。

$ mtn log --last 1
o   ----------------------------------------------------------------------
|   Revision: a3cf58d9c62cca23c7cb533338a2ba2279d69c64
|   Parent:   a2e9e2035cc3a53a3507a190f890f70675014101
|   Author:   foobar@example.com
|   Date:     2011/04/22 10:32:55
|   Branch:   com.example.hoge
|
|   Changelog:
|
|   hello.cのコメントがファイル名と一致していなかったので修正した。
|
|   Changes against parent a2e9e2035cc3a53a3507a190f890f70675014101
|
|     patched  hello.c

最後の1つだけ表示されます。–last nと指定することで、 最新n個のログだけを表示します。

あるリビジョンの変更だけを表示するには–revision、または-rオプションを使います。

$ mtn log -r 21a501aa
mtn: expanded selector '21a501aa' -> 'i:21a501aa'
mtn: expanding selection '21a501aa'
mtn: expanded to '21a501aaf0eb2240d3c591228987ec2c41498b0f'
o   ----------------------------------------------------------------------
|   Revision: 21a501aaf0eb2240d3c591228987ec2c41498b0f
|   Parent:   49cc9e900d124b31b8e5db73e8797fb9c13c7d96
|   Author:   foobar@example.com
|   Date:     2011/04/11 19:16:30
|   Branch:   com.example.hoge
|
|   Changelog:
|
|   hallo_hoge.cの名前がおかしかったのでhello.cに変更した。
|
|   Changes against parent 49cc9e900d124b31b8e5db73e8797fb9c13c7d96
|
|     renamed  hallo_hoge.c
|          to  hello.c

リビジョンIDの指定はセレクタと呼ばれる省略形を使って行なうことができます。上の例ではリビジョンIDの先頭数文字のみを指定して補完させていますが、他のセレクタの使い方はあとで紹介します。

変更ログと同時にdiffを表示させることもできます。–diffsオプションを使います。

$ mtn log --diffs -r --last 1
o   ----------------------------------------------------------------------
|   Revision: a3cf58d9c62cca23c7cb533338a2ba2279d69c64
|   Parent:   a2e9e2035cc3a53a3507a190f890f70675014101
|   Author:   foobar@example.com
|   Date:     2011/04/22 10:32:55
|   Branch:   com.example.hoge
|
|   Changelog:
|
|   hello.cのコメントがファイル名と一致していなかったので修正した。
|
|   Changes against parent a2e9e2035cc3a53a3507a190f890f70675014101
|
|     patched  hello.c
|
|   ============================================================
|   --- hello.c fb9d91f064dbebe633ad4ff33aa1b9ea96a5e5a0
|   +++ hello.c 2fd1f8818e258058ccf0050676f948799f803a40
|   @@ -1,4 +1,4 @@
|   -/* hallo_hoge.c */
|   +/* hello.c */
|    #include <stdio.h>
|    int main(int argc, char** argv)
|    {

左に出てるリビジョングラフを–no-graphオプションで消すと、そのままpatchに渡せる形にもなります。

$ CHARSET=cp932 mtn log --diffs --no-graph --last 1
----------------------------------------------------------------------
Revision: a3cf58d9c62cca23c7cb533338a2ba2279d69c64
Parent:   a2e9e2035cc3a53a3507a190f890f70675014101
Author:   foobar@example.com
Date:     2011/04/22 10:32:55
Branch:   com.example.hoge

Changelog:

hello.cのコメントがファイル名と一致していなかったので修正した。

Changes against parent a2e9e2035cc3a53a3507a190f890f70675014101

patched  hello.c

============================================================
--- hello.c     fb9d91f064dbebe633ad4ff33aa1b9ea96a5e5a0
+++ hello.c     2fd1f8818e258058ccf0050676f948799f803a40
@@ -1,4 +1,4 @@
-/* hallo_hoge.c */
+/* hello.c */
 #include <stdio.h>
 int main(int argc, char** argv)
 {

ソースツリーの操作

ここまではおおむねワークスペースでの操作でした。ここからはソースツリー自体の操作を紹介します。

分岐と合流

分散バージョン管理では一つのブランチ内でも分岐が発生します。

というのも、あるリビジョンを元にした複数の子リビジョンがそれぞれ別なリポジトリにコミットされ、その後同期されたときにそれぞれの順序づけができないためです。集中型のバージョン管理ではリポジトリが一つなので、コミットしようとした時に元のリビジョンより新しいリビジョンがあればまず元のリビジョンを更新するよう強制することができ、明示的にブランチを分けない限り分岐は発生しません。

分岐が発生したときには合流(マージ)する必要があります。分岐がおきたまま作業継続することはできますが、一方の変更がもう一方の分岐に反映されませんのでいつかは合流することになるでしょう。

分岐が発生した場合はあるブランチ内で最新リビジョンが複数ある状態になります。ブランチ内の最新リビジョンはheadsコマンドで確認できます。

$ mtn heads
mtn: branch 'com.example.hoge' is currently merged:
49cc9e900d124b31b8e5db73e8797fb9c13c7d96 foobar@example.com 2011/04/08 20:28:05

上は分岐が起きていない状態。下が分岐が起きている状態です。

$ mtn heads
mtn: branch 'com.example.hoge' is currently unmerged:
21a501aaf0eb2240d3c591228987ec2c41498b0f foobar@example.com 2011/04/11 19:16:30
a0a4ec6698a9c64c39d0042b8a9ad46ab76561a9 foobar@example.com 2011/04/11 19:13:58

合流するにはmergeコマンドを使います。

$ mtn merge
mtn: 2 heads on branch 'com.example.hoge'
mtn: merge 1 / 1:
mtn: calculating best pair of heads to merge next
mtn: [left]  21a501aaf0eb2240d3c591228987ec2c41498b0f
mtn: [right] a0a4ec6698a9c64c39d0042b8a9ad46ab76561a9
mtn: [merged] a2e9e2035cc3a53a3507a190f890f70675014101

最新版が一個になってるのを確認します。

$ mtn heads
mtn: branch 'com.example.hoge' is currently merged:
a2e9e2035cc3a53a3507a190f890f70675014101 foobar@example.com 2011/04/12 9:09:50

マージ結果は新しいリビジョンとしてリポジトリ内にコミットされています。ワークスペースには反映されていないので各ワークスペースはmtn updateで更新しておきましょう。

分岐間の変更が競合した場合は、競合解決をしてやる必要があります。簡単なものはMTN_MERGE環境変数で指定したマージプログラムが起動するので手動マージしましょう。無理な場合は後で説明する競合解決コマンドを使用することになります。

ブランチの分岐

上で紹介したのはあまり意図せず分岐が起きてしまった場合ですが、自分のカスタマイズバージョンを作りたいとかしばらく本流と関係なく実験バージョンを作りたいなど明確な意図をもって分岐したい場合があります。そういう場合はブランチを分けましょう。

コミットする際に–branchオプションまたは-bオプションで新しいブランチ名を指定すると、コミットはそちらのブランチに行なわれます。

mtn commit -b com.example.hoge.fuga

ここで指定したブランチ名はワークスペースの設定((-_MTN/optionsファイルに書かれています-。))に保存され、以降のコミットも新しいブランチに行なわれます。

ブランチ間の合流

分岐したブランチで行なった変更を元のブランチに入れたり、逆に分岐以降で元のブランチに入った変更を分岐先にも取り込みたい時には、propagateコマンドでブランチ間の合流を行ないます。

mtn propagate com.example.hoge.fuga com.example.hoge

propagateには変更適用元のブランチ名と適用先のブランチ名を指定します。上の例では分岐後にcom.example.hoge.fugaに入れた変更をcom.example.hogeに適用しています。

mergeでのブランチ内分岐・合流と違い、propagateを行なったあともブランチは分かれたままです。あくまで一方の変更をもう一方にも適用するだけになります。

propagateで変更を適用した分はやはりリポジトリに適用先の最新リビジョンとしてコミットされます。ワークスペースの更新は手動で行ないましょう。

タグをつける

tagコマンドで、あるリビジョンに名前をつけることができます。

mtn tag a3cf58d9c62cca23c7cb533338a2ba2279d69c64 "Version 1.0.0.0"

引数はリビジョンIDとタグ名を指定します。タグ名はスペースなどを含んでいても問題ないのですがコマンドライン上から指定する場合は引用符で括らないと正しく認識されません。リビジョンには選択子が指定できます。

タグ一覧はls tagsコマンドで確認できます。

$ mtn ls tags
Version 1.0.0.0 a3cf58d9c6... com.example.hoge foobar@example.com (be3ded446c...)

logコマンドでそのリビジョンを表示した場合も確認できます。

$ mtn log -r a3cf58d9c62cca23c7cb533338a2ba2279d69c64
o   ----------------------------------------------------------------------
|   Revision: a3cf58d9c62cca23c7cb533338a2ba2279d69c64
|   Parent:   a2e9e2035cc3a53a3507a190f890f70675014101
|   Author:   foobar@example.com
|   Date:     2011/04/22 10:32:55
|   Branch:   com.example.hoge
|   Tag:      Version 1.0.0.0
|
|   Changelog:
|
|   hello.cのコメントがファイル名と一致していなかったので修正した。
|
|   Changes against parent a2e9e2035cc3a53a3507a190f890f70675014101
|
|     patched  hello.c

Tagの欄にタグ名がついています。

選択子(セレクタ)

コマンドライン上からリビジョンを指定する際にいちいち長いリビジョンIDを指定するのは大変です。そこで、コマンドのリビジョンIDを指定できるところでは選択子(セレクタ;selector)というのを指定し自動補完させることができます。

選択子は

選択子名:引数

のような形で指定します。

一番簡単でよく使われるのがID選択で、i:で始まり、引数にリビジョンIDの先頭数文字を指定するだけで補完してくれます。

$ mtn log --brief --no-graph -r i:a3cf5
mtn: expanding selection 'i:a3cf5'
mtn: expanded to 'a3cf58d9c62cca23c7cb533338a2ba2279d69c64'
a3cf58d9c62cca23c7cb533338a2ba2279d69c64 foobar@example.com 2011/04/22 10:32:55 com.example.hoge

最新リビジョンを指定するにはh:選択子を使います。引数はブランチ名ですがワークスペース内なら省略することもできます。

$ mtn log --brief --no-graph -r h:com.example.hoge
mtn: expanding selection 'h:com.example.hoge'
mtn: expanded to 'a3cf58d9c62cca23c7cb533338a2ba2279d69c64'
a3cf58d9c62cca23c7cb533338a2ba2279d69c64 foobar@example.com 2011/04/22 10:32:55 com.example.hoge

p:選択子で特定のリビジョンの親リビジョンを選択できます。引数にリビジョンIDを指定しますが、省略形でかまいません。

$ mtn log --brief --no-graph -r p:a3cf58
mtn: expanding selection 'p:a3cf58'
mtn: expanded to 'a2e9e2035cc3a53a3507a190f890f70675014101'
a2e9e2035cc3a53a3507a190f890f70675014101 foobar@example.com 2011/04/12 9:09:50 com.example.hoge

t:選択子はタグ名を指定してリビジョン指定できます。引数にはタグ名を指定します。タグの一番の使い途ですね。

$ mtn log --brief --no-graph -r "t:Version 1.0.0.0"
mtn: expanding selection 't:Version 1.0.0.0'
mtn: expanded to 'a3cf58d9c62cca23c7cb533338a2ba2279d69c64'
a3cf58d9c62cca23c7cb533338a2ba2279d69c64 foobar@example.com 2011/04/22 10:32:55 com.example.hoge

タグ名にスペースなどを入れた場合は引用符括りましょう。

日時でリビジョンを選択するにはd:選択子を使います。引数には日時や日時の一部を指定します。

$ mtn log --brief --no-graph -r d:2011-04
mtn: expanding selection 'd:2011-04'
mtn: expanded to '21a501aaf0eb2240d3c591228987ec2c41498b0f'
mtn: expanded to '2b59a0304ebd10287de6bed16ac40922594c1692'
mtn: expanded to '49cc9e900d124b31b8e5db73e8797fb9c13c7d96'
mtn: expanded to '4d0e889f0054bfd21bc1bb44f32a3810214f8923'
mtn: expanded to 'a0a4ec6698a9c64c39d0042b8a9ad46ab76561a9'
mtn: expanded to 'a2e9e2035cc3a53a3507a190f890f70675014101'
mtn: expanded to 'a3cf58d9c62cca23c7cb533338a2ba2279d69c64'
a3cf58d9c62cca23c7cb533338a2ba2279d69c64 foobar@example.com 2011/04/22 10:32:55 com.example.hoge
a2e9e2035cc3a53a3507a190f890f70675014101 foobar@example.com 2011/04/12 9:09:50 com.example.hoge
a0a4ec6698a9c64c39d0042b8a9ad46ab76561a9 foobar@example.com 2011/04/11 19:13:58 com.example.hoge
21a501aaf0eb2240d3c591228987ec2c41498b0f foobar@example.com 2011/04/11 19:16:30 com.example.hoge
49cc9e900d124b31b8e5db73e8797fb9c13c7d96 foobar@example.com 2011/04/08 20:28:05 com.example.hoge
2b59a0304ebd10287de6bed16ac40922594c1692 foobar@example.com 2011/04/07 20:08:47 com.example.hoge
4d0e889f0054bfd21bc1bb44f32a3810214f8923 foobar@example.com 2011/04/06 18:17:31 com.example.hoge

2011年4月に作られたリビジョンを指定しました。日付は「年-月-日T時:分:秒」の書式で指定します。日以降はそれぞれ省略可能です。

ここで分かる通り、選択子では複数のリビジョンにマッチするとそれら全てが選択されます。logコマンドでは複数のリビジョン指定を受け付けるので全部表示されていますが、一つしかリビジョンを受け付けないコマンドで複数マッチする選択子を指定するとエラーになるので、その場合はもっと絞り込んだ指定をしましょう。

ある日時以前のリビジョンを選択するe:選択子もあります。引数はやはり日時です。

$ mtn log --brief --no-graph -r e:2011-04
mtn: expanded date '2011-04' -> '2011-04-01T00:00:00'
mtn: expanding selection 'e:2011-04'
mtn: misuse: no match for selection 'e:2011-04'

4月1日以前のリビジョンはこのリポジトリに入ってないようです…。

ある日時以降はl:です(小文字のL)。

$ mtn log --brief --no-graph -r l:2011-04-10
mtn: expanded date '2011-04-10' -> '2011-04-10T00:00:00'
mtn: expanding selection 'l:2011-04-10'
mtn: expanded to '21a501aaf0eb2240d3c591228987ec2c41498b0f'
mtn: expanded to 'a0a4ec6698a9c64c39d0042b8a9ad46ab76561a9'
mtn: expanded to 'a2e9e2035cc3a53a3507a190f890f70675014101'
mtn: expanded to 'a3cf58d9c62cca23c7cb533338a2ba2279d69c64'
a3cf58d9c62cca23c7cb533338a2ba2279d69c64 foobar@example.com 2011/04/22 10:32:55 com.example.hoge
a2e9e2035cc3a53a3507a190f890f70675014101 foobar@example.com 2011/04/12 9:09:50 com.example.hoge
a0a4ec6698a9c64c39d0042b8a9ad46ab76561a9 foobar@example.com 2011/04/11 19:13:58 com.example.hoge
21a501aaf0eb2240d3c591228987ec2c41498b0f foobar@example.com 2011/04/11 19:16:30 com.example.hoge

あるブランチのリビジョンはb:で選択できます。引数はブランチ名です。

$ mtn log --brief --no-graph -r b:com.example.hoge
mtn: expanding selection 'b:com.example.hoge'
mtn: expanded to '21a501aaf0eb2240d3c591228987ec2c41498b0f'
mtn: expanded to '2b59a0304ebd10287de6bed16ac40922594c1692'
mtn: expanded to '49cc9e900d124b31b8e5db73e8797fb9c13c7d96'
mtn: expanded to '4d0e889f0054bfd21bc1bb44f32a3810214f8923'
mtn: expanded to 'a0a4ec6698a9c64c39d0042b8a9ad46ab76561a9'
mtn: expanded to 'a2e9e2035cc3a53a3507a190f890f70675014101'
mtn: expanded to 'a3cf58d9c62cca23c7cb533338a2ba2279d69c64'
a3cf58d9c62cca23c7cb533338a2ba2279d69c64 foobar@example.com 2011/04/22 10:32:55 com.example.hoge
a2e9e2035cc3a53a3507a190f890f70675014101 foobar@example.com 2011/04/12 9:09:50 com.example.hoge
a0a4ec6698a9c64c39d0042b8a9ad46ab76561a9 foobar@example.com 2011/04/11 19:13:58 com.example.hoge
21a501aaf0eb2240d3c591228987ec2c41498b0f foobar@example.com 2011/04/11 19:16:30 com.example.hoge
49cc9e900d124b31b8e5db73e8797fb9c13c7d96 foobar@example.com 2011/04/08 20:28:05 com.example.hoge
2b59a0304ebd10287de6bed16ac40922594c1692 foobar@example.com 2011/04/07 20:08:47 com.example.hoge
4d0e889f0054bfd21bc1bb44f32a3810214f8923 foobar@example.com 2011/04/06 18:17:31 com.example.hoge

選択子は他にもいくらかありますし、自分で作ることもできるのですがあとはドキュメントを見てください。

選択子は単体では沢山選択しすぎる物が多いですが、複数組み合わせて絞ることができます。

$ mtn log --brief --no-graph -r "d:2011-04-22|(l:2011-04-10/e:2011-04-20)"
mtn: expanded date '2011-04-10' -> '2011-04-10T00:00:00'
mtn: expanded date '2011-04-20' -> '2011-04-20T00:00:00'
mtn: expanding selection 'd:2011-04-22|(l:2011-04-10/e:2011-04-20)'
mtn: expanded to '21a501aaf0eb2240d3c591228987ec2c41498b0f'
mtn: expanded to 'a0a4ec6698a9c64c39d0042b8a9ad46ab76561a9'
mtn: expanded to 'a2e9e2035cc3a53a3507a190f890f70675014101'
mtn: expanded to 'a3cf58d9c62cca23c7cb533338a2ba2279d69c64'
a3cf58d9c62cca23c7cb533338a2ba2279d69c64 foobar@example.com 2011/04/22 10:32:55 com.example.hoge
a2e9e2035cc3a53a3507a190f890f70675014101 foobar@example.com 2011/04/12 9:09:50 com.example.hoge
a0a4ec6698a9c64c39d0042b8a9ad46ab76561a9 foobar@example.com 2011/04/11 19:13:58 com.example.hoge
21a501aaf0eb2240d3c591228987ec2c41498b0f foobar@example.com 2011/04/11 19:16:30 com.example.hoge

|(パイプ)で条件の論理和、/(スラッシュ)で論理積になります。論理和と論理積を同時に使う場合は優先順位を括弧でくくって明示する必要があります。またコマンドラインから指定する時は引用符でくくった方がいいでしょう。 上の例だと2011年4月10日以降かつ2011年4月20日以前、または2011年4月22日のリビジョンを指定しています。

リポジトリの同期

今までは一つのリポジトリ内での操作を行なってきましたが、リポジトリ間でデータをやりとりする方法を紹介します。

monotoneではネットワーク経由で同期を行なうのが基本になります。

公開鍵の登録

ネットワーク経由でリポジトリの読み書きを行なうためにアクセス可能なユーザを指定します。 ユーザはキー名で指定するのですが、そのためには前もってキーをリポジトリに記録させておく必要があります。

今リポジトリが記録しているキーはls keysコマンドで見ることができます。

$ mtn ls keys -d my.mtn

[public keys]
be3ded446c06da297884a2773114614a200aa367 foobar@example.com
695aa0a8004fa5e1d194cf3ac7798d63b8993646 kumaryu@kumaryu.net   (*)
(*) - only in 'C:/Users/kumaryu/AppData/Roaming/monotone/keys/'


[private keys]
be3ded446c06da297884a2773114614a200aa367 foobar@example.com
695aa0a8004fa5e1d194cf3ac7798d63b8993646 kumaryu@kumaryu.net

public keys(公開鍵)の欄に出てるキーのうち(*)がついてない物がリポジトリが知っているキーです。 private keys(秘密鍵)は自分で作ったキーなので他人の物は入りません。

ネットワーク越しの読み書きアクセス権限はリポジトリに公開鍵が記録されてる人にしか与えられないので2、入ってなければもらってきましょう。

公開鍵はpubkeyコマンドで書き出せます。引数にはキー名を指定します。

$ mtn pubkey kumaryu@kumaryu.net
[pubkey kumaryu@kumaryu.net]
MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDMt+9p5dysZtiaVy9KsQQKGhUKg0aMUmil9mbpz52A
PA9LOmrsSEiGTrUX+Y2KMEZBhiAOepU4ZFFrKGL2D0di/9uPd3hwU/oywdl6ag6EPu26lVWTzqwu12ql
8nvQClBTdklR7u6x3r44iRH40Ytb86iyiQ7gNc7W/tiJSh1suwIBEQ==
[end]

出力をコピペでもいいのですが、ファイルにリダイレクトします。

$ mtn pubkey kumaryu@kumaryu.net > kumaryu.pubkey

この公開鍵ファイルをもらってきましょう。もらってきたらreadコマンドでリポジトリに記録します。

$ mtn read -d my.mtn kumaryu.pubkey
mtn: read 1 packet

ls keysで確認します。

$ mtn ls keys -d my.mtn

[public keys]
be3ded446c06da297884a2773114614a200aa367 foobar@example.com
695aa0a8004fa5e1d194cf3ac7798d63b8993646 kumaryu@kumaryu.net


[private keys]
be3ded446c06da297884a2773114614a200aa367 foobar@example.com
695aa0a8004fa5e1d194cf3ac7798d63b8993646 kumaryu@kumaryu.net

リポジトリに入ったので(*)が消えました。ここでは自分で作ったキーを入れましたが、他人の公開鍵をもらってきた時には上のpublic keysにそのキー名が追加されていれば成功です。

アクセス制御

キーの登録をしたら読み書き許可を設定します。

monotoneの設定ディレクトリ3read-permissionswrite-peissionsファイルを作成してください。どちらも拡張子はありません。

read-permissionsファイルにはリポジトリの読み出し許可を与える人を設定します。書式は以下のようになります。

pattern "*"
allow "foobar@example.com"
allow "kumaryu@kumaryu.net"

上記のようにファイルに書いておきます。patternのところに指定するのはブランチ選択のパターンで、読み出し許可をするブランチ名を指定するところなのですが、*はワイルドカードでどんな文字列にもマッチします。つまり上の例では全てのブランチを指定しています。 allowのところには許可を与えるキー名を指定します。ここもワイルドカードを指定できるので*を指定すると誰でも読み出せるようになります。

write-permissionsファイルは逆に書き込み許可を与える人を設定します。

foobar@example.com
kumaryu@kumaryu.net

こっちはシンプルで、上のように1行に1つずつ許可を与えるキー名を書いていくだけです。 めんどうなら*を指定して誰でも書き込めるようにできますが、誰でもアクセスできるところに起動させておく場合はやめたほうがいいかもしれません。

monotoneサーバ

アクセス制御を設定したら、リポジトリサーバを起動します。

サーバはserveコマンドで起動できます。

$ mtn serve -d my.mtn -k foobar@example.com
mtn: beginning service on <all interfaces> : 4691

同期をしたいリポジトリとキー名を指定します。キー名は秘密鍵を1つしか持っていなければ省略してもかまいません。起動が成功すると全てのネットワークインターフェースの4691番ポートで接続待ちをします。 serveコマンドはこの後ひたすら接続待ちになるので、必要なくなったらCtrl+Cなどで止めてください。

気をつけないといけないのが、サーバは初めて誰かが接続してきた時にキーのパスワード入力を求めてきます。なんだかサーバの反応がないなーと思ったらパスワード入力で止まっていたなどということがよくあるので、接続が上手くいかなかったらサーバプロセスの状態を見てみましょう。

リポジトリ間の同期

サーバを起動したら別なプロセスから同期しに行きます。同期コマンドはpull、push、syncの3つです。

pullはサーバのリポジトリから指定したブランチ内のリビジョンデータを貰ってきてローカルのリポジトリに格納します。

pushはローカルのリポジトリにあるリビジョンデータをサーバのリポジトリにアップロードします。

syncはpullとpushの両方をやります。

どれも使い方はほぼ同じなのでよく使うであろうsyncで説明します。

$ mtn sync -d yours.mtn -k foobar@example.com "mtn://localhost?*"
mtn: connecting to 'mtn://localhost'
mtn:   include pattern  '*'
mtn:   exclude pattern  ''
mtn: finding items to synchronize:
mtn: bytes in | bytes out | revs in | revs out
mtn:   12.0 k |       680 |     7/7 |      0/0
mtn: successful exchange with 'mtn://localhost'

mtn://localhost?*が接続先および交換するブランチパターンの指定です。URLと同じような形式で指定します。mtnが接続プロトコル、monotoneサーバと直接接続するときはmtnです。localhostは接続先ホストで、今回は同じマシンにサーバを起動しているのでlocalhost、ポート番号はデフォルトなので指定していません。「?」以降が交換するブランチパターンで、ワイルドカードである*を指定したので全てのブランチを同期しています。?のあとにブランチ名を指定すればそのブランチだけ同期することができます。

上の例ではyours.mtnに(サーバが提供している)my.mtnから7つのリビジョンが格納されました。 yours.mtnは作ったばかりのリポジトリで交換すべきリビジョンが入っていないのでアップロードしたリビジョン数は0です。 yours.mtnに入っていてmy.mtnにはないリビジョンがあったら、そのリビジョンのアップロードも行なわれたことでしょう。

当然ながら既に互いのリポジトリに入っているリビジョンは交換されません。もう一度syncすると…

$ mtn sync -d yours.mtn -k foobar@example.com "mtn://localhost?*"
mtn: connecting to 'mtn://localhost'
mtn:   include pattern  '*'
mtn:   exclude pattern  ''
mtn: finding items to synchronize:
mtn: certificates | keys | revisions
mtn:           22 |    1 |         7
mtn: bytes in | bytes out | revs in | revs out
mtn:      813 |     1.1 k |     0/0 |      0/0
mtn: successful exchange with 'mtn://localhost'

入出力されたリビジョンともに0になりました。

ここではsyncを使いましたが、pullはサーバにだけあるリビジョンをもらってくる操作だけを行ない、pushはサーバにないリビジョンをアップロードする操作だけを行ないます。syncするには読み書きの権限が必要ですが、pullは読み出しの権限だけ、pushは書き込みの権限さえあれば行なうことができます。普通使うのはsync、書き込み権限が無いリポジトリから貰うときだけpullを使うことになるでしょう。

もらってきたリビジョンは普通にコミットされたものと同じようにリポジトリに入っているので、各ワークスペースでupdateするなり、新しいブランチならcheckoutするなりしましょう。

その他便利なコマンド

他に覚えておくとよいコマンドを紹介します。

help

コマンドを忘れた時や細かいオプションが知りたい時にはhelpコマンドを使いましょう。

$ mtn help commit
Usage: mtn [OPTION...] command [ARG...]

Options specific to 'mtn commit' (run 'mtn help' to see global options):

--author <arg>          override author for commit
--branch [ -b ] <arg>   select branch cert for operation
--date <arg>            override date/time for commit
--depth <arg>           limit the number of levels of directories to descend
--exclude <arg>         leave out anything described by its argument
--message [ -m ] <arg>  set commit changelog message
--message-file <arg>    set filename containing commit changelog message

Syntax specific to 'mtn commit':

commit [PATH]...

Description for 'mtn commit':

Commits workspace changes to the database.

Aliases: ci.

引数にコマンド名を渡すと、そのコマンドの使い方やオプションが表示されます。

そもそもコマンド名を覚えてないという時にはhelpを引数無しで使うとコマンドグループ名が表示されます(database、tree、networkなど)。 そのコマンドグループを引数にすると、そのグループに関連するコマンド一覧が表示されるので、そこからまたコマンドをhelpの引数として使い方を調べましょう。

とにかくhelpさえ覚えておけば、ちょっとくらい忘れてもなんとかなります。もうちょっと詳しく知りたい場合は公式サイトのドキュメントを読みましょう。

Unix系の環境ならmtn manでmanページを表示することもできます。ちょっと詳しいことが知りたい場合はこれを読むのもいいでしょう。

競合解決

上ではごまかしてきましたが、mergeやpropagateなどで変更の競合が起きたときに、エディタを起動して解決できない複雑な競合の場合があります((-両方で同じ名前のファイルを追加したとか、片方ではファイルを消しながらもう片方ではファイルを変更したなどなど-))。また、合流操作時にその場で手動マージするのでなく、もっと落ち着いてやりたいなぁということもよくあります。そんな時に使うのがconflictsという競合解決コマンドです。

これはたまに使うわりにmonotoneのコマンド群の中で使い方が最高にわかりづらいので解説します。

まずは何かしら競合が起きてないと試しようがありません。以下の例ではcom.example.hogeブランチ内で分岐を作ってしまったので合流しようとしてます。

$ mtn merge
mtn: 2 heads on branch 'com.example.hoge'
mtn: merge 1 / 1:
mtn: calculating best pair of heads to merge next
mtn: [left]  141609e48a504b26dce8367c9b9f4479fc615af7
mtn: [right] 971c469a8cb94b4e69b61330e60c7e411c1acbbe
mtn: conflict: duplicate name 'bye.c' for the directory ''
mtn: added as a new file on the left
mtn: added as a new file on the right
mtn: misuse: merge failed due to unresolved conflicts

しかし、両方のリビジョンで同じ名前のbye.cを追加してしまったので合流は失敗してしまいました。これをconflictsコマンドで解決し合流できるようにします。

競合解決コマンドを使う際にはワークスペースが必要です。propagateもmergeも実行するのにワークスペースは必要無いのですが、conflictsコマンドにはワークスペースが必要になります。ただワークスペースは何が入っててもかまいません。

まずはconflicts storeコマンドで合流しようとする2つのリビジョンの競合情報をワークスペースに保存します。

$ mtn conflicts store 141609 971c469
mtn: expanded selector '141609' -> 'i:141609'
mtn: expanding selection '141609'
mtn: expanded to '141609e48a504b26dce8367c9b9f4479fc615af7'
mtn: expanded selector '971c469' -> 'i:971c469'
mtn: expanding selection '971c469'
mtn: expanded to '971c469a8cb94b4e69b61330e60c7e411c1acbbe'
mtn: 1 conflict with supported resolutions.
mtn: stored in '_MTN/conflicts'

conflicts storeに指定する引数は左のリビジョンと右のリビジョンです。どっちが右でどっちが左だよというのは、上のmergeのところで[left]と[right]となってるのを見ればわかります。

これでワークスペースの_MTN/conflictsに競合情報が格納されました。これを一個ずつ解決していきます。

conflicts show_remainingコマンドを使うと、あとどれだけ解決しないといけないかが表示できますので確認しましょう。

$ mtn conflicts show_remaining
mtn: duplicate_name bye.c

今回は1つだけです。mergeではそんなにたくさん発生しませんが、propagateだと大量に発生しがちなのでげんなりできます。

解決はなぜか上から順番にしかできません。次にやるべきものはconflicts show_firstで表示できます。

$ mtn conflicts show_first
mtn: duplicate_name bye.c
mtn: possible resolutions:
mtn: resolve_first_left drop
mtn: resolve_first_left keep
mtn: resolve_first_left rename "name"
mtn: resolve_first_left user "name"
mtn: resolve_first_right drop
mtn: resolve_first_right keep
mtn: resolve_first_right rename "name"
mtn: resolve_first_right user "name"

bye.cが重複してる([left]と[right]のリビジョンの両方で出現した)ことと、解決方法として選べる物が表示されます。 解決方法は競合内容によって選べるものが違うので、show_firstで表示されるものの中から選びましょう。

ここでは2つのファイルの競合なので[left]と[right]のリビジョンにおけるbye.cについてそれぞれどうするかを選択する必要があります。 それぞれのリビジョンでそのファイルの中身はどうなってたっけとかあれば、diffやlogコマンドを駆使て見てください。

mtn conflicts resolve_first_left drop

[left]のリビジョンのbye.cを削除します。

mtn conflicts resolve_first_left keep

[left]のリビジョンのbye.cをそのまま採用します。

mtn conflicts resolve_first_left rename "name"

[left]のリビジョンのbye.cをnameという名前に変更します。

mtn conflicts resolve_first_left user "name"

[left]のリビジョンのbye.cをnameという名前のファイルの中身で置き換えて採用します。

mtn conflicts resolve_first_right drop

[right]のリビジョンのbye.cを削除します。

mtn conflicts resolve_first_right keep

[right]のリビジョンのbye.cをそのまま採用します。

mtn conflicts resolve_first_right rename "name"

[right]のリビジョンのbye.cをnameという名前に変更します。

mtn conflicts resolve_first_right user "name"

[right]のリビジョンのbye.cをnameという名前のファイルの中身で置き換えて採用します。

以上をleftとright両方に選択してください。[left]のbye.cはイラネと思ってresolve_first_left dropをしても[right]側にもresolve_first_right keepを実行しないといけません。

userだけちょっと特殊です。userを使う場合はマージ結果のファイルをワークスペースのどこかに作成し、そのファイル名を指定します。するとその内容をマージ結果として採用します。

ここではbye.cの内容が両方で違っていたので手動マージした結果をmerged_bye.cとして作っておいてuserで解決します。

$ mtn conflicts resolve_first_left user merged_bye.c

$ mtn conflicts show_first
mtn: duplicate_name bye.c
mtn: possible resolutions:
mtn: resolve_first_right drop
mtn: resolve_first_right keep
mtn: resolve_first_right rename "name"
mtn: resolve_first_right user "name"

左右どちらをuserにしてもよかったのですがとりあえず左で。左を解決したあとにshow_firstを見ると右を解決する必要があるのがわかります。

$ mtn conflicts resolve_first_right drop

$ mtn conflicts show_first
mtn: all conflicts resolved

右はもういらないのでdropで無かったことにしました。他の競合したファイルがあればshow_firstで次のファイルが出てくるのですが、今回は1ファイルだけだったので全ての競合が解決されたことが表示されています。

解決方法指定を間違った時はconflicts cleanをすると_MTN/conflictsに格納された競合解決データが削除されるのでstoreからもう一度やりなおしましょう。

ここまでは解決方法を指示しただけですので、実際にこの解決指示を使ってmergeします。–resolve-conflictsオプションを指定しましょう。

$ mtn merge --resolve-conflicts
mtn: 2 heads on branch 'com.example.hoge'
mtn: merge 1 / 1:
mtn: calculating best pair of heads to merge next
mtn: [left]  141609e48a504b26dce8367c9b9f4479fc615af7
mtn: [right] 971c469a8cb94b4e69b61330e60c7e411c1acbbe
mtn: replacing content of 'bye.c' with 'merged_bye.c'
mtn: dropping 'bye.c'
mtn: [merged] 2f410917cc52fe618eaab05433f1904178f610f0

上手くいきました。これで競合解決データはいらなくなるのでconflicts cleanで消しておきます。

$ mtn conflicts clean

merged_bye.cももういらないので消してかまいません。

ここではmergeの例を示しましたが、propagateの場合も同じく–resolve-conflictsを指定するだけです。

次に読むべきもの

これで一通り簡単な使いかたを紹介しましたが、ちゃんと使い始めるとこれだけではもの足りないでしょう。

そこで次に読むべきものとして、公式ドキュメントをあげておきます。monotoneのドキュメントは英語ですが、わかりやすくよくメンテナンスもされいるので非常に頼りになります。使い方がわからない機能があったりこんな機能ないのかな?と思った時にはまっさきにドキュメントを確認するのがよいでしょう。

コマンドのオプションをちょっと忘れたとかいう場合には上で紹介したhelpコマンドを見るのがいいでしょう。使える環境ならmanも忘れないでください。

公式Wikiにいくらか追加のスクリプトや周辺のツールが紹介されています。コマンドライン操作めんどいよという時はWikiで探しましょう。表示だけでなく一通り操作もできるGUIツールは今のところGuitoneしかないですが。

Luaスクリプトで拡張したい場合はLuaについて学ぶのがよいでしょう。Luaはシンプルで簡単な言語ですし、日本語での資料もそこそこ見つかります。Luaがなんとなくわかった後、monotoneで使うにはドキュメントにあるデフォルトフックスクリプトを読んで理解する必要があるでしょう。Luaによる拡張はちょっとドキュメントが少なめなので、C++側のソースを読む必要も出るかもしれません。あんまり凝ったことをするなら別な言語から自動化コマンド(automateコマンド)を呼び出す方が楽ですね。

以上にあげた資料は英語ばっかりですが、monotoneはいまいちマイナーなので日本語での情報はあんまり期待できません。というか英語でも公式サイト以外はあんまり期待できないのですが、逆に言うと公式サイトに情報が集まっているということなので、技術英語でそんなに難しくもないので公式ドキュメントをゆっくり読んでみてください。概念やチュートリアルあたりは分散バージョン管理についての知識が無いとなかなかピンとこないかもしれませんが、ここを読んだ人なら大丈夫…じゃないかな?たぶん。

更新履歴

  1. パスは環境によって多少異なります 

  2. *を指定して誰でも読み書きできるようにした場合はキーを持ってるかどうかは関係ありません。 

  3. MacやLinuxなどのUnix系OSでは~/.monotone/、Windowsでは%APPDATA%\monotone\です。