menu
書いてる野郎
orebike@gmail.com
通称MQ。パッチを管理する。管理の実装上黒魔術的にHgのリポジトリの履歴改変をする必要があった。なのでそのような履歴改変機能もおまけでたくさんついている・・・(むしろこっちメイン)
歴史改変とかリビジョンの切ったり貼ったり
初期化 | 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したやつら |
-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
適当に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 qimport
とhg qfinish
と併用すると結構うれしいのだ。qimport
でリビジョン群をパッチスタックとして抽出しqfold
で押しつぶし、qfinish
で書き戻せば。gitのrebaseコマンドみたいにリビジョンの統合もできてしまうのだ。
既存のリビジョン群をパッチキューに変換することができる。
現在、リビジョンが4まで積み重なっていてworkは4の状態から変更なしだとする。
0 | 1 | 2 | 3 | 4(tip) | work |
● | ● | ● | ● | ● | ○ |
ここでqimportのコマンドを使って
$ hg qimport -r 3:tip
リビジョン番号3から先っちょまでのリビジョン群をパッチキューに変換する
0 | 1 | 2(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
とかで対象のリビジョンを含む形でリビジョン→パッチ化
使ったバージョンはWindows版の2.0.1
MQ拡張をONにしておく
まず今回のリポジトリとなるディレクトリを用意。こいつを初期化する
$ hg init
.hg
ディレクトリが出来上がって管理ができる状態になった
.hg
ディレクトリの中は
というファイル構成になっていた。
次に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パッチ | 無 |
今回も同じように
$ 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管理自体が入れだけにしか使えなくなって、ほかの用途に使いにくくなる・・・困った。
環境は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とワークの間に入るレイヤーのように働く。 パッチの由来ブランチは関係ないということだな。
まず定番の
$ 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
今回はうまくいった。
つまりそういう挙動
実はあんまりパッチスタックをリビジョン管理しても嬉しくないような・・・
パッチ自体を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操作でパッチと対応しているのリビジョンが消滅する、しかしワークには影響無しという状態。確かにリビジョンが消えてもワークには何も無いというのは正しそうな挙動だが初めてやるとびっくりする。
ここでupdateの位置とqimportで転換した位置が一致するとまた通常のパッチ操作のようにパッチとワークが追従するようになる。
qpushによる失敗はupdateでは起きない(qpop後なのでリビジョンが無いから飛べない)ので気持ち悪さは出ない。