Mercurial/Mercurial Queues

Mercurial/Mercurial Queues

Mercurial Queuesとは

通称MQ。パッチを管理する。管理の実装上黒魔術的にHgのリポジトリの履歴改変をする必要があった。なのでそのような履歴改変機能もおまけでたくさんついている・・・(むしろこっちメイン)

MQ拡張を使うと何ができるようになる

歴史改変とかリビジョンの切ったり貼ったり

コマンド

初期化
hg qinit
パッチ使うリポジトリに関しての最初の準備 hg initみたいなもの
hg qinit -c
パッチスタックの準備に加えてそのパッチ自身をhgでリビジョン管理するための初期化を同時に行う
hg qnew hoge
管理するパッチを新規に宣言する。現状として変更がある(hg diffの結果が出る)ならばその変更分を取り込んだパッチができる
記録
hg qrefresh -m "hoge"
ワークの変更分をMQのパッチとして管理に取り込む。hg commitのパッチ版。取り込み先はhg qtopで示されるパッチ
パッチ剥がし
hg qpop
ワークからMQが管理しているパッチを1個引き剥がす
hg qpop 3
ワークから現在MQが管理しているパッチ全体のうちを3番目の状態になるまで引き剥がす。つまりゼロを指定すれば全部引き剥がした状態になる
hg qpop hoge
ワークから現在MQが管理しているパッチhogeがあたっているの状態になるまで引き剥がす
hg qpop -a
ワークから現在MQが管理しているパッチを全部引き剥がす
パッチ当て
hg qpush
MQが管理しているパッチを1個あてる
hg qpush 3
ワークに現在MQが管理しているパッチ全体のうちのを3個目までをあてる。3回あてるでは無い
hg qpush hoge
MQが管理しているパッチhogeまで順番にあてる
hg qpush -a
MQが管理しているパッチを全部あてる
パッチ操作
hg qfold hoge
現在当たっているパッチに指定した名前のパッチ(同一パッチスタックのパッチ)を統合する
確認
hg qtop
現在適用されているパッチ(最後にpushしたパッチ)が何かを表示する
hg qseries
現在管理しているパッチスタックの中身を全部表示する。つまりqnewしたやつら

メモqqueueのオプション

-l --list   list all available queues
   --active print name of active queue
-c --create create new queue
   --rename rename active queue
   --delete delete reference to queue
   --purge  delete queue, and remove patch dir

キーワード

  • パッチ
    • MQ管理するソースコード変更の最小単位。こいつを付けたり外したりする
  • パッチスタック
    • MQはパッチをスタック構造で当てる順番をシーケンシャルに管理する。このユーザーの操作側からみたパッチ群をパッチスタックという
  • パッチキュー
    • パッチスタックは最終的に本体のソースに連続的に適用されるわけだが、この本体ソース側から見たパッチ群をパッチキューと言う・・・っぽい

パッチをマージする

適当にqnewしていると単なるタイポを直したり、スペースやコメントの調整だけの意味のない変更が蓄積してしまったりする。

こういう時に複数のパッチを1個にまとめることができる

$ hg qseries
a
b
c

こういう状態で

$ hg qtop
c

こうだとする。つまりabc全部のパッチが当たっている状態。

この状態からaだけ当たった状態にする

$ hg qpop -a
$ hg qpush
$ hg qtop
a

この状態で

$ hg qfold b

とする。

そうすると

$ hg qseries
a
c
$ hg qtop
a

こうなる

「パッチb」が「パッチa」に取り込まれた。

どんなパッチでもマージできるわけではない。同じ状況で

$ hg qfold c

とやると失敗してしまったりする。なぜかと言えば「パッチc」のあたる条件が「パッチb」を前提にしている場合があるから。 こういう場合はパッチのマージが失敗する。連続性が重要なのだ

なので

$ hg qfold b
$ hg qfold c

ならば

$ hg qseries
a
$ hg qtop
a

で全部のパッチを1個に統合できた。

当然連続性に問題が無いなら飛び飛びになっているパッチを1個にすることができる。

あんまり嬉しい気がしないが、hg qimporthg qfinishと併用すると結構うれしいのだ。qimportでリビジョン群をパッチスタックとして抽出しqfoldで押しつぶし、qfinishで書き戻せば。gitのrebaseコマンドみたいにリビジョンの統合もできてしまうのだ。

既存のリビジョン群をパッチキューに変換

既存のリビジョン群をパッチキューに変換することができる。

現在、リビジョンが4まで積み重なっていてworkは4の状態から変更なしだとする。

01234(tip)work

ここでqimportのコマンドを使って

$ hg qimport -r 3:tip

リビジョン番号3から先っちょまでのリビジョン群をパッチキューに変換する

012(qparent)3(qbase)4(tip,qtip)work

するとこうなる。◆がパッチ、ワークはqimportの前と変わっていない

この段階では変換というか吸出し+ブヨブヨ化と表現したほうがいいかな。 事実log上はリビジョンが普通に存在していてそのリビジョンへupdateすることが可能なのだ。

qimportはリビジョン群の端っこ単位で適用する。つまり長いリビジョンの途中の一部だけを指定してqimportすることはできないということ。必ずtipのような子供を持っていないリビジョンを含めないといけない

途中をしてししまうと

abort: revision 2 has unmanaged children

のようなエラーが出てqimportに失敗する。

コミットメッセージの変更

$ hg qimport -r 3:tip

とかで対象のリビジョンを含む形でリビジョン→パッチ化

メモ1

使ったバージョンはWindows版の2.0.1

MQ拡張をONにしておく

まず今回のリポジトリとなるディレクトリを用意。こいつを初期化する

$ hg init

.hgディレクトリが出来上がって管理ができる状態になった

.hgディレクトリの中は

  • storeディレクトリ(中身空)
  • 00changelog
  • requires
  • thgstatus

というファイル構成になっていた。

次にMQ用の初期化をする。

$ hg qinit

他は何も変わらずpatchesというディレクトリが一個.hgディレクトリに出来上がった中身は空だ。 このpatchesというディレクトリの存在がMQ初期の印らしい。

これから管理する変更分をパッチ群として管理するためその名前をつけてその群のスタートの合図をする。 今回は最初のキューということでq1にする

$ hg qnew q1

これをやると.hgの中におそらくパッチを管理するための管理ファイルがいろいろ作られる。 pachesの中にq1を管理してそうなq1というファイルができていたのでたぶんここらへんで管理しているのだろう

ここで登場人物は3人

ワーク
defaultブランチ
q1パッチ

このような状態だ。

ファイルを追加してみる。定番のindex.htmlを追加してみる。内容は

<html>
    <head></head>
    <body></body>
</html>

こんな感じで中身なし

ここで

ワーク中身なしhtml
defaultブランチ
q1パッチ

の状態になったわけだ

じゃあdefaultブランチにこの変更をコミットする

$ hg status
? index.html

ということでindex.htmlが管理下に入ってないよということで入れる

$ hg add index.html

入った

$ hg status
A index.html

コミットしようと思ったら・・・

$ hg commit
abort: cannot commit over an applied mq patch

だって。有効なmqのパッチを越えてコミットできませんよと。つまりq1が有効だからコミットできないよと

どうやらMQのパッチがあたっている状態ではリビジョンを先に進めることはできないようになっているようだ。

それではhgのりビジョンではなく、現在の変更をmq側に記録することにする

$ hg status
A index.html

ワークの内容をパッチとして吸い上げる。mオプションをつけて、 この記録へのメッセージをわかりやすいように書き込んでおく

$ hg qrefresh -m "1st"

これはhg commitと似たような処理になる。

そしてリポジトリ状態を確認

$ hg status

無・・・・さっき

A index.html

ってなってたのに何もなかったようになっている。

TortoiseHg上でリビジョンのグラフを見ると、 こんな風になっていて、通常のリビジョンだったら丸いのに、ひし形になっている

ということで

ワーク 中身なしhtml
defaultブランチ
q1パッチ 中身なしhtml

状態になったということか では再びコミットしてみると・・・

$ hg commit
abort: cannot commit over an applied mq patch

同様に起こられた。とにかくまたげないよ・・・パッチがあたっているからね・・・

ついでにちょっと変更を入れてみる

<html>
    <head></head>
    <body>hoge</body>
</html>

bodyに少し書き込んだ

hg status
M index.html

Mになっている。つまりHgの管理対象にはもう入っていて、それから更新がありましたよ, ということにちゃんとなっている。 ・・・がしかし正常なリポジトリにはなっていない。

defaulブランチに居ながら、MQ(q1)亜空間みたいなところでふわふわしている状態。

この変更を取り込む

$ hg qrefresh -m "2nd"

そしてグラフを改めて見てみる・・・まったく変化していない

まるで1stが無かったように1stと2ndの合成結果。ワークと同じものがそこにあるようだ。

こういう状態を予測していたが

ワーク 中身なしhtml
defaultブランチ
q1パッチ 中身なしhtml→bodyちょっとhtml

実際はこうだった

ワーク bodyちょっとhtml
defaultブランチ
q1パッチ bodyちょっとhtml(中身なしhtmlと合成されたもの)

では亜空間から脱出。

$ hg qfinish -a

このコマンドでパッチを全部リビジョン化する。 リビジョンになる。つまりパッチじゃない、つまりパッチがあったっていない。つまり脱出

グラフはこうなった。

$ hg qseries

無・・・MQ亜空間は消滅してしまった。 パッチの蓄積分が全部元のブランチに反映されてリビジョンが進んだ

ワークbodyちょっとhtml
defaultブランチ無→bodyちょっとhtml
q1パッチ

メモ2

今回も同じように

$ hg init
$ hg qinit

まず通常のhg側でリビジョンを伸ばしてみる 定番のindex.htmlを作る

<html>
<head></head>
<body>
</body>
</html>

hg側のリビジョンを伸ばす

$ hg status
? index.html
$ hg add index.html
$ hg commit

少し変更

<html>
<head></head>
<body>
hoge
</body>
</html>
$ hg status
M index.html
$ hg commit

ファイルpiyo.htmlを一個追加

<html>
<head></head>
<body>
This is piyo.
</body>
</html>
$ hg status
? piyo.html
$ hg add piyo.html
$ hg commit

グラフ上はこうなっている

それではパッチ管理を始めてみる

$ hg qnew q1

グラフ上ではこうなった。MQ用のリビジョンが1個差し込まれたような感じになっている。

それではワーク上の2個のファイルにそれぞれ変更を入れてみる

index.html

<html>
<head></head>
<body>
hoge
mod for q1
</body>
</html>

piyo.html

<html>
<head></head>
<body>
This is piyo.
mod for q1
</body>
</html>

確認

$ hg status
M index.html
M piyo.html

hg側にcommit….当然できないよね

$ hg commit
abort: cannot commit over an applied mq patch

適用されているパッチを確認

$ hg qapplied
q1

確かにq1パッチが適用されている

じゃあこの変更をパッチ側に取り込む

$ hg qrefresh -m "mod for q1-1"

確認

$ hg status

取り込まれたようだ

ではコミットしてみよう

$ hg commit
abort: cannot commit over an applied mq patch

うむ、できない。こういうことだ。 MQは開発者ローカル環境の便利ツールだということ。パッチ化したものはローカル環境のみで使われることを前提にしていて、 リビジョンとして積み上げて外に出すことはできない。

したいなら

$ hg qfinish -a

でパッチをリビジョン化してhg側に焼きこまないといけないということだ。

ということでパッチを取り除いてみる

$ hg qpop
popping q1
patch queue now empty

これでq1が取り除かれた。

グラフを見ると取り除いたパッチがまったく宙ぶらりんのどこにもつながっていない状態で 表示されて、hgのリビジョンはパッチの前の状態に戻っている。

確認してみよう。両方のファイルから

mod for q1

の記述がなくなっている。

$ hg status

特に何もなし。本当にパッチ適用前に戻ってしまったようだ。

今まっさらだからまたパッチを当ててみる

$ hg qpush
applying q1
now at: q1

確認してみよう。両方のファイルに

mod for q1

の記述が戻っている。

グラフはこのようになった。 MQはワークとリビジョンのtipとの間に割り込みをかけるレイヤーのような働きがあるとうことだな。 介入する部分はワークとtipとの間なのでローカルに閉じてるしhgのリビジョンとも無関係ということだな。

ではパッチをもう一個作ってみよう 現在のパッチを取り外して、素の状態に戻す

$ hg qpop
popping q1
patch queue now empty

戻した状態でもう一個パッチを新規に作る

$ hg qnew q2

グラフはこうなる。tipとworkの間にMQが介入してq2を滑り込ませている。q1は宙ぶらりんで外れている

変更を書き込む。index.html,piyo.html共に

mod for q2

を追記する。

$ hg status
M index.html
M piyo.html

パッチに記録

$ hg qrefresh -m "mod for q2-1"

記録できた

確認

$ hg qseries
q2
q1

じゃあ次にq2パッチをはずしてq1パッチをあててみる。

自分の頭の中では1個のhgのリビジョンを元にして2個のパッチを作成したことになっているので、 パッチのマルチプルヘッド状態になっていると思っている

さてやってみよう。まず全部外す

$ hg qpop -a
popping q2
patch queue now empty

aオプションを使って全部のパッチを剥ぎ取る fオプションでワークの変更まで剥ぎ取ることができる

もう一度全部当ててみる

$ hg qpush -a
applying q2
applying q1
patching file index.html
Hunk #1 FAILED at 1
1 out of 1 hunks FAILED -- saving rejects to file index.html.rej
patching file piyo.html
Hunk #1 FAILED at 1
1 out of 1 hunks FAILED -- saving rejects to file piyo.html.rej
patch failed, unable to continue (try -v)
patch failed, rejects left in working dir
errors during apply, please fix and refresh q1

こうなった。

q2,q1の順で適用されて。q1のパッチを当てる際に失敗している。 そりゃそうだq1はhgの元のリビジョン状態に対するパッチであってq2があたった状態のパッチではないのだ。

1個のMQのスタックでいったりきたりして連続性の無い変化を記録しちゃいかんということだ。

ワークはq2が適用された状態で停止、q1対象のrejファイルが作られていた。

確認してみると

$ hg qapplied
q2
q1

ということで失敗しても思いっきり適用されてることになっている・・・

とりあえず剥ぎ取ろう

$ hg qpop -a
popping q1
popping q2
patch queue now empty

MQの管理もワークも元通りに戻った。

ということはq1を当てることはできないのか?

ヘルプを見ると

$ hg -v help qpush
 --move      reorder patch series and apply only the patch

こんな頼もしいオプションがあったのでやってみる

$ hg qpush --move q1
applying q1
now at: q1
$ hg qapplied
q1

お!あたった

$ hg qseries
q1
q2

ん? 順番が入れ替わっている なので全部剥ぎ取って・・・

$ hg qpop -a
popping q1
patch queue now empty

もう一度あてると

$ hg qpush -a
applying q1
applying q2
patching file index.html
Hunk #1 FAILED at 1
1 out of 1 hunks FAILED -- saving rejects to file index.html.rej
patching file piyo.html
Hunk #1 FAILED at 1
1 out of 1 hunks FAILED -- saving rejects to file piyo.html.rej
patch failed, unable to continue (try -v)
patch failed, rejects left in working dir
errors during apply, please fix and refresh q2

ということで今度はq1が有効になってq2の段階で失敗する。moveというのは対象のパッチを先頭に持っていって当てるということなのね

これでも単一パッチの入れ替え管理はできるが、 これではMQ管理自体が入れだけにしか使えなくなって、ほかの用途に使いにくくなる・・・困った。

メモ3 ブランチとMQ

環境はMac版のMercurial 2.1

定番の

$ hg init
$ hg qinit

で初期化

適当に変更とコミットを繰り返してしてdefaultブランチのリビジョンを伸ばす。

次に

$ hg qnew q1

でパッチ管理開始

ワークを変更して

$ hg qrefresh -m "mod for q1"

でパッチに取り込む。

取り込んだ後パッチを引き剥がす

$ hg qpop -a

さらにリビジョンを幾つか戻る

$ hg update 1234

ブランチ切る

$ hg branch dev1

ワークを変更して(パッチで操作したとはあんまり関係ない別の場所を変更)コミット

$ hg commit -m "mod hogehoge"

そこへパッチをあてる

$ hg qpush -a

普通にあたった。 実験的にやっているのでソースコードの乖離がほとんど無いということもあるが。

ワークは「mod hogehoge」で反映した変更にちゃんとq1の変更が合成されたものになっていた。

ということでやっpりMQの管理するパッチはやっぱりtipとワークの間に入るレイヤーのように働く。 パッチの由来ブランチは関係ないということだな。

メモ5 qq

まず定番の

$ hg init
$ hg qinit

で初期化して、適当にリビジョンを積み重ねる。

途中から

$ hg qnew q1

でパッチ管理開始

そこで

$ hg qqueue -l
patches (active)

ということでブランチの標準がdefaultブランチなのようにMQのパッチ管理のデフォルとは

patches

らしい。

とういうことはpatchesによって今q1が管理されているということだな

適当にワークを変更してq1に変更を記録する

$ hg qrefresh

もう一個開始

$ hg qnew q2

適当にワークを変更してq2に記録

$ hg qrefresh

この時点でワークから全部剥ぎとってみる

$ hg qpop -a

もとに戻った。

そして作ってみる

$ hg qqueue -c qq1

こうなる

$ hg qqueue -l
patches
qq1 (active)

出来上がってactiveが移っている

ということは当然

$ hg qseries

何も無い

管理を開始して hg qnew qq1q1 じゃあこいつのためのファイルpiyo.htmlを追加

$ hg add piyo.html
$ hg qrefresh

記録できた。では切り替えてみよう

hg対MQではパッチが当たった状態ではhg側に制御を移すことはできなかった ではMQ対MQならどうだろうか

$ hg qqueue patches
abort: patches applied - cannot set new queue active

となった。パッチがあたっている状態では切り替えられないようだ

では全部剥ぎとってみる

$ hg qpop -a

移動する

$ hg qqueue patches

今回はうまくいった。

つまりそういう挙動

メモ6 パッチスタックをリビジョン管理

実はあんまりパッチスタックをリビジョン管理しても嬉しくないような・・・

パッチ自体をpull,したりpushしたりできると嬉しいのでリビジョン管理してみる

初期化

$ hg init

適当にコミットを繰り返してリビジョンを伸ばす

パッチスタックをリビジョン管理用で初期化。もうすでにhg qinitで初期化されていた場合は、管理ファイルの追加という動作になる

$ hg qinit -c

こうすると本当にばかみたいに普通に

.hg/patches/.hg

が作られるだけの状態になる。つまりパッチ群をさらに別リポジトリで管理しようというわけだ。 本線のリポジトリとはまったくなんの関係も無い。

自動的にhgignoreファイルも作られていて内容は

^\.hg
^\.mq
syntax: glob
status
guards

こんな感じだ。これらは管理しないらしい

あとは今まで変更のパッチへの取り込みqrefreshでやっていたやつを

hg qcommit -m "hoge"

にすれば取り込みに加えて、リビジョンの進めてくれる。

ただそんだけ。

そして、本線をpushで戻してもpatchesは完全無視でローカルにとどまったまま。

つまりパッチはパッチでなんかウマイこと管理しないとだめだな。

パッチをあてるあてないと実際のソースは別管理

別管理みたいなのでちょっと気持ちが悪いが実用性を考えるとこれはこれでいいのかも

具体的にはどんなところで気持ち悪さがでるかというと、 ワークを操作してパッチの前提条件が崩れると

$ hg qpush

でのパッチ当てが失敗する。

このような場合hoge.rejファイルが作られて、実ファイルは変更なし・・・なのだがqpush自体は完了していて

$ hg qtop

はqpushであてられるはずだったパッチになっている。つまりあたったかあたってないかは関係なくqpushはあてる動作をしてポインタを進めるのだ。

他の場合では、qimportとかしてリビジョンをパッチ化した場合リビジョンの歴史改変とパッチオペレーションが一体化することになる。ここでおかしいことになる。

変換後はパッチが全部あたっている状態になっているが、ここで

$ hg update -r 2

とかでワークのソースをまったく別のものにできてしまうんだ。 しかしあたっているパッチを確認すると

$ hg qtop
3.diff

みたいな感じでMQ自体はパッチをあてているという認識になっている。

なのでここでqpopも可能だ。このようにqimport後にあたっているパッチとワークに乖離が出来てしまった場合(逆方向パッチがうまく当たらない状態)MQの挙動はリビジョン操作のみになる。つまりqpop操作でパッチと対応しているのリビジョンが消滅する、しかしワークには影響無しという状態。確かにリビジョンが消えてもワークには何も無いというのは正しそうな挙動だが初めてやるとびっくりする。

  1. qimport
  2. updateでワークを巻戻し
  3. qpopでパッチを引き剥がし
    • パッチを引き剥がし
    • ワークを巻き戻し
    • リビジョンを消滅

ここでupdateの位置とqimportで転換した位置が一致するとまた通常のパッチ操作のようにパッチとワークが追従するようになる。

qpushによる失敗はupdateでは起きない(qpop後なのでリビジョンが無いから飛べない)ので気持ち悪さは出ない。

関連ページ

バージョン

  • 2012-05-07 qpop, qpushのインデックス指定の間違いを修正等 TokyoMercurial で得たことを加筆
  • 2012-05-07 qfoldの使い方を追加
  • 2012-04-22 メモ6追加
  • 2012-04-22 メモ4,5追加
  • 2012-04-20 メモ2,メモ3追加
  • 2012-04-19 新規

タグ

vcs/hg/mercurial_queues.txt · 最終更新: 2017-10-10 10:50 by ore