Mercurial: "Managing change with Mercurial Queues" を読む(2)
Tagged:  •    •  

前回に引き続き "Getting started with Mercurial Queues" から読み進めます。 ここからいよいよ実際の MQ 利用に関する説明が始まります。

まずは extensions 設定を記述して MQ を有効化し、 MQ の利用に先立ってリポジトリに固有領域を作成する話から始まります。

余談になりますが、 以前は各リポジトリ配下の .hg/hgrc に extensions 設定を書いても有効にならなかった (読み込み処理で無視されていた)のですが、 リリースノートによると 0.9.4 からはそれも有効になったそうなので、 リポジトリ固有の拡張機能は ~/.hgrc を変更する必要がなくなりました。 実験的に extension を書く際には、この対応は結構嬉しいものです。

12.5.1 Creating a new patch

ここでの肝は、 「.hg/patches 配下のファイルを手でいじるな」ということと、 「通常の hg コマンドが普通に使えますよ」ということです。

また、TeX ベースで作成されているため (TeX ではページに占める図の比率を一定程度以下に保つため、 ソース上での図の挿入指示と実際の配置位置が異なることがあります) 図と説明が前後してしまっていますが、 hg qnew を行うコードサンプルは、 こちらのサブセクションに関する図です。

12.5.2 Refreshing a patch

原文の:

This command(= hg qrefresh) folds the changes you have made in the working directory into your patch, and updates its corresponding changeset to contain those changes.

を翻訳しつつ大雑把に言い換えるなら:

hg qrefresh は、 tip チェンジセットの commit を一旦解消(= rollback)し、 現時点での変更(直前まで tip に含まれていたものを含む) 全てを含むチェンジセットとして再度 commit するようなもの

というところでしょうか。

原文にも hg qrefresh 前後のチェンジセット内容の示す実行例が掲載されていますが、 分割されているため幾分わかりづらい気がします。 そこで「チェンジセットの内容改変」に着目した実行例を以下に示します。

$ hg log
changeset:   1:c1df16e8d303
tag:         qtip
tag:         first.patch
tag:         tip
tag:         qbase
user:        fujiwara
date:        Wed Jul 25 15:03:15 2007 +0900
summary:     imported patch first.patch

changeset:   0:d45a703da8ad
tag:         qparent
user:        fujiwara
date:        Wed Jul 25 14:38:56 2007 +0900
summary:     initial status

$ hg log -r 1 -p
    # log -p によるパッチ形式の内容出力
changeset:   1:c1df16e8d303
tag:         qtip
tag:         first.patch
tag:         tip
tag:         qbase
user:        fujiwara
date:        Wed Jul 25 15:03:15 2007 +0900
summary:     imported patch first.patch


    # この時点ではチェンジセット 1 は「空」
$ echo 'line 2' >> file1
$ hg diff
diff -r c1df16e8d303 file1
--- a/file1	Wed Jul 25 15:03:15 2007 +0900
+++ b/file1	Wed Jul 25 15:03:31 2007 +0900
@@ -1,1 +1,2 @@ line 1
 line 1
+line 2
$ hg qrefresh
$ hg log
    # ハッシュ値が以前と異なっている
changeset:   1:08acf5c0361b
tag:         qtip
tag:         first.patch
tag:         tip
tag:         qbase
user:        fujiwara
date:        Wed Jul 25 15:03:52 2007 +0900
summary:     patch queue: first.patch

changeset:   0:d45a703da8ad
tag:         qparent
user:        fujiwara
date:        Wed Jul 25 14:38:56 2007 +0900
summary:     initial status
$ hg log -r 1 -p 
    # log -p によるパッチ形式の内容出力
changeset:   1:08acf5c0361b
tag:         qtip
tag:         first.patch
tag:         tip
tag:         qbase
user:        FUJIWARA Katsunori 
date:        Wed Jul 25 15:03:52 2007 +0900
summary:     patch queue: first.patch

    # qrefresh 直前の変更がチェンジセット 1 に取り込まれている
diff -r d45a703da8ad -r 08acf5c0361b file1
--- a/file1	Wed Jul 25 14:38:56 2007 +0900
+++ b/file1	Wed Jul 25 15:03:52 2007 +0900
@@ -1,1 +1,2 @@ line 1
 line 1
+line 2

qrefresh によるチェンジセットの内容改変

ちなみに、 hg qnew でチェンジセットと関連付けられた「パッチ」は、 hg qrefresh で何度でも改変できるという特徴はあるものの、 通常のチェンジセットと同様の扱いを受けるため、 例えば他のリポジトリから hg pull すると、 改変不可能な通常のチェンジセットとしてコピーされてしまいます。

12.5.3 Stacking and tracking patches

ここで述べられている「hg qnew を実施することで、 それ以前のパッチによる改変部分が、 diff 出力の context 部分に表示されるようになる」というのは、 hg qnew により生成されるチェンジセットと、 それ以前のパッチによる改変部分の属するチェンジセットが異なる、 ということを考えれば至極当然の話ですので、 このサブセクションでは特に目新しい話はありません。

「適用済み(applied)」と「管理対象(known)」があるらしいことを匂わせつつ、 次のサブセクションに続きます。

12.5.4 Manipulating the patch stack

hg qnew された「パッチ」の内容は、 hg qpopにより「非適用」な状態になっても MQ により管理され続けます。

原文の概念図では単純なスタックの様相が提示されているのですが、 hg qpop によってスタックの途中に位置した状態で hg qnew したら、 新しく作成されるパッチはスタックのどこに位置するのでしょう? それとも分岐してツリー状になる?

12.5.5 Pushing and popping many patches

hg qpush -a で全パッチ適用ということになると、 「パッチ適用順序」は神経質になるに値する重要事項ですから、 前節での疑問である 「パッチスタック途中での hg qnew は、 パッチスタックに対してどの位置にパッチを作成するのか?」 がますます深刻なものになってきます。

というわけで、 「パッチスタック途中での hg qnew は、 パッチスタックに対してどの位置にパッチを作成するのか?」 を確認してみました。

$ hg qinit
$ hg qnew 1st.patch
$ echo 'line 2' >> file1 
$ hg status
M file1
$ hg qrefresh
$ hg status
$ hg qseries
1st.patch
$ hg qnew 2nd-1.patch
$ echo 'line 3 by 2nd-1.patch' >> file1 
$ hg status
M file1
$ hg qrefresh
$ hg status
$ hg qnew 2nd-1.3rd.patch
$ echo 'line 1 by 2nd-1.3rd.patch' > file2
$ hg status
? file2
$ hg add file2
$ hg qrefresh
$ hg status
$ hg qpop
Now at: 2nd-1.patch
$ hg qpop
Now at: 1st.patch
$ hg qapplied
1st.patch
$ hg qnew 2nd-2.patch
$ echo 'line 3 by 2nd-2.patch' >> file1 
$ hg status
M file1
$ hg qrefresh
$ hg status
$ hg qnew 2nd-2.3rd.patch
$ echo 'line 1 by 2nd-2.3rd.patch' > file2
$ hg status
? file2
$ hg add file2
$ hg qrefresh
$ hg status
$ hg qpop
Now at: 2nd-2.patch
$ hg qpop
Now at: 1st.patch
$ hg qpop
Patch queue now empty
パッチスタック途中での hg qnew

もしもパッチがツリー上に分岐しているのなら、 上記の操作で以下のようなツリーが形成されている筈です。

想定されるパッチ「ツリー」

それではパッチを一括適用してみましょう。

$ hg qpush -a
applying 1st.patch
applying 2nd-2.patch
applying 2nd-2.3rd.patch
applying 2nd-1.patch
file1
Hunk #1 succeeded at 1 with fuzz 2.
applying 2nd-1.3rd.patch
1 out of 1 hunk ignored -- saving rejects to file file2.rej
patch failed, unable to continue (try -v)
patch failed, rejects left in working dir
Errors during apply, please fix and refresh 2nd-1.3rd.patch
全パッチの一括適用

何のことは無い、 hg qnew で生成されるパッチは、 最上段の「applied patch」(この場合は 1st.patch) と最下段の「not applied patch」(この場合は 2nd-1.patch)の間に生成されます。 「スタック」と言い切っているのですから、 確かにそれ以外に作りようが無いですよね。

実際のパッチ「スタック」

「本当はツリー状に管理しているけど、 パッチ適用履歴を管理しているので、 それを元に適用順序を決定しているのでは?」という推理も成立します (あくまで「スタックと明言している」という点を無視すれば、ですが…)ので、 hg qpush にパッチ名を指定して、 明示的に「2nd-1.patch 側の分岐」のパッチを適用させてみましょう。

$ hg qpop -a
Patch queue now empty
$ hg qpush
applying 1st.patch
Now at: 1st.patch
$ hg qpush 2nd-1.patch
applying 2nd-2.patch
applying 2nd-2.3rd.patch
applying 2nd-1.patch
file1
Hunk #1 succeeded at 1 with fuzz 2.
Now at: 2nd-1.patch
パッチ名を指定しての適用

やはり 2nd-2.patch が先に適用されます。 スタック途中で hg qnew により作成されたパッチは、 間違いなくスタック途中に挿入されるようです。

12.5.6 Safety checks, and overriding them

原文の Figure 12.13: Forcibly creating a patch の実行例は、 1 行目と 2 行目の間に "hg add file3" が抜けているため、 -f 付きの方が実行に失敗しています。 正しくは以下のような実行手順になる筈です。

$ echo 'file3 line1' > file3
$ hg add file3
$ hg qnew add-file3
abort: local changes found, refresh first
$ hg qnew -f add-file3
hg qnew -f の正しい実行例

ちなみに、hg add されていない unknown なファイルの存在は無視されるようですので注意が必要です。

12.5.7 Working on several patches at once

「rollback による取り消しと再 commit 」により、 commit 直後の tip が例外的に改変(と同等の効果のある操作が)できる以外は、 通常なら一度 commit したチェンジセットは改変できません。 一方で MQ で管理されるパッチ(=チェンジセット)は、 qpush/qpop によって選択されたチェンジセットの qrefresh による改変を、 任意の時点で実施できます。

そういった点から見ても、 MQ による「パッチスタック」とやらが、 なかなか便利そうであることがわかってきました。


以下、次回に続く。


"Distributed revision control with Mercurial" 関連エントリの一覧は、BOSBook(Bryan O’Sullivan Book)タグで参照できます。