「入門TortoiseHg+Mercurial」発売中(詳細は「執筆情報」参照)


ローカルリポジトリ

本節では hg initで新規作成された単一のリポジトリ (= ローカルリポジトリ)を元に、 Mercurial が構成変更をどのようなモデルで管理しているかを説明します。

内容的には分散リポジトリ型 SCM らしさはありませんが、 「マージ」の概念などは、 リポジトリ分散時の更新伝播には欠かせないものとなります。

初期状態作成

本節では、 以下の手順で作成した Mercurial のローカルリポジトリを元に説明を行います。 この手順で作成されたリポジトリには、 初期状態として3つのファイル(A.txt, B.txt および C.txt)が含まれています。

以下、コマンド実行例における行番号位置を表す場合、 角括弧表記(e.g.: "[1]")を用います。

[ 1]:  % hg init myrepo
[ 2]:  % cd myrepo
[ 3]:  A.txt, B.txt, C.txt の作成
[ 4]:  % hg add A.txt B.txt C.txt
[ 5]:  % hg commit -m 'add initial contents'
初期状態作成

hg status の実行結果を見てみると、 [3] 直後の時点では "?"(未知)扱いだったファイルが [4] 直後の時点では "A"(追加)扱いになり、 [5] 直後の時点では何も出力されなくなります(= 全ての変更がコミット済み)。

チェンジセット

前述の手順で作成されたリポジトリ上で以下の操作を行い、 操作の都度 hg commit を実施して、 変更内容を記録するものとします。

以後、以下に示す各操作を表す場合、 丸括弧表記(e.g.: "(1)")を用います。 また、初期状態を表す場合は "(0)" を用います。

  1. A.txt を変更
  2. B.txt, C.txt を変更
  3. A.txt, C.txt を変更

ファイルごとの変更履歴をベースとしている CVS を使って同様の操作を行った場合、 構成遷移は以下のようになります。

ファイル毎履歴による構成遷移

上図中では、 各変更操作ごとの構成内容を破線で表していますが、 CVS 自体はこのようなファイル横断的情報を管理していないため、 個々の破線時点に対応する構成を得るためには、 タグ付けや日付ベースでの取り出しを行う必要があります。

一方の Mercurial では、 変更操作のまとまりそのものを「チェンジセット」と呼び、 チェンジセット単位で構成要素の変遷を管理していますので、 上記操作を終えた時点では、 以下のような構成遷移となっています。

チェンジセットによる構成遷移

ファイルを表す図形中の括弧付きの数値は、 どの操作時点でのファイル内容を構成要素とみなしているかを表します。 例えば、操作 (2) 完了時点での A.txt の内容は、 (1) 操作時点での内容と同じものです。

備考: 「チェンジセット」自体は、 「構成変更の内容」即ち「差分」に相当する筈ですが、 「あるチェンジセットを指定する」ことを 「そのチェンジセットを適用した後の構成を指定すること」 と考えれば、 「チェンジセットの遷移」=「構成の遷移」と言えなくもないため、 説明の便宜上、上記のような表現をしています。

なお、 Mercurial において一意に「チェンジセット」を識別する方法には、 以下の3つの方法がありますが、 本ドキュメントにおける説明では、 簡便化のために「リビジョン番号」を使用します。

ハッシュ値:
40 桁の 16 進数による識別。 分散されたリポジトリ間に渡って一意にチェンジセットを識別できる。
短縮ハッシュ値:
ハッシュ値の短縮形(12 桁)。 完全ではないが概ね一意にチェンジセットを識別できる(?)。
リビジョン番号:
ローカルリポジトリ限定の識別情報。 同じリビジョン番号であっても、 分散されたリポジトリ間では、 必ずしも同一のチェンジセットを指すとは限らない。

複数ヘッド

単一のローカルリポジトリに対して順に作業成果をコミットし続ける分には、 当該リポジトリの構成遷移は、 CVS の main trunk がそうであるように、 一本の直線状にチェンジセットが順に並ぶイメージとなります。

しかし、 あるチェンジセットを元にして、 CVS で言うところの所謂「ブランチ」を作成したい場合もあるでしょう。

例えば、チェンジセット (1) を元に、 チェンジセット (2) とは別の変更を加えたブランチは、 以下の手順で作成することが出来ます。

[ 6]:  % hg update 1
[ 7]:  A.txt, B.txt を変更
[ 8]:  % hg commit -m 'another head' 
新規ヘッドの作成

hg heads は、 「子チェンジセット(= 当該チェンジセットを元にしたチェンジセット) を持たない」チェンジセット (「ヘッド」と呼びます)を表示しますが、 [8] 直前までは (3) のみであった表示が、 [8] 直後には (3) と (4) の2つが表示されるようになります。

新規ヘッド作成後のチェンジセット構成

また、hg parent は、 「現時点の作業領域の内容がどのチェンジセットを元にしているか」を表示しますが、 [6] 直前では (3) だったものが、 [6] 直後には (1) に、 [8] 直後には (4) になっている筈です。

diffaddcommit といったローカルリポジトリに対する操作の多くが、 特に指定が無い場合には parent が示すチェンジセットを対象としているため、 実際の作業の際には、 現在の作業領域に対する parent がどのチェンジセットを指しているのかを常に把握しておく必要があります。

備考: 幾つかのローカルリポジトリ操作 (e.g.: updatemerge)は、 parent でなく tip チェンジセットを対象とします。

tip は、 ローカルリポジトリに対する commit 以外にも、 外部からの更新伝播を受けた際に変化しますので、 チェンジセットを明示せずに使用する場合は注意が必要です。

マージ

リポジトリ中の任意の2つのチェンジセット(この例では (3) と (4))は、 以下のような手順でマージすることが出来ます。

[ 9]: hg update 3
[10]: hg merge 4
[11]: 衝突(conflict)の解消
[12]: hg commit -m 'merge'
マージ

衝突(conflict)の解消」とされている箇所は、 使用するマージプログラムに応じて作業の内容が異なります。 例えば、kdiff3 のような GUI ツールを使用する場合は、 衝突が発生する都度ファイルの修正を行うことになりますし、 diff3 ベースの非対話的マージプログラムの場合は、 "hg merge" 実行後に衝突箇所を逐一手動で修正することになります。

備考: Mercurial で「マージ」と言った場合:

のいずれを指すかは、話者や話題に依存して変化し得ますので、 お互いがどの「マージ」を指しているのかを擦り合わせつつ、 話をされることをお勧めします。

Mercurial 1.1 版からは、 hg merge 実行の際にマージプログラムが適用されたファイルは、 「マージプログラム適用対象ファイル」として記録されるようになりました。

マージプログラム適用対象ファイルの一覧は "hg resolve --list" 実行で表示させることができます。

$ hg resolve --list
U A.txt
U B.txt
$
マージプログラム適用対象ファイルの表示

上記の表示における行頭表示が "U" の場合、 そのファイルに対するマージプログラムの適用において、 衝突が検出された(衝突が解決されていない = Unresolved)ことを意味します。 diff3 ベースの非対話的マージプログラムなどを使用している場合、 衝突が検出されたファイルは "U" 表示されます。

その一方で、衝突が検出されなかった場合や、 kdiff3 などの対話的に衝突を解消するツールで全ての衝突を解消した場合、 そのファイルは衝突が解決された(= Resolved)ことを意味する "R" が表示されます。

全てのマージプログラム適用対象ファイルが 「衝突解決」状態にならない限り、 "hg commit" の実行は出来ません。 「衝突解決」状態にするには以下の様な方法があります。

非対話的なマージプログラムを使用する場合は、 前者の方法が妥当でしょう。

"hg commit" 実施までのマージ手順を完了することで、 対象となる2つのチェンジセットを parent とする新たなチェンジセットが生成されます。 例えば、 先述の実行手順によるマージの実施後には、 ローカルリポジトリのチェンジセット構成は以下のようになります。

マージ後のチェンジセット構成

上記図のチェンジセット (5) における各ファイルの内容は、 マージ操作において各ファイルの内容をどの版にするか (あるいは全く新しい内容にするか)が不明であるため、 「チェンジセット (5) における操作結果」の意味で、 「(5)」と記述しています。

hg mergehg commit の一連の処理は、 作業領域の親チェンジセットである (3) に対して、 由来の異なる (4) の変更を反映する作業です。

ですから、 必ずしも衝突したファイルの取り込みだけが行われるわけではありません。 例えば、(4) で新たに追加したファイル D.txt があれば、 (3) と (4) のマージ結果として、 そのファイルは (5) に反映されなければなりません。

下手に CVS 等の利用経験があると、 「自分が把握(or 改変)しているファイルだけcommit に明示」しがちですが、 マージ後のチェンジセットに求められる内容は、 先の「(4) での D.txt の追加」例のように、 時にその範囲を超えることがあります。

以上の様な理由から、 hg merge 後の hg commit では、 対象ファイル名の指定は一切出来ないようになっています。


次節「リモートリポジトリ」へ