読者です 読者をやめる 読者になる 読者になる

Rebase拡張を利用しないrebase方法

mercurial

このエントリはhttp://partake.in/events/902cd6d9-0215-4ea3-b51f-b8ff32e56426:title=の23日目です。

1日目のhttp://d.hatena.ne.jp/troter/20111201/1322665466:title=ではRebase拡張を紹介しました。今回はRebase拡張を利用しないrebase方法について説明します。

説明に出てくるリポジトリhttps://bitbucket.org/troter/mercurial-advent-calendar-2011-23/に置いてあるシェルスクリプトで作成出来ます。unixcygwinの環境が有る方は次のコマンドで作業環境が整います。(コマンドを列挙しているだけなのでwindows環境でも簡単に再現できると思います。)

$ hg clone https://bitbucket.org/troter/mercurial-advent-calendar-2011-23
$ cd mercurial-advent-calendar-2011-23
$ make

rebase方法

1日目のエントリではrebaseを「指定したリビジョンの親リビジョンを差し替える事」と説明しました。この説明ではrebaseはリビジョングラフを操作する特別なコマンドで実現している様に思えます。

しかし、実際は次の手順でこの説明相当の操作が行えます。Rebase拡張も基本的にこの方法をとっています(1日目のex1-2-keepの例を参照)。

  1. rebaseしたいリビジョンをrebase先に複製する
  2. 複製元のリビジョンを削除する

つまり、リビジョンの複製とリビジョンの削除が行えればrebaseが実現出来るという事です。こう考えると次の組み合わせでrebaseが行えそうです。

  • hg export + hg import + MQ(hg strip)
  • Transplant拡張 + MQ(hg strip)
  • MQ

hg export + hg import + MQ(hg strip)

hg exportとhg importは二つペアのサブコマンドです。次の様に利用します。

$ hg export {REV} > {REV}.patch # リビジョンからパッチファイルを作成
$ hg import {REV}.patch         # hg export で作成したパッチファイルからリビジョンを作成

この二つを利用すればリビジョンを複製出来そうです。

ex1-export-importに移動してLOCALの変更をBASEからOTHERにrebaseしてみましょう。

$ cd ex1-export-import
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{branches}\n'
@  4:7319a5b5306a | ex1-export-import: OTHER2
|
o  3:5d9f7c96b5e5 | ex1-export-import: OTHER1
|
| o  2:c2fc75bf0f11 | ex1-export-import: LOCAL2
| |
| o  1:b86ef104b561 | ex1-export-import: LOCAL1
|/
o  0:3db3aa7255bc | ex1-export-import: BASE
rebaseしたいリビジョンをrebase先に複製する

まずhg exportを使ってパッチファイルを作成します。

$ hg export 1 > 1.patch
$ hg export 2 > 2.patch

次にhg importで作成したパッチファイルを取り込みます。

$ hg parents --template '{rev}:{node|short}\n'
4:7319a5b5306a
$ hg import 1.patch
applying 1.patch
$ hg import 2.patch
applying 2.patch
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{branches}\n'
@  6:5b03c2974c46 | ex1-export-import: LOCAL2
|
o  5:9c20a42b5185 | ex1-export-import: LOCAL1
|
o  4:7319a5b5306a | ex1-export-import: OTHER2
|
o  3:5d9f7c96b5e5 | ex1-export-import: OTHER1
|
| o  2:c2fc75bf0f11 | ex1-export-import: LOCAL2
| |
| o  1:b86ef104b561 | ex1-export-import: LOCAL1
|/
o  0:3db3aa7255bc | ex1-export-import: BASE

LOCALの変更をBASEからOTHERに複製する事が出来ました。

複製元のリビジョンを削除する

hg strip で複製元のリビジョンを削除しましょう。

$ hg strip 1
saved backup bundle to /home/takumi/sandbox/mercurial-advent-calendar-2011-23/ex1-export-import/.hg/strip-backup/b86ef104b561-backup.hg
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{branches}\n'
@  4:5b03c2974c46 | ex1-export-import: LOCAL2
|
o  3:9c20a42b5185 | ex1-export-import: LOCAL1
|
o  2:7319a5b5306a | ex1-export-import: OTHER2
|
o  1:5d9f7c96b5e5 | ex1-export-import: OTHER1
|
o  0:3db3aa7255bc | ex1-export-import: BASE
(ちょいネタ)hg exportで複数のリビジョンを指定する

先ほどの例では hg export を使って1リビジョン1パッチにしていました。hg exportはリビジョンを複数指定出来るので、次の様に複数のリビジョンから一つのパッチファイルを作成出来ます。hg importはそのパッチファイルを取り込めます。

$ hg export 1:2 > 1:2.patch
$ hg import 1:2.patch

これは次と等価です。hg rebase --collapseの様にリビジョンの圧縮は行われないので気をつけてください。

$ hg export 1 > 1.patch
$ hg export 2 > 2.patch
$ hg import 1.patch
$ hg import 2.patch

Transplant拡張 + MQ(hg strip)

hg exportとhg importを同時に行うMercurial拡張がTransplant拡張です。次の様に利用します。

$ hg transplant {REV}                     # 指定したリビジョンを複製
$ hg transplant --branch BRANCHNAME --all # 指定したブランチのリビジョンを全て複製

ex2-transplantに移動してLOCALの変更をBASEからOTHERにrebaseしてみましょう。

$ cd ex2-transplant
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{branches}\n'
@  4:129b04eef7e7 | ex2-transplant: OTHER2
|
o  3:0ce46a95d861 | ex2-transplant: OTHER1
|
| o  2:0b762135ca79 | ex2-transplant: LOCAL2
| |
| o  1:088f61a11e12 | ex2-transplant: LOCAL1
|/
o  0:93e43e3e89ed | ex2-transplant: BASE
rebaseしたいリビジョンをrebase先に複製する

hg transplant でリビジョンを複製しましょう。

$ hg parents --template '{rev}:{node|short}\n'
4:129b04eef7e7
$ hg transplant 1 2
applying 088f61a11e12
088f61a11e12 transplanted to b9000c5dab8d
applying 0b762135ca79
0b762135ca79 transplanted to e5d87133feb0
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{branches}\n'
@  6:e5d87133feb0 | ex2-transplant: LOCAL2
|
o  5:b9000c5dab8d | ex2-transplant: LOCAL1
|
o  4:129b04eef7e7 | ex2-transplant: OTHER2
|
o  3:0ce46a95d861 | ex2-transplant: OTHER1
|
| o  2:0b762135ca79 | ex2-transplant: LOCAL2
| |
| o  1:088f61a11e12 | ex2-transplant: LOCAL1
|/
o  0:93e43e3e89ed | ex2-transplant: BASE

LOCALの変更をBASEからOTHERに複製する事が出来ました。

複製元のリビジョンを削除する

hg strip で複製元のリビジョンを削除しましょう。

$ hg strip 1
saved backup bundle to /home/takumi/sandbox/mercurial-advent-calendar-2011-23/ex2-transplant/.hg/strip-backup/088f61a11e12-backup.hg
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{branches}\n'
@  4:e5d87133feb0 | ex2-transplant: LOCAL2
|
o  3:b9000c5dab8d | ex2-transplant: LOCAL1
|
o  2:129b04eef7e7 | ex2-transplant: OTHER2
|
o  1:0ce46a95d861 | ex2-transplant: OTHER1
|
o  0:93e43e3e89ed | ex2-transplant: BASE
(ちょいネタ)transplantの情報

transplantの履歴はリビジョンのextras領域に含まれています。なので元のリビジョンを参照したりtranspalntしたリビジョンを絞り込んだり出来ます。

$ hg glog -r "transplanted()" \
> --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{transplanted}\n\n'
@  4:e5d87133feb0 | ex2-transplant: LOCAL2
|  0b762135ca799b9288ae75179d7e88b96549eff0
|
o  3:b9000c5dab8d | ex2-transplant: LOCAL1
|  088f61a11e12457517bf9164fdd0ce79df09cafa
|

詳しくはtransplantの実装を参照してください。自分でリビジョングラフを操作する拡張を書いたときに参考になりそうです。

MQ

MercurialQueue(MQ)はパッチを操作する為の拡張です。MQ自体の説明をすると数エントリ分になってしまうので、MercurialQueueのページと10日目の@gab_kmの記事http://blog.livedoor.jp/gab_km/archives/1412534.html:title=を紹介するだけにとどめます。

$ hg qimport -r {REV} # 指定したリビジョンをパッチに変換してキューに追加
$ hg qseries          # パッチキューの状態を表示する
$ hg qpop -a          # 全てのパッチを取り外す
$ hg qpush -a         # パッチキューのパッチを全て適応する
$ hg qfinish -a       # 適応済みのパッチをリビジョンに変換。

ex3-mercurial-queueに移動してLOCALの変更をBASEからOTHERにrebaseしてみましょう。

$ cd ex3-mercurial-queue
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{tags}\n'
@  4:8a30c0f34ea8 | ex3-mercurial-queue: OTHER2
|  tip
o  3:d9c4d3695e45 | ex3-mercurial-queue: OTHER1
|
| o  2:d06c3f6b33b5 | ex3-mercurial-queue: LOCAL2
| |
| o  1:2b434eb1f4a5 | ex3-mercurial-queue: LOCAL1
|/
o  0:b68bc9167cdb | ex3-mercurial-queue: BASE
リビジョンをパッチに変換してパッチを取り外す

hg qimport -r で移動したいリビジョンをパッチに変換します。

$ hg qimport -r 1 -r 2
$ hg qseries
1.diff # ハイライトされている
2.diff # ハイライトされている
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{tags}\n'
@  4:8a30c0f34ea8 | ex3-mercurial-queue: OTHER2
|  tip
o  3:d9c4d3695e45 | ex3-mercurial-queue: OTHER1
|
| o  2:d06c3f6b33b5 | ex3-mercurial-queue: LOCAL2
| |  2.diff qtip
| o  1:2b434eb1f4a5 | ex3-mercurial-queue: LOCAL1
|/   1.diff qbase
o  0:b68bc9167cdb | ex3-mercurial-queue: BASE
   qparent

コミット済みのリビジョンをパッチに変換した場合、そのパッチは適応済みになっています。移動させる為に、パッチを取り外しましょう。

$ hg qpop -a # 全てのパッチを取り外す
popping 2.diff
popping 1.diff
patch queue now empty
$ hg qseries
1.diff # グレーアウトされている
2.diff # グレーアウトされている
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{tags}\n'
@  2:8a30c0f34ea8 | ex3-mercurial-queue: OTHER2
|  tip
o  1:d9c4d3695e45 | ex3-mercurial-queue: OTHER1
|
o  0:b68bc9167cdb | ex3-mercurial-queue: BASE

今までに無い展開ですね。

rebase先でパッチを適応してパッチをリビジョンに変換する

さて、先ほど取り外したパッチをrebase先に適応します。

$ hg parents --template '{rev}:{node|short}\n'
2:8a30c0f34ea8
$ hg qpush
applying 1.diff
applying 2.diff
now at: 2.diff
$ hg qseries
1.diff # ハイライトされている
2.diff # ハイライトされている
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{tags}\n'
@  4:39f50d5bcf5d | ex3-mercurial-queue: LOCAL2
|  2.diff qtip tip
o  3:ee2edc388fc3 | ex3-mercurial-queue: LOCAL1
|  1.diff qbase
o  2:8a30c0f34ea8 | ex3-mercurial-queue: OTHER2
|  qparent
o  1:d9c4d3695e45 | ex3-mercurial-queue: OTHER1
|
o  0:b68bc9167cdb | ex3-mercurial-queue: BASE

さて、目的のリビジョングラフになりました。パッチをリビジョンに変換しましょう。

$ hg qfinish -a
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{tags}\n'
@  4:39f50d5bcf5d | ex3-mercurial-queue: LOCAL2
|  tip
o  3:ee2edc388fc3 | ex3-mercurial-queue: LOCAL1
|
o  2:8a30c0f34ea8 | ex3-mercurial-queue: OTHER2
|
o  1:d9c4d3695e45 | ex3-mercurial-queue: OTHER1
|
o  0:b68bc9167cdb | ex3-mercurial-queue: BASE
(ちょいネタ)MQ入門コースとしてのrebase

MQをいきなり使いこなすのは難しいですが、コミット済みのリビジョンをrebaseするためにMQを使う場合は次の5つのコマンドと順番を覚えれば何とかなります(コンフリクトない場合だけどね)

$ hg qimport -r {REV} # 指定したリビジョンをパッチに変換してキューに追加
$ hg qseries          # パッチキューの状態を表示する
$ hg qpop -a          # 全てのパッチを取り外す
$ hg qpush -a         # パッチキューのパッチを全て適応する
$ hg qfinish -a       # 適応済みのパッチをリビジョンに変換。

この5つのコマンドの動きを把握するのが第一歩です。このコマンドを足掛かりに次のことを覚えていきましょう。

  1. qpush時にコンフリクトした場合の解決方法
  2. qnewで新しいパッチを作成する
  3. qrefreshで適応済みのパッチを更新する
  4. qfoldでパッチを結合する
  5. qqueueを使ってパッチキューを複数持つ
  6. qguardとqselectで条件に応じてパッチを適応する
  7. パッチキューをバージョン管理する

全部できたらMQ中級者です。ちなみに僕は4番目までしかできていないのでMQ初心者です。

stripを元に戻すには?

ところで、ここまで何も考えずにhg stripを使ってきましたが、間違えてstripした場合はどうやって復旧すればよいのでしょう。

ex4-recovery-stripで実験してみましょう。

$ cd ex4-recovery-strip
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{branches}\n'
@  4:6fe3278c576d | ex4-recovery-strip: OTHER2
|
o  3:8b0651fd2f6c | ex4-recovery-strip: OTHER1
|
| o  2:8f58b2b0e8e3 | ex4-recovery-strip: LOCAL2
| |
| o  1:9019a00ed54e | ex4-recovery-strip: LOCAL1
|/
o  0:827d0cc538c5 | ex4-recovery-strip: BASE

stripします。

$ hg strip 1
saved backup bundle to /home/takumi/sandbox/mercurial-advent-calendar-2011-23/ex4-recovery-strip/.hg/strip-backup/9019a00ed54e-backup.hg
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{branches}\n'
@  2:6fe3278c576d | ex4-recovery-strip: OTHER2
|
o  1:8b0651fd2f6c | ex4-recovery-strip: OTHER1
|
o  0:827d0cc538c5 | ex4-recovery-strip: BASE

strip時のメッセージに「saved backup bundle to BACKUP_FILE」とある通り、バックアップファイルが作成されているようです。hg unbundleでstripしたリビジョンを復活させてみましょう。

$ hg unbundle .hg/strip-backup/9019a00ed54e-backup.hg
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 2 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)
$ hg glog --template '{rev}:{node|short} | {author|user}: {desc|strip|firstline}\n{branches}\n'
o  4:8f58b2b0e8e3 | ex4-recovery-strip: LOCAL2
|
o  3:9019a00ed54e | ex4-recovery-strip: LOCAL1
|
| @  2:6fe3278c576d | ex4-recovery-strip: OTHER2
| |
| o  1:8b0651fd2f6c | ex4-recovery-strip: OTHER1
|/
o  0:827d0cc538c5 | ex4-recovery-strip: BASE

stripしたはずのリビジョンが復活しました。これでstripも何度でもやり直せます!

まとめ

三種類のRebase拡張を利用しないrebaseの方法を紹介しました。また、stripの復旧方法についいても簡単に説明しました。

ここで紹介した方法をマスターできれば普段使いのMercuiralの履歴改変の技法については6割ほど理解できたことになります。残り4割は

とかですかねぇ。
また、22日の@sabo2さんが紹介していたhttp://mercurial.selenic.com/wiki/StatesPlan:title=の件も有るのでMercurial2.1ではさらに履歴改変の手段が増える様です。履歴改変マスターへの道はながいですね。