#106 Mercurialでちょっぴりの歴史改変

この記事は、Mercurial Advent Calendar 2011 の10日目のエントリです。

以前、 @troter さんが興味深いaliasを書いていました。

hg fix "fixed message" # (hg 1.8.x only) - Gist

どうやらコミットメッセージを書き換えるコマンドのようで、「おぉ!Mercurialでもコミットメッセージを改編できるんだ!」と興奮しました。
およそ4時間後、試行錯誤の末に、このaliasのWindows対応版ができあがりました。

hg fix "fixed message" - Gist

このaliasを書く中で、MQとShelveというMercurial拡張を使っています。

Mercurialと歴史改変

私の中では、Gitは歴史改変を(正しく使った上で)ガンガン行うVCSだと思っている一方、Mercurialはそんなに歴史改変をしない印象があります。
(個人の感想です)

しかし、Mercurialにも手段がないわけではありません。
例えば本Advent Calendarの初日に @troter さんが「ケーススタディで学ぶMercurialのRebase拡張(Mercurial Advent Calendar 2011 1日目)」として説明してくれた Rebase 拡張がありますね。

そしてもう一つ、Mercurial Queues 拡張(MQ)があります。

MqExtension - Mercurial

MQとは

MQはMercurialのコミットオブジェクト(changesets)に対してパッチを当てるための拡張機能です。
Mercurialに標準搭載されている拡張ですが、普段は有効化されていません。
hgrcに、次の記述を行うことで有効にできます。
[extensions]
mq =

MQの使い方

上のGistで使ったコマンドを説明していきます。

qtop

現在作業中のパッチ名を表示します。
次に説明するqimportなどを実行してパッチ作業中になっていると、
c:\source\mq>hg qtop
2.diff
などと表示されます。
特にパッチ作業中でない場合は
c:\souce\mq>hg qtop
no patches applied
と表示されるでしょう。

qimport

新しいパッチをパッチ群("the series"と言ってるからパッチ列かな)に追加します。
例えば最後のchangesetに対してパッチ作業を始めるときは
c:\source\mp>hg qimport -r .
とします。
なお、複数のリビジョンを引っ張ってくることもできます。
c:\source\mq>hg qimport -r 1:.
こうすると、リビジョン番号が1から最後までのリビジョンを、パッチとしてキューに入れることができます。

qrefresh

現在作業中のパッチを更新します。
c:\source\mq>hg qrefresh -m "えむ☆きゅー"
とすれば、コミットメッセージを渡した文字列で書き換えることができます。
また、hgrcで指定済みのエディタで修正したい場合は、
c:\source\mq>hg qrefresh -e
Waiting for Emacs...
となって、エディタからコミットメッセージを書き換えられます。(ここではEmacsを使用)

qfinish

パッチの作業を完了し、リポジトリの履歴に反映します。
普段使いなら、すべてのパッチをリポジトリに反映させるために -a オプションを指定するでしょう。
c:\source\mq>hg qfinish -a

(おまけ)qpop と qpush

先ほどqimportで複数リビジョンをまとめてMQにプッシュする例を紹介しました。
複数のパッチがキューイングされていますが、一度に扱えるのは1つです。
1つ以上前のパッチに対して作業を行いたいときは、qpopを使います。
c:\source\mq>hg qpop
popping 2.diff
now at: 1.diff
この例では、パッチを1つだけ取り出して、1つ前のパッチに対して作業を行えるようにします。
作業が終わったら、qpushで再びパッチをキューに戻します。
c:\source\mq>hg qpush
applying 2.diff
now at: 2.diff

Shelve拡張とは

さて、先ほどのaliasでは、shelve(とunshelve)というコマンドも使っていました。
これはShelve拡張という、Gitで言うところのstashコマンドに当たるもので、未ステージング状態の変更を退避させます。

ShelveExtension - Mercurial

なお、shelveは標準拡張機能ではないので、ソース類を落としてくる必要があります。
(上記リンクを参照)

一式をダウンロードしたら、お使いのhgextフォルダか、どこか適当なパスに格納します。
拡張を有効化するには次のようにします。
[extensions]
# hgextフォルダに格納した場合
hgext.hgshelve =
# 任意のフォルダに格納した場合(ここでは取得したhgshelve.pyを ~/.mercurial/extensions/hgshelve に格納)
hgshelve = $HOME/.mercurial/extensions/hgshelve/hgshelve.py

Shelveの使い方

shelve

まずは未ステージングのものがリポジトリに残っているか確認します。
c:\source\mq>hg status
M hoge.txt
この状態でhg shelveを発行します。
c:\source\mq>hg shelve
diff --git a/hoge.txt b/hoge.txt
1 hunks, 1 lines changed
shelve changes to 'hoge.txt'? [Ynsfdaq?]
特に何のオプションも指定しない場合、このように対話的に処理を進めます。
shelve changes to 'hoge.txt'? [Ynsfdaq?]  y
@@ -1,3 +1,4 @@
 hoge
 fuga
 bar
+blur
shelve this change to 'hoge.txt'? [Ynsfdaq?]  y
再びhg statusしてみましょう。
c:\source\mq>hg status
何も表示されなければ成功しています。

--allオプションを指定すると、対話的な入力なしに、全ての未ステージングな変更を退避させます。

unshelve

退避した変更を再びリポジトリに戻します。
c:\source\mq>hg unshelve
unshelve completed
楽チンです。

もうちょっと便利なhg fix

さて、最初に取り上げたfixというaliasですが、このコマンドには変更したいコミットメッセージを渡す必要があります。
うっかりメッセージを渡し忘れると、コミットメッセージを空っぽにしてしまうことになりますし、間違った文字列を渡しても、コミットメッセージを壊してしまいます。
それが危なっかしかったので、ちょっとだけ改良しました。

hg fix ["fixed message"] (for Windows) - Gist

何を変更したかというと、引数に文字列が渡されなかったとき、hg qrefresh -m "$1"の代わりにhg qrefresh -eを使って、エディタからコミットメッセージを修正できるようにしました。

MQはchangesetを破壊的に修正する

さて、MQの歴史改変とShelveの退避を紹介しましたが、MQについて気をつけるべきことがあります。
c:\source\mq>hg glog
@  changeset:   2:43285bb12d66
|  tag:         tip
|  user:        Gab_km<censored>
|  date:        Sat Dec 10 11:38:57 2011 +0900
|  summary:     third commit
|
o  changeset:   1:13bed65b5f81
|  user:        Gab_km<censored>
|  date:        Tue Dec 06 01:10:39 2011 +0900
|  summary:     second commit
|
o  changeset:   0:197754f7f12f
   user:        Gab_km<censored>
   date:        Tue Dec 06 01:08:29 2011 +0900
   summary:     initial commit
試しにhg fixしてみましょう。
c:\source\mq>hg fix "3rd commit"

c:\source\mq>hg glog
@  changeset:   2:59ce8ce12f28
|  tag:         tip
|  user:        Gab_km<censored>
|  date:        Sat Dec 10 11:38:57 2011 +0900
|  summary:     3rd commit
|
o  changeset:   1:13bed65b5f81
|  user:        Gab_km<censored>
|  date:        Tue Dec 06 01:10:39 2011 +0900
|  summary:     second commit
|
o  changeset:   0:197754f7f12f
   user:        Gab_km<censored>
   date:        Tue Dec 06 01:08:29 2011 +0900
   summary:     initial commit
リビジョン2のsummaryが書き換わっていますが、もう一箇所だけ変更されているものがあります。

それは、リビジョン2のハッシュ値です。

つまり、MQは対象のchangesetのコメントを修正したのではなく、そのchangesetから作ったパッチで元のchangesetを置き換えたのだということです。

また、hg fixでShelve拡張を使っているのは、未ステージング変更を退避せずにMQでqrefreshしてしまうと、パッチがその変更も取り込んでしまうからです。
今回はコミットメッセージだけ変更したいので、改変前に変更を退避しています。

分かってやる分には問題はなさそうですが、知らずにMQの便利さだけを享受しようとすると、後で歴史改変の怖さを知ることになるかもしれません。

まとめ

MQとShelve拡張の紹介をしました。
Mercurial拡張を上手く使うことで、柔軟にリポジトリを操作できるようになります。
ただし、力には責任がつきものだということを心に留めておきましょう。

明日12/11(日)は @choplin さんです。よろしくお願いします。

トラックバックURL

#106 Mercurialでちょっぴりの歴史改変 へのトラックバック

まだトラックバックはありません。

#106 Mercurialでちょっぴりの歴史改変 へのコメント一覧

まだコメントはありません。

コメントする

#106 Mercurialでちょっぴりの歴史改変 にコメントする
絵文字
プロフィール
あわせて読みたい
あわせて読みたい
記事検索
Project Euler
なかのひと
アクセス解析
Coderwall
  • ライブドアブログ

トップに戻る