Mercurialは、Merucurial拡張という拡張モジュールを使って、Merucrialの挙動をいろいろ拡張できるようになっています。
デフォルトのままだと使いにくいので、Mercurialを使う上で便利にしてくれる拡張を設定しておきましょう。
デフォルトでバンドルされているMercurial拡張は、Using Mercurial Extensionsにまとめられています。
今回はGit使いがMercurial使いに転職するときに、Gitで実現できたことをMercurialで実現するための、組み込み拡張、および、サードパーティ製の拡張について紹介します。
ブランチの確認、diff、パッチ等々、色づけされていないとつらいです。
というわけでGit同様に色づけしましょう。
Color Extensionはすでにバンドルされているので、.hgrcに次の記述を加えましょう。
[extensions]
color =
この拡張は、ANSIカラーとWindowsコンソールの両方をサポートしており、
一般的な開発環境で使用する場合、自動でターミナルの情報を認識して設定してくれます。
ただ、任意のモード、たとえばWindowsコンソールモードを使用したい場合、
[color]
# 有効なモードは、win32, ansi, auto, off の4種
mode = win32
と記述します。
これでhg status や hg log、hg branchesなどが色づけされます。
これらの色づけの挙動を変更したい場合は、それぞれの設定を上書きできます。
よくつかうコマンドや、オプションを組み合わせたものを頻繁に使うときはaliasを設定しておくと便利です。
Alias Extensionはすでに、Version1.3からMercurial拡張からコアへと組み込まれました。
そのため、Gitで設定するaliasのように設定できます。
筆者は、
[alias]
l = log
lb = log -b
gl = glog
gll = glob --limit
pr = pull --rebase
sh = shelve
ush = unshelve
qi = qimport
qr = qrefresh
qf = qfinish
などを追加しています。
Subversionで提供されているsvn infoは便利です。
Gitではgit config –listで確認します。
そこでMercurialでもhg infoを実現するInfo Extensionが作られています。
これはデフォルトでバンドルされていないので、モジュール(info.py)をダウンロードして設定します。
[extensions]
info = /path/to/info.py
拡張モジュールは任意の場所に保存しておきましょう。
.hgrcでそのモジュールのパスを渡すと使用できるようになります。
$ hg info
Repository: /home/yoppi/work/mercurial/sandbox/info [hg root]
Base Hash: ae91f328abaefb69886b34dcb7cb9fe4843c8301 [hg id -r0]
Revisions: 1 [hg tip --template "{rev}"]
Files: 1 [hg manifest | wc -l]
Cloned From: None [hg paths default]
はるか昔から議論されているにも関わらず、改行コードにまつわるトラブルは現在も顕在しています。
Gitであれば、設定ファイルにcore.safecrlfやcore.autocrlfなどの設定で改行コードのトラブルを回避できます。
MercurialではEOL Extensionを使ってそんなわずらわしさから解放されましょう。
~/.hgrcに次の設定を加えます。
[extensions]
eol =
プロジェクト内で、改行コードを統一するためにプロジェクトルートに次の.hgeolファイルを設置しておきましょう。
たとえば、Javaで開発しているときに基本的に改行コードをLFで統一したい場合、
[patterns]
**.java = LF
**.jsp = LF
**.tag = LF
**.xml = LF
...
としておくと、たとえば、ローカルでCRLFのファイルをリモートリポジトリにpushするときにLFに変換してくれます。
MercurialのGUIクライアントを使うとログをきれいにグラフで見ることができますが、Graph log extensionを使うとgit log –graph相当のものを実現できます。
[extensions]
graphlog =
と~/.hgrcに記述します。
コマンドは、
$ hg glog [OPTIONS]
でログをグラフ状に確認できます。
また、Graph Log Extensionを有効にすると、hg log コマンドに -G オプションが追加され、同様にコミットの歴史をグラフ表示できます。
Mercurialは、Gitと異なりMultiple Headの概念があることから、常にグラフ形式でコミットログを確認することをお勧めします。
Git使いがMercurial使いへ転職するときに、git pullの挙動とhg pullの挙動の違いにみなさん驚かれることでしょう。
git pullすると、リモートブランチから取得してワーキングブランチへ取り込みマージまでしてくれるのに対して、hg pullはリモートブランチのチェンジセットをローカルに取り込むだけしかしてくれません。
そこで、Fetch Extensionを使いましょう。
~/.hgrcに次の設定を追加します。
[extensions]
fetch =
これでfetchできるようになります。
hg fetch
hg pull、hg mergeを一度にこなしてくれます。
ただし、Fast Forwardですむ場合でも不要なマージが発生し、歴史を振り返った時に追いかけにくくなってしまうので、マージしたくない場合は次に紹介するrebaseを使いましょう。
現在あるブランチで開発していて、いくつかコミットを築いたとします。
分散VSCでは、ローカルで開発し、コミットの歴史はローカルで積み重ねられるので、リモートブランチにpushするタイミングで歴史を取り込み、マージしてからpushすることになります。
Mercurialではhg pull、hg merge、hg updateという流れを踏みます。
しかし、pullするときに本来ならFast ForwardですむのにMultiple Head(無名のブランチ)を作られることにより無駄なマージが発生します。
Gitでは、お互いが編集していない場合では、マージが発生せずFast Forwardで歴史を取り込みます。
Gitはこの仕組みをrebaseと呼んでいます。
この機能をMercurialでも実現したのがRebase Extensionです。
有効にするには、.hgrcに次の設定を加えましょう。
[extensions]
rebase =
では、実際にリモートリポジトリから歴史をローカルに適用してみましょう。
リモートリポジトリにmaintブランチ、featureブランチ、およびdefaultブランチが存在するとします。
開発者Aliceはfeatureブランチで新機能開発を開発者Bobと共同で進めています。
ここでAliceは機能Aを開発しているとしましょう。同様にBobも機能Aを開発しています。
[bob]$ h gl --style compact
@ 4[tip]:2 1724a9784c79 2011-03-14 15:36 +0900 bob
| added foo4
|
| o 3:0 f6b470b38c33 2011-03-14 15:33 +0900 alice
| | bug fixed hoge
| |
o | 2 296b91be79de 2011-03-14 15:32 +0900 alice
| | added foo2
| |
o | 1 952cffa88cb6 2011-03-14 15:28 +0900 alice
|/ developping foo
|
o 0 15c785b69dc6 2011-03-14 15:12 +0900 alice
added hoge
[alice]$ h gl --style compact
@ 4[tip]:2 4bfa526da3c5 2011-03-14 15:36 +0900 alice
| added foo3
|
| o 3:0 f6b470b38c33 2011-03-14 15:33 +0900 alice
| | bug fixed hoge
| |
o | 2 296b91be79de 2011-03-14 15:32 +0900 alice
| | added foo2
| |
o | 1 952cffa88cb6 2011-03-14 15:28 +0900 alice
|/ developping foo
|
o 0 15c785b69dc6 2011-03-14 15:12 +0900 alice
added hoge
AliceとBobは機能fooに対しそれぞれfoo3とfoo4を追加しています。
これらは別のファイルであり、かつ同一機能なので歴史はFast Fowardでになってほしいわけです。
Bobが先にpushしてAliceがfoo3をpullして取り込むとしましょう。
するとAliceのローカルブランチでは次のような歴史になるはずです。
[alice]$hg glog --style compact
o 5[tip]:2 1724a9784c79 2011-03-14 15:36 +0900 bob
| added foo4
|
| @ 4:2 4bfa526da3c5 2011-03-14 15:36 +0900 alice
|/ added foo3
|
| o 3:0 f6b470b38c33 2011-03-14 15:33 +0900 alice
| | bug fixed hoge
| |
o | 2 296b91be79de 2011-03-14 15:32 +0900 alice
| | added foo2
| |
o | 1 952cffa88cb6 2011-03-14 15:28 +0900 alice
|/ developping foo
|
o 0 15c785b69dc6 2011-03-14 15:12 +0900 alice
added hoge
Aliceがpullした直後になります。
featureブランチにrevsion5とrevision4がheadとして作成されてしまいます。
いわゆるこれが、MercurialにおけるMultiple Headです。
さてこのままMercurialのお作法に従うと、hg mergeして4をマージします。
すると、featureブランチで同じfoo機能を開発しているにもかかわらず無駄なマージが発生してしまうのです!
これは、不本意なマージです。
[alice]$ hg glog --style compact
@ 6[tip]:4,5 93cc2c6a4b9f 2011-03-14 15:55 +0900 alice
|\ merged
| |
| o 5:2 1724a9784c79 2011-03-14 15:36 +0900 bob
| | added foo4
| |
o | 4:2 4bfa526da3c5 2011-03-14 15:36 +0900 alice
|/ added foo3
|
| o 3:0 f6b470b38c33 2011-03-14 15:33 +0900 alice
| | bug fixed hoge
| |
o | 2 296b91be79de 2011-03-14 15:32 +0900 alice
| | added foo2
| |
o | 1 952cffa88cb6 2011-03-14 15:28 +0900 alice
|/ developping foo
|
o 0 15c785b69dc6 2011-03-14 15:12 +0900 alice
added hoge
不本意なマージをhg rebaseを使って回避しましょう。
これには2つの方法があります。
hg pullした時点でheadが2つできたところでrebaseする方法と、pullするときに–rebaseオプションを付ける方法です。
RebaseExtensionを有効化すると、hg pullに–rebaseオプションが追加されています。
では、rebaseコマンドを実行する方法を見てみましょう。
$ hg rebase
このコマンドでrebaseされ、Bobのコミット(rev4)のあとに自分のコミット(rev5)を重ねることができます。
[alice]$ h gl --style compact
@ 5[tip] fc163b1b87ca 2011-03-14 15:36 +0900 alice
| added foo3
|
o 4:2 1724a9784c79 2011-03-14 15:36 +0900 bob
| added foo4
|
| o 3:0 f6b470b38c33 2011-03-14 15:33 +0900 alice
| | bug fixed hoge
| |
o | 2 296b91be79de 2011-03-14 15:32 +0900 alice
| | added foo2
| |
o | 1 952cffa88cb6 2011-03-14 15:28 +0900 alice
|/ developping foo
|
o 0 15c785b69dc6 2011-03-14 15:12 +0900 alice
added hoge
またpullするときにhg pull –rebaseすると同様の働きをします。
このようにあとから、歴史を振り返ったときに無駄なマージが発生させないように開発することで、あとからデバッグしやすくなるのでRebaseも使いこなせるようになりましょう。
たとえば、コミットメッセージを書き換えたり、コミットを並び替えたりという歴史の整形は、Gitを使っての開発において日常茶飯事です。
Gitでは git rebase を使うと容易に歴史を変更できます。
Mercurialではどうでしょうか。Git使いには残念なお知らせがあります。
Mercurialでコミットしてしまったら、その歴史を書き換えるには rollback するしか道は残されていません。
1つ前であれば、rollbackするだけでよいですが、2つ前にコミットしたものを変更したい場合だと破綻します。
そこで、Mercurial Queue Extensionを使いましょう。
通称MQ拡張です。
これは、Mercurialにおけるパッチ管理システムなのですが、コミットした歴史を書き換えることもできます。
MQを使いこなせばMercurialでの開発が格段に便利になります。
Mercurialにバンドルされているので、
[extensions]
mq =
と.hgrcに設定しておきましょう。
ローカルリポジトリで、あるコミットログがおかしいことに気づきます。さてこのコミットログを書き直したい場合、どうすればいいでしょうか?
その変更したいコミットをパッチ化することで解決します。
$ hg glog --style compact
@ 2[tip] 152f67e69b5b 2011-03-14 20:45 +0900 yoppi
| added hoge2
|
o 1 a74c33e376cc 2011-03-14 20:33 +0900 yoppi
| added hoge
|
o 0 24fa78ec5dba 2011-03-14 19:36 +0900 yoppi
initial commit
rev1のコミットは追加ではなくてhoge.txtを変更したものでした。
コミットログを変更しなければ、混乱を招きます。
では、rev1からtip(rev2)までをパッチ化しましょう。
$ hg qimport -r 1:tip
$ hg qseries
1.diff
2.diff
$ hg qtop
2.diff
$ hg glog --style compact
@ 2[2.diff,qtip,tip] 152f67e69b5b 2011-03-14 20:45 +0900 yoppi
| added hoge2
|
o 1[1.diff,qbase] a74c33e376cc 2011-03-14 20:33 +0900 yoppi
| added hoge
|
o 0[qparent] 24fa78ec5dba 2011-03-14 19:36 +0900 yoppi
initial commit
さてこれで、rev1とrev2をパッチ化できました。
hg glogの出力がパッチを意味するものになっていることがわかります。
hg qseriesコマンドは、現在適用されているパッチの一覧で、hg qtopは現在のパッチの先頭を表示します。
変更したいのはrev1です。
rev2のパッチが現在いる場所になるので、rev1に移ります。
$ hg qgoto 1.diff
popping 2.diff
now at: 1.diff
$ hg qtop
1.diff
$ hg glog --style compact
@ 1[1.diff,qbase,qtip,tip] a74c33e376cc 2011-03-14 20:33 +0900 yoppi
| added hoge
|
o 0[qparent] 24fa78ec5dba 2011-03-14 19:36 +0900 yoppi
initial commit
qgotoコマンドで編集したいパッチに移ります。
ここで驚かないでほしいのですが、rev2がログから消えてしまいました。
どこにいったのでしょうか?
そうです。qimportしたのでパッチとして残されているのです。
hg qseriesしてみましょう。
$ hg qseries
1.diff
2.diff
ColorExtensionを設定していれば、2.diffがグレーで表示されていることがわかります。
つまり、パッチとしては保存されているけれども、現在のワーキングディレクトリで有効化されていないパッチを意味します。
rev1を修正してから適用すれば問題ないので、しばしこのまま進めましょう。
パッチを適用しなおすコマンドはhg qrefreshコマンドで、コミットログを編集するには-eオプションを付けます。
$ hg qrefresh -e
エディタが起動しコミットログを編集できるようになります。
コミットログを編集したあとは保存して、エディタを終了しましょう。
$ hg glog --style compact
@ 1[1.diff,qbase,qtip,tip] d8f0251ccf0d 2011-03-14 20:33 +0900 yoppi
| modified hoge
|
o 0[qparent] 24fa78ec5dba 2011-03-14 19:36 +0900 yoppi
initial commit
さてこのあと、このパッチを有効化するため、qfinishしましょう。
$ hg qfinish -a
$ hg glog -style compact
@ 1[tip] d8f0251ccf0d 2011-03-14 20:33 +0900 yoppi
| modified hoge
|
o 0 24fa78ec5dba 2011-03-14 19:36 +0900 yoppi
initial commit
hg glogで確認するとrev1がパッチではなく本来のコミットとして扱われていることがわかるでしょう。
では、横にのけておいたrev2のパッチを再度適用しましょう。
$ hg qapplied
$ hg qpush
$ hg qfinish -a
hg qappliedは現在適用されているパッチのリストを表示するコマンドです。
rev2のパッチは適用されていないので表示されないですね。
qpushして有効化して、qfinishしましょう。rev2は本来何もいじる必要はないので、qrefreshは必要ありません。
これで元通りになります。
ところで、コミットログを変更する方法は他にも方法があります。
直接パッチを編集しても書き換えることが可能です。
.hg/patchesいかにパッチが保存されています。
hg qimport した時点で.hg/patches以下には、1.diffと2.diffのファイルが存在します。
$ hg qimport -r 1:tip
$ ls .hg/patches/
1.diff 2.diff series status
そこで1.diffファイルがあるのでエディタで開きましょう。
$ vim .hg/patches/1.diff
# HG changeset patch
# User yoppi
# Date 1300102423 -32400
# Node ID d8f0251ccf0dc2e3e7009b73b91284f2ae56c90c
# Parent 24fa78ec5dbae85de0687bb7d3eaef62d97ab908
modified hoge
diff --git a/hoge.txt b/hoge.txt
--- a/hoge.txt
+++ b/hoge.txt
@@ -0,0 +1,1 @@
+hoge
コミットログやその他の情報も修正できます。
コミットログを修正したら保存して終了しましょう。
まだこの段階では修正したパッチは適用されていないので、qgotoして移動し、qrefreshしてパッチを再適用しましょう。
$ hg qgoto 1.diff
$ hg qrefresh
あとは、どうようにrev2のパッチを有効化しqfinishします。
$ hg qpush
$ hg qfinish -a
今回は、コミットログの修正にMQを使用しました。
MQはいろいろな場面で便利に使える(transplantの例で使用します)ので、マニュアルを読んでいろんな場面で使うようにしてみましょう。
人間である以上ミスはつきものです。メンテナンスブランチに対してバグ修正しなければならないのに、featureブランチに対して修正するというミスを犯したとしましょう。
Gitであれば、git cherry-pick を使ってその修正のみをメンテナンスブランチに持ってこれます。
MercurialではTransplant Extensionを使います。
[extensions]
transplant =
と同様に記述しておきましょう。
コマンドの定義は、次の通りです。
$ hg transplant [-s REPOSITORY] [-b BRANCH [-a]] [-p REV] [-m REV] [REV]
ブランチmaintに対して、バグ修正しなければならないのに対して、間違ってfeatureブランチで修正してしまった状況を例に挙げましょう。
$ hg glog --style=compact
@ 4[tip]:2 e50e22d3998d 2011-03-10 18:30 +0900 yoppi
| fixed foo bug2
|
| o 3:1 d9ef5a4b5b84 2011-03-10 18:29 +0900 yoppi
| | fixed foo bug
| |
o | 2 493a5feabaf2 2011-03-10 18:28 +0900 yoppi
|/ added feature in hoge
|
o 1 2832749994fb 2011-03-10 18:26 +0900 yoppi
| added foo
|
o 0 ed07682b0722 2011-03-10 18:23 +0900 yoppi
initial commit
ここでコミットしてしまったrev4は本来ならばmaintブランチでコミットしなければなりませんでした。
したがってこのコミットをrev3の後に移植しましょう。
まずは、MQを使ってrev4のコミットをパッチ化しましよう。
$ hg qimport -r 4
これでrev4のコミットがパッチ化されます。
$ hg qseries
4.diff
ではmaintブランチに移って、パッチ化したrev4をtransplantで取り込みましょう。
$ hg update 3
$ hg transplant -b feature 4
$ hg glob --style compact
@ 5[tip]:3 ab208c564fc4 2011-03-10 18:30 +0900 yoppi
| fixed foo bug2
|
| o 4[4.diff,qbase,qtip]:2 e50e22d3998d 2011-03-10 18:30 +0900 yoppi
| | fixed foo bug2
| |
o | 3:1 d9ef5a4b5b84 2011-03-10 18:29 +0900 yoppi
| | fixed foo bug
| |
| o 2[qparent] 493a5feabaf2 2011-03-10 18:28 +0900 yoppi
|/ added feature in hoge
|
o 1 2832749994fb 2011-03-10 18:26 +0900 yoppi
| added foo
|
o 0 ed07682b0722 2011-03-10 18:23 +0900 yoppi
initial commit
このコマンドは、「featureブランチのリビジョン4を現在のブランチに移植する」ことを意味します。
さて、これでmaintブランチで修正できました。
ここで、glogの出力をよく見てみましょう。featureブランチで間違ったコミットのパッチが残っています。
このコミットはすでにmaintブランチに取り込んだので、もはや不要です。削除してしまいましょう。
$ hg qpop -- 現在有効化されているパッチを取り出す
$ hg qdelete -- パッチを削除する
これで再度hg glogで確認してみましょう。
$ hg glog
@ 4[tip] ab208c564fc4 2011-03-10 18:30 +0900 yoppi
| fixed foo bug2
|
o 3:1 d9ef5a4b5b84 2011-03-10 18:29 +0900 yoppi
| fixed foo bug
|
| o 2 493a5feabaf2 2011-03-10 18:28 +0900 yoppi
|/ added feature in hoge
|
o 1 2832749994fb 2011-03-10 18:26 +0900 yoppi
| added foo
|
o 0 ed07682b0722 2011-03-10 18:23 +0900 yoppi
initial commit
featureブランチから間違ったコミットが削除されていることがわかると思います。
transplantはこのように歴史の整形にも適用できるので、ぜひマスターしましょう。
一度にファイルを修正して、1ファイルに論理的でない変更を加えてしまったときに、Gitだとgit add -pでコミットする部分を選択できます。
MercurialでもRecord Extensionを使うと、部分的にコミットできます。
~/.hgrcに
[extensions]
record =
を追加しましょう。
これで、細かい単位(hunkと呼びます)でコミットできるようになります。
では、実際にrecordを試してみましょう。
$ hg diff
diff --git a/hoge.rb b/hoge.rb
--- a/hoge.rb
+++ b/hoge.rb
@@ -1,3 +1,12 @@
+class Hoge2
+ def initialize
+ end
+
+ def hoge2
+ "hoge2"
+ end
+end
+
class Hoge
def initialize
end
@@ -7,6 +16,15 @@
end
end
+class Hoge3
+ def initialize
+ end
+
+ def hoge3
+ "hoge3"
+ end
+end
+
if __FILE__ == $0
Hoge.new.hoge
end
hoge.rbにまちがって一度にクラスを追加してしまいました。
ここで一度にコミットするのではなくて1クラスずつ追加しないと論理的な歴史を積み重ねられません。
hg recordを使うと、hunk毎にコミットできます。
$ hg record
diff --git a/hoge.rb b/hoge.rb
2 hunks, 18 lines changed
examine changes to 'hoge.rb'? [Ynsfdaq?]
hoge.rbには2個のhunkがあってそれらをコミットするかどうかを尋ねられます。
これらのオプションの詳細はhg help recordを参照してください(その場で?で確認できます)。
ここではhoge.rbのhunkの1つ目をコミットしています。
@@ -1,3 +1,12 @@
+class Hoge2
+ def initialize
+ end
+
+ def hoge2
+ "hoge2"
+ end
+end
+
class Hoge
def initialize
end
record change 1/2 to 'hoge.rb'? [Ynsfdaq?]y
@@ -7,6 +16,15 @@
end
end
+class Hoge3
+ def initialize
+ end
+
+ def hoge3
+ "hoge3"
+ end
+end
+
if __FILE__ == $0
Hoge.new.hoge
end
record change 2/2 to 'hoge.rb'? [Ynsfdaq?] n
エディタが起動し、コミットログを記述できます。
これで一つめのhunkのみをコミットしたことになります。
ただGitのようにhunkをさらに細かく分割できません。Feature Requestとして提案されているので、しばし待ちましょう。もちろん自分でパッチ書いて投げることが望ましいです:)
ワーキングブランチで一時的に変更したあと、他のブランチに移動してすぐに修正したい場合があります。
しかし、変更が加えられているため他のブランチに移動することはできません。
こういうときには、Gitではgit stashを使って現在のワーキングコピーでの変更を横に退けておけます。
Mercurialでもできないでしょうか?
Shelve Extensionがそれを実現してくれます。
この拡張も、デフォルトではバンドルされていないので、モジュールを取得して設定しましょう。
$ hg clone https://bitbucket.org/tksoh/hgshelve
取得したあと、.hgrcに同様に設定します。hg cloneしたディレクトリにhgshelve.pyが拡張モジュールになります。
[extensions]
hgshelve = /path/to/hgshelve.py
さて、これでMercurialでもstashできるようになりました。
ローカルのチェンジセットでの変更点を回避しておくコマンドは
$ hg shelve
となります。
逆に、横にのけておいたものを元に戻すには
$ hg unshelve
となります。
では、いくつかワーキングディレクトリのファイルに変更を加えてみましょう。
$ hg status
M hoge.rb
$ hg shelve
diff --git a/hoge.rb b/hoge.rb
1 hunks, 9 lines changed
shelve changes to 'hoge.rb'? [Ynsfdaq?] y
これで編集した箇所を横にのけておけます。
-nオプションをつけることで、shelveに名前を付けることもできます。
このあと、他のブランチで作業し、再度戻ってきて横に退けておいたshelveを元に戻しましょう。
$ hg unshelve
これもRecord Extensionと同様に、現在hunkを細かく分割できません。