Chapter 3
A tour of Mercurial: merging work
前章においては、リポジトリの複製、リポジトリでのチェンジセットの生成、ならびに“hg push” および“hg pull” によるリポジトリ間でのチェンジセットの授受を見てきました。次の段階として、別々のリポジトリにおける変更のマージ(
merge)について見てみましょう。
3.1 Merging streams of work
分散構成管理ツールにおいて、マージは作業の基本です。
-
Alice と Bob が、共同作業しているプロジェクトのリポジトリから複製した、個人的なリポジトリを持っているものとします。
Alice は自分のリポジトリにおいてバグを修正しました。 Bob は自分のリポジトリにおいて機能を追加しました。二人は、バグフィックスと新機能の両方を含むリポジトリを共有したいと思うでしょう。
- 筆者は、個別のリポジトリによって、お互いが安全に隔離された複数の異なる作業を、同一プロジェクトにおいて同
時に実施することが頻繁にあります。この形式での作業では、あるリポジトリにおける成果を、他のリポジトリに対して頻繁にマージする必要があります。
マージは必要に応じて実施するありふれた作業ですので、 Mercurial では簡単に行えるようになっています。それでは、マージ手順を見て行きましょう。もう一度リポジトリの複製を行い(もう何度も複製しましたよね?)、そのリポジトリにおいて変更を行いま
す。
1 $ cd ..
2 $ hg clone hello my-new-hello
3 updating working directory
4 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 $ cd my-new-hello
6 $ sed -i ’/printf/i∖∖tprintf("once more, hello.∖∖n");’ hello.c
7 $ hg commit -m ’A new hello for a new day.’
この時点で、内容の異なる2つの hello.c のコピーが存在するはずです。2つのリポジトリの履歴は、図 3.1 に示すように、枝分かれしています。
1 $ cat hello.c
2 /*
3 * Placed in the public domain by Bryan O’Sullivan. This program is
4 * not covered by patents in the United States or other countries.
5 */
6
7 #include <stdio.h>
8
9 int main(int argc, char **argv)
10 {
11 printf("once more, hello.∖n");
12 printf("hello, world!∖");
13 return 0;
14 }
15 $ cat ../my-hello/hello.c
16 /*
17 * Placed in the public domain by Bryan O’Sullivan. This program is
18 * not covered by patents in the United States or other countries.
19 */
20
21 #include <stdio.h>
22
23 int main(int argc, char **argv)
24 {
25 printf("hello, world!∖");
26 printf("hello again!∖n");
27 return 0;
28 }
“hg pull” を行っても、作業領域ディレクトリには影響を及ぼさないことは既に説明したとおりですので、my-hello から“hg
pull”してみましょう。
1 $ hg pull ../my-hello
2 pulling from ../my-hello
3 searching for changes
4 adding changesets
5 adding manifests
6 adding file changes
7 added 1 changesets with 1 changes to 1 files (+1 heads)
8 (run ’hg heads’ to see heads, ’hg merge’ to merge)
作業領域ディレクトリには影響を及ぼしていませんが、“hg pull” コマンドは “heads” について何か警告していま
す。
3.1.1 Head changesets
“head” とは、リポジトリ中において、子孫(ないし子供)となるチェンジセットが存在しないチェンジセットのことです。リポジトリ
における最も最新のリビジョンは、一切の子チェンジセットを持ちませんから、従って tip リビジョンは head となりますが、1つのリ
ポジトリには複数の head が存在しえます。
my-hello から my-new-hello への“hg pull” による影響を、図 3.2 で見ることができます。既に my-new-hello に存在していた履歴には手が付けられていませんが、新しいリビジョンが追加されています。図
3.2 からは、新しいリポジトリ(my-new-hello)において、チェンジセット識別子は同じままでも、リビジョン番号が異なる様が読み取れます(そして、図らずも、チェンジセットにつ
いて話をする際に、リビジョン番号を使用するのが良くない、という好例になっています)。“hg heads” コマンドにより、リポジトリ
の head を見ることができます。
1 $ hg heads
2 changeset: 6:30682a1ee732
3 tag: tip
4 parent: 4:2278160e78d4
5 user: Bryan O’Sullivan <bos@serpentine.com>
6 date: Mon Jul 20 21:59:02 2009 +0000
7 summary: Added an extra line of output
8
9 changeset: 5:88c00c2c8751
10 user: Bryan O’Sullivan <bos@serpentine.com>
11 date: Mon Jul 20 21:59:07 2009 +0000
12 summary: A new hello for a new day.
13
3.1.2 Performing the merge
作業領域ディレクトリを、(my-hello から取り込んだ)新たな tip リビジョンに更新するために、いつものように“hg update” コマ
ンドを実行すると、どうなるでしょう?
1 $ hg update
2 abort: crosses branches (use ’hg merge’ or ’hg update -C’)
Mercurial から、“hg update” コマンドではマージが行われない旨が通達されます。マージの実施が必要と思われる場合、強制的
な実行をしない限りは“hg update” コマンドによる作業領域ディレクトリの更新は行われません。“hg update” コマンドの代わり
に、“hg merge” コマンドを用いて2つの head をマージします。
1 $ hg merge
2 merging hello.c
3 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
4 (branch merge, don’t forget to commit)
“hg merge” コマンドによって、“hg parents” コマンドの出力、および hello.c の内容の変更という形で、両方の head の変更内容が作業領域ディレクトリに反映されます。
1 $ hg parents
2 changeset: 5:88c00c2c8751
3 user: Bryan O’Sullivan <bos@serpentine.com>
4 date: Mon Jul 20 21:59:07 2009 +0000
5 summary: A new hello for a new day.
6
7 changeset: 6:30682a1ee732
8 tag: tip
9 parent: 4:2278160e78d4
10 user: Bryan O’Sullivan <bos@serpentine.com>
11 date: Mon Jul 20 21:59:02 2009 +0000
12 summary: Added an extra line of output
13
14 $ cat hello.c
15 /*
16 * Placed in the public domain by Bryan O’Sullivan. This program is
17 * not covered by patents in the United States or other countries.
18 */
19
20 #include <stdio.h>
21
22 int main(int argc, char **argv)
23 {
24 printf("once more, hello.∖n");
25 printf("hello, world!∖");
26 printf("hello again!∖n");
27 return 0;
28 }
3.1.3 Committing the results of the merge
結果を“hg commit” するまでは、“hg parents” はマージの際には常に2つの親(チェンジセット)を表示します。
1 $ hg commit -m ’Merged changes’
これで、新しい tip リビジョンが作成されました。先述した2つの head の両方を親に持つ点に注意してください。これらは、先に
“hg parents” で表示したリビジョンと一致します。
1 $ hg tip
2 changeset: 7:e947038a4c90
3 tag: tip
4 parent: 5:88c00c2c8751
5 parent: 6:30682a1ee732
6 user: Bryan O’Sullivan <bos@serpentine.com>
7 date: Mon Jul 20 21:59:08 2009 +0000
8 summary: Merged changes
9
作業領域ディレクトリがマージの際にどのようになっているのか、そしてコミットによってどのようにリポジトリに作用するのかを、図
3.3 から読み取ることができます。マージの際に作業領域ディレクトリの親であった2つのチェンジセットは、コミットの際には新たなチェンジセットにとっての親チェンジセットとなります。
3.2 Merging conflicting changes
殆どのマージ作業は簡単に済みますが、時にはマージ対象のチェンジセット同士が、同じファイルの同じ部位を変更している場合があり
ます。両者の変更内容が同一で無ければ、マージは衝突(conflict)を生じるため、両者の異なる変更内容を両立させて何らかの一貫性
の取れた状態にするための決断が必要です。
文書に対する2つの変更の衝突の例を、図 3.4 が図示しています。両者はファイルの同じ版を元にしていますが、一方が変更を行う傍ら、他方が同じ段落に対して異なる変更をしてしまいます。変更の衝突を解消する作業とは、そのファイルがどのようになっているべ
きかを決定することに他なりません。
Mercurial には衝突を扱う機能が組み込まれていません。その代わりに、hgmerge と呼ばれ
る外部プログラムを実行します。このプログラムは、 Mercurial に添付されるシェルスクリプ
ト で
すが、別なプログラムを起動させることもできます。hgmerge の基底動作では、幾つかの著名なマージツールのう
ち、稼働環境においてインストールされていると思われるものを探します。まず始めに、非対話的マージツー
ル を実行してみますが、
(人手によって解決する必要性があるために)それが失敗した場合や、そもそもそれらのツールが提供されていない場合、他のグラフィカルなマージツー
ルの起動を試みます 。
HGMERGE環境変数に起動対象プログラムないしスクリプト名を設定することで、 Mercurial にhgmerge 以外を起動させる事もでき
ます
3.2.1 Using a graphical merge tool
著者のお薦めのグラフィカルなマージツールはkdiff3 なので、グラフィカルなファイルマージツールに求められる機能について、こ
れを題材に説明しようと思います。作業中の画面イメージが図 3.2.1にあります。着目している1つのファイルに対して、3つの異なる
リビジョンが存在することから、マージ方法は3方向マージ(three-way merge)と呼ばれています。それゆえ、マージツールはウィン
ドウ上部を3つの区画に分割しています。
-
左端に表示されているのは、ファイルの元(base)の版、つまりマージ対象としている2つの版にとって、最も新し
い分岐元となっている版です。
-
中央に表示されているのは、マージ “先” の版 で
すので、作業領域ディレクトリにおける変更内容が表示されます。
-
右端に表示されているのは、マージ “元” で
すので、マージしようとしているチェンジセットに由来する内容が表示されます。
これらの区画の下方に表示されているのは、現時点でのマージ結果です。マージにおける作業とは、画面上に赤字で表示され
た 、慎重
なファイルのマージが必要とされる未解決の衝突を、妥当な内容で置き換えることです。
これら4つの区画は互いに固定されているので、いずれかの区画をスクロールさせた場合には、他の区画も相応の場所を表示するよ
うに更新されます。
ファイル中の個々の衝突箇所において、衝突を解消するために、元版/マージ先版/マージ元版のテキストを(それらの組み合わせも含めて)任意に選択することができます。また、更なる変更を行うために、マージ結果を直接手で入力することもできま
す。
ここで紹介し切れないほど多くのファイルマージツールが存在します。これらはそれぞれ、稼動可能プラットホームや、特徴的な得
手不得手などの点で異なります。殆どのツールはテキストファイルのマージに特化していますが、中には特定のファイルフォーマット
(一般には XML)に特化したものもあります。
3.2.2 A worked example
本節での例では、前述の図 3.4におけるファイル更新の履歴を再現します。元となる版のファイルを格納したリポジトリを作成することから始
めましょう 。
1 $ cat > letter.txt <<EOF
2 > Greetings!
3 > I am Mariam Abacha, the wife of former
4 > Nigerian dictator Sani Abacha.
5 > EOF
6 $ hg add letter.txt
7 $ hg commit -m ’419 scam, first draft’
次に、リポジトリを複製し、ファイルを変更します。
1 $ cd ..
2 $ hg clone scam scam-cousin
3 updating working directory
4 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 $ cd scam-cousin
6 $ cat > letter.txt <<EOF
7 > Greetings!
8 > I am Shehu Musa Abacha, cousin to the former
9 > Nigerian dictator Sani Abacha.
10 > EOF
11 $ hg commit -m ’419 scam, with cousin’
もう一つリポジトリを複製し、他の利用者によるファイルへの変更を模擬的に再現します(この模擬的な実行は、タスクごとに隔離
したリポジトリの間でのマージどころか、それらのマージの際の衝突を解消することですら、決して珍しいことではない、ということを
暗示しています)。
1 $ cd ..
2 $ hg clone scam scam-son
3 updating working directory
4 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 $ cd scam-son
6 $ cat > letter.txt <<EOF
7 > Greetings!
8 > I am Alhaji Abba Abacha, son of the former
9 > Nigerian dictator Sani Abacha.
10 > EOF
11 $ hg commit -m ’419 scam, with son’
同一ファイルに2つの異なる版ができたので、マージ実施の環境が整いました。
1 $ cd ..
2 $ hg clone scam-cousin scam-merge
3 updating working directory
4 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 $ cd scam-merge
6 $ hg pull -u ../scam-son
7 pulling from ../scam-son
8 searching for changes
9 adding changesets
10 adding manifests
11 adding file changes
12 added 1 changesets with 1 changes to 1 files (+1 heads)
13 not updating, since new heads added
14 (run ’hg heads’ to see heads, ’hg merge’ to merge)
マージにおける対話的な処理の部分が、本書における実行例の自動実行機構 refsec:automated-example-running を損ねるため、こ
の例では Mercurial のhgmerge を使用しません。その代わりに、HGMERGE を設定することで、 Mercurial に非対話的なmerge コマン
ドを実行させます。このコマンドは多くの Unix 的なシステムに同梱されています。以下の例を実際に試す際には、HGMERGE をわざわ
ざ設定する必要はありません。
1 $ export HGMERGE=merge
2 $ hg merge
3 merging letter.txt
4 merge: warning: conflicts during merge
5 merging letter.txt failed!
6 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
7 use ’hg resolve’ to retry unresolved file merges or ’hg up --clean’ to abandon
8 $ cat letter.txt
9 Greetings!
10 <<<<<<< /tmp/tour-merge-conflictXc7qDZ/scam-merge/letter.txt
11 I am Shehu Musa Abacha, cousin to the former
12 =======
13 I am Alhaji Abba Abacha, son of the former
14 >>>>>>> /tmp/letter.txt~other.Fi_RTZ
15 Nigerian dictator Sani Abacha.
merge コマンドは衝突を解消せずに、どの行における変更が衝突していて、その変更がどのチェンジセットに由来するのかを示す
マージマークを、衝突が検出されたファイルに書き込みます。
Mercurial は、mergeの終了コードがマージ処理 失
敗を示す場合、マージ処理を再実行する手順を表示します。ここで提示される手順は、マージ作業の途中で混乱して
しまったり、間違ってしまったことに気付いて、グラフィカルなマージツールを中途終了させた場合などに役立ちま
す。
自動ないし手動のマージが失敗した場合であっても、関連の有るファイルを直接 “修正” した上で、マージ結果をコミットすること
も可能です。
1 $ cat > letter.txt <<EOF
2 > Greetings!
3 > I am Bryan O’Sullivan, no relation of the former
4 > Nigerian dictator Sani Abacha.
5 > EOF
6 $ hg commit -m ’Send me your money’
7 abort: unresolved merge conflicts (see hg resolve)
8 $ hg tip
9 changeset: 2:ad1a49b7184f
10 tag: tip
11 parent: 0:ac90448432c1
12 user: Bryan O’Sullivan <bos@serpentine.com>
13 date: Mon Jul 20 21:59:09 2009 +0000
14 summary: 419 scam, with son
15
3.3 Simplifying the pull-merge-commit sequence
ここまでに述べてきた変更マージの手順は単純なものですが、3つのコマンドを順に実行する必要があります。
1 hg pull
2 hg merge
3 hg commit -m ’Merged remote changes’
最後のコミットの際には、通常は面白くも無い “決まりきった” 内容にならざるを得ませんが、コミットメッセージを入力する必要
があります。
可能であれば、必要とされる手順を低減させたいものです。実際に Mercurial は、これを可能とするfetchと呼ばれるイクステン
ションが同梱されています。
Mercurial は、取り扱いの利便性上から中核機能を小さく簡潔に保つ一方で、機能追加を可能にするための柔軟
な拡張(イクステンション)機構を提供しています。コマンドラインから利用できる Mercurial コマンドを追加する
イクステンションもあれば、例えばサーバ機能を拡張するような、 “舞台裏” で機能するイクステンションもありま
す。
fetch イクステンションは、予想したこととは思いますが、“hg fetch” と呼ばれる新しいコマンドを追加します。“hg fetch” コ
マンドは、“hg pull” /“hg update” /“hg merge” /“hg commit” の組み合わせのように振舞います。まずは他のリ
ポジトリから作業中のリポジトリへ変更を取り込みます。取り込んだチェンジセットによる新たな head の追加が検
知 された
場合、マージを開始し、自動的に生成されたコミットメッセージを使ってコミットを行います。新たな head の追加が無かった場
合、“hg fetch” コマンドは作業領域ディレクトリを tip リビジョンで更新します。
fetch イクステンションは簡単に有効化できます。.hgrc ファイルを編集し、[extensions] セクション(無い場合は作成してく
ださい)に移動し、 “fetch ” で始まる行を追加します。
(通常は、 “=” の右辺にイクステンションの位置を指定しますが、fetch イクステンションは標準の配布物に同梱されているので、
Mercurial はfetch を探し出すことができます)