前回に引き続き "Getting started with Mercurial Queues" から読み進めます。 ここからいよいよ実際の MQ 利用に関する説明が始まります。
まずは extensions 設定を記述して MQ を有効化し、 MQ の利用に先立ってリポジトリに固有領域を作成する話から始まります。
余談になりますが、 以前は各リポジトリ配下の .hg/hgrc に extensions 設定を書いても有効にならなかった (読み込み処理で無視されていた)のですが、 リリースノートによると 0.9.4 からはそれも有効になったそうなので、 リポジトリ固有の拡張機能は ~/.hgrc を変更する必要がなくなりました。 実験的に extension を書く際には、この対応は結構嬉しいものです。
ここでの肝は、 「.hg/patches 配下のファイルを手でいじるな」ということと、 「通常の hg コマンドが普通に使えますよ」ということです。
また、TeX ベースで作成されているため
(TeX ではページに占める図の比率を一定程度以下に保つため、
ソース上での図の挿入指示と実際の配置位置が異なることがあります)
図と説明が前後してしまっていますが、
hg qnew を行うコードサンプルは、
こちらのサブセクションに関する図です。
原文の:
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 Katsunoridate: 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
ちなみに、
hg qnew でチェンジセットと関連付けられた「パッチ」は、
hg qrefresh で何度でも改変できるという特徴はあるものの、
通常のチェンジセットと同様の扱いを受けるため、
例えば他のリポジトリから hg pull すると、
改変不可能な通常のチェンジセットとしてコピーされてしまいます。
ここで述べられている「hg qnew を実施することで、
それ以前のパッチによる改変部分が、
diff 出力の context 部分に表示されるようになる」というのは、
hg qnew により生成されるチェンジセットと、
それ以前のパッチによる改変部分の属するチェンジセットが異なる、
ということを考えれば至極当然の話ですので、
このサブセクションでは特に目新しい話はありません。
「適用済み(applied)」と「管理対象(known)」があるらしいことを匂わせつつ、 次のサブセクションに続きます。
hg qnew された「パッチ」の内容は、
hg qpopにより「非適用」な状態になっても
MQ により管理され続けます。
原文の概念図では単純なスタックの様相が提示されているのですが、
hg qpop によってスタックの途中に位置した状態で
hg qnew したら、
新しく作成されるパッチはスタックのどこに位置するのでしょう?
それとも分岐してツリー状になる?
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 により作成されたパッチは、
間違いなくスタック途中に挿入されるようです。
原文の 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 なファイルの存在は無視されるようですので注意が必要です。
「rollback による取り消しと再 commit 」により、 commit 直後の tip が例外的に改変(と同等の効果のある操作が)できる以外は、 通常なら一度 commit したチェンジセットは改変できません。 一方で MQ で管理されるパッチ(=チェンジセット)は、 qpush/qpop によって選択されたチェンジセットの qrefresh による改変を、 任意の時点で実施できます。
そういった点から見ても、 MQ による「パッチスタック」とやらが、 なかなか便利そうであることがわかってきました。
以下、次回に続く。
"Distributed revision control with Mercurial" 関連エントリの一覧は、BOSBook(Bryan O’Sullivan Book)タグで参照できます。