Mercurial 勉強中 (5) - conflict と multiple heads, merge

他レポジトリとの conflict 状態と multiple heads の関係を step by step で確認していきます。

Step 1: 既存レポジトリを clone する

まずは,既存のレポジトリを clone します。

[hg]% hg clone remote local
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

[hg]% cd remote/

[remote]% ls -F
message.txt

message.txt があるだけの単純なレポジトリです。

現在の状態は下記のようになっています。

remote と local の間に conflict を発生させる

あえて conflict を発生させます。

remote 側に「foo」という内容,local 側には「bar」という内容を追加して,それぞれ commit してみます。

[remote]% cat >> message.txt
foo
^D

[remote]% cat message.txt
=== contents of message.txt ===
foo

[remote]% hg ci -m "foo added"
[local]% cat >> message.txt
bar
^D

[local]% cat message.txt
=== contents of message.txt ===
bar

[local]% hg ci -m "bar added"

この状態は,下記のようになっています。



それぞれのレポジトリでリビジョン番号 #2 が振られていますが,変更内容の異なる changeset です。

local に remote の内容を pull する

まだお互いのレポジトリで編集作業を行っただけであり,conflict は発生していません。

なので,いざいざ pull してみましょう。

[local]% hg pull
pulling from /home/dayflower/hg/remote
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)

hg pull 自体はうまくいきました。ですが,heads が増えたこと(「+1 heads」というメッセージ)が示されています。また,それを確かめるには hg heads と入力すればいいこと,merge するためには hg merge すればいいことも表示されています。

hg heads で現状どのような heads が存在するのか見てみましょう。

[local]% hg heads
changeset:   3:618e76e5a8d4
tag:         tip
parent:      1:9ed8a117e04c
user:        dayflower
date:        Mon Mar 10 16:57:30 2008 +0900
summary:     foo added

changeset:   2:fe56186971fd
user:        dayflower
date:        Mon Mar 10 16:57:49 2008 +0900
summary:     bar added

ローカルレポジトリとしては remote で追加した foo に関する changeset が一番新しいものになっていること((tip というタグがついています)),また 2 つの heads が存在することがわかります。

これを図示すると下記のようになります。



いままで編集していた内容は消えてしまったのでしょうか?心配ご無用。Working copy の parents(直近の親 changeset)を確認してみましょう。

[local]% hg parents
changeset:   2:fe56186971fd
user:        dayflower
date:        Mon Mar 10 16:57:49 2008 +0900
summary:     bar added

[local]% cat message.txt 
=== contents of message.txt ===
bar

この通り,いままで編集していた内容のままになっていると考えてよさそうです。

merge する

では merge してみます。

[local]% hg merge
merging message.txt
conflicts detected in /home/dayflower/hg/local/message.txt

(このあと,$EDITOR で指定されたエディタが開き,
 conflict を解消するべく編集することになります)

デフォルトでは hgmerge というコマンドにより,diff3 形式のファイルを編集することになります。具体的にはこんな見た目です。

=== contents of message.txt ===
<<<<<<< /home/dayflower/hg/local/message.txt.orig.248014591
bar
||||||| /tmp/message.txt~base.GQR1A-
=======
foo
>>>>>>> /tmp/message.txt~other.ZwCxK9

んーわかりにくいっすね。

ともかく,編集を終了すると下記のメッセージが表示されます。

0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)

[local]% cat message.txt
=== contents of message.txt ===
bar
foo

単純に両方の成果をマージしたものにしました。

この時点で Working copy の parents がどのようになったのか確認してみましょう。

[local]% hg parents
changeset:   2:fe56186971fd
user:        dayflower
date:        Mon Mar 10 16:57:49 2008 +0900
summary:     bar added

changeset:   3:618e76e5a8d4
tag:         tip
parent:      1:9ed8a117e04c
user:        dayflower
date:        Mon Mar 10 16:57:30 2008 +0900
summary:     foo added

remote の内容を反映した changeset:3 と local でいじっていた changeset:2 の両方が parents となっていることがわかります。

「don't forget to commit」といわれたので commit します。

[local]% hg ci -m "merged from remote"

[local] % hg heads
changeset:   4:1a899fefa9db
tag:         tip
parent:      2:fe56186971fd
parent:      3:618e76e5a8d4
user:        dayflower
date:        Mon Mar 10 17:00:28 2008 +0900
summary:     merged from remote

無事 multiple heads が解消され,一本化されました。大事なのは multiple heads が解消された,ということより,merge をすることにより merge 元の heads が head ではなくなる,ということです。

図示するとこのようなイメージになります。



local の変遷を hg log コマンドで確認してみます。

[local]% hg log | egrep -e '(changeset:|summary:)'
changeset:   4:1a899fefa9db
summary:     merged from remote

changeset:   3:618e76e5a8d4
summary:     foo added

changeset:   2:fe56186971fd
summary:     bar added

changeset:   1:9ed8a117e04c
summary:     message.txt created

changeset:   0:d2966889cc12
summary:     Initial import

図と対応していることがわかるかと思います。

ここで push するとどうなるか

merge の成果を push してみます。

[local]% hg push

pushing to /home/dayflower/hg/remote
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 1 files

この時点での両レポジトリの状態は下記のようになっています。



一見,同じ内容が clone されているように見えますが,local レポジトリ側では foo という内容が changeset revision number 2 になっているのに対して,remote レポジトリ側では changeset revision number 3 になっているところが異なります。

hg log して,見てみましょう。

[local]% cd ../remote/

[remote]% hg log | egrep -e '(changeset:|summary:)'
changeset:   4:1a899fefa9db
summary:     merged from remote

changeset:   3:fe56186971fd
summary:     bar added

changeset:   2:618e76e5a8d4
summary:     foo added

changeset:   1:9ed8a117e04c
summary:     message.txt created

changeset:   0:d2966889cc12
summary:     Initial import

先ほどの hg log の結果と比較すればわかりますが,まさに上記の図のようになっていることがわかります。また,対応する changeset の hash 値が等しくなっていること*1がわかります。

すなわち,『レポジトリ全体の management としては』changeset hash id のほうが重要であることがわかります。ま,あくまでローカルで作業する分には changeset revision number で扱っていても支障はありません。

注意点

先ほどの例では hg pull する前に hg commit してますが,Mercurialcommit する前に pull してもうまく扱ってはくれます。ですが,heads の状態がわかりにくくなる可能性があること,working copy での作業内容が不明確になりうることを鑑みると,hg pull する前に,ローカル側で hg commit しておくほうがいいと思います。もちろん hg incoming してあらかじめ状況を把握しておくとなおベターです。

補足(2008/03/11 追記)

もう一度,レポジトリ間で conflict が発生している状態に立ち返って考えてみます。


この状況で remote へ push するとどうなるでしょうか。remote で branch が分岐して multiple heads の状態になりそうなものですが……

[local]% hg push
pushing to /home/dayflower/hg/remote
searching for changes
abort: push creates new remote branches!
(did you forget to merge? use push -f to force)

「abort: push creates new remote branches!」と,怒られてしまいました。

これ,意図的に切った branch を commit しようとしているのか,あるいは単に Master Repository の成果・変更を無視して push しようとしているのか,Mercurial には判断がつかないためです(だから「merge 忘れてない?」と聞かれているんですね)。安全サイドに倒す意味で,remote side で branch が分岐する(heads が増える)状況では,-f オプションをつけない限り abort するようになっているのです。


一般的には push する前に,pull して remote の変更を merge してから push することになります*2。ですが今回は,テストのため,force な push をしてみましょう。

[local]% hg push -f
pushing to /home/dayflower/hg/remote
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)

無事 push できました。heads が +1 されたよ,といわれています。

remote side での heads を見てみます。

[remote]% hg heads
changeset:   3:fe56186971fd
tag:         tip
parent:      1:9ed8a117e04c
user:        dayflower
date:        Mon Mar 10 16:57:49 2008 +0900
summary:     bar added

changeset:   2:618e76e5a8d4
user:        dayflower
date:        Mon Mar 10 16:57:30 2008 +0900
summary:     foo added

たしかに multiple heads になっています。

*1:これが,分散型 SCM でハッシュなどの unique id が採用される理由ですね。

*2:これはどんな SCM でもそうですよね