Mercurialをソースコードからインストールする

基本的にterminal上で使うアプリケーション(エディタ、言語処理系、細かなツール等)は、ソースコードからビルドして使うようにしている。
複数の版を使えるようにしたいのもあるし、なによりビルドすることが好きだからだ。
Mercurialをビルドする前に、docutilsを使えるようにしておこう。Mercurialのドキュメントはdoctuilsを使って生成している。
pythonモジュールなのでpipを使うのが簡単だ。
ソースからインストールする場合Docutils: Documentation Utilitiesから最新のものを取得しよう。

$ tar zxf docutils-0.7.tar.gz
$ cd docutils/
$ ./setup.py install

pipならコマンドを叩くだけでいい。

$ pip install docutils

これでMercurialをインストールする準備が整った。Mercurialのソースコードを取得しよう。

$ wget http://mercurial.selenic.com/release/mercurial-1.9.tar.gz
$ tar zxf mercurial-1.9.tar.gz
$ cd mercurial-1.9

デフォルトでは/usr/local以下にインストールされるので、任意の場所にインストールする。 Makefileのコメントにあるように、makeを実行するときにPREFIXを指定する。

$ vim Makefile
# If you want to change PREFIX, do not just edit it below. The changed
# value wont get passed on to recursive make calls. You should instead
# override the variable on the command like:
#
# % make PREFIX=/opt/ install

PREFIX=/usr/local
export PREFIX
$ make PREFIX=/path/to/install/dir install

これでMercurialをインストールできた。

Mercurialで築いたコミットを並び替える

DVCS(GitやMercurialなど)の利点は、コードを書き散らかしたあとにとりあえずコミットして、あとから論理的な歴史になるようにコミットを並び変えたり、コミットをまとめたりできることだ。
Mercurialでは、これらの歴史の改変はMQ拡張を使って実現できる。
たとえば、次のような歴史を作っていたとしよう。

$ hg glog --style=compact
@  4[tip]   722b0c3ef8f7   2011-06-07 22:34 +0900   yoppi
|    D
|
o  3   e912ff6fd1f1   2011-06-07 22:33 +0900   yoppi
|    B
|
o  2   eaca60210a86   2011-06-07 22:33 +0900   yoppi
|    C
|
o  1   f669da88832f   2011-06-07 22:33 +0900   yoppi
|    A
|
o  0   b3a79f426bdc   2011-06-07 22:32 +0900   yoppi
     initial commit

コミットがA->C->Bとなっており論理的な並びになっていない。
そこでこのコミットを論理的な歴史に修正しよう。
コミットをまずMQでパッチにする。

$ hg qimport -r tip:1
$ hg qseries
1.diff
2.diff
3.diff
4.diff

さて、パッチ化したあとは適用済みになっているので、これらのパッチをキューから解除する。

$ hg qpop --all
popping 4.diff
popping 3.diff
popping 2.diff
popping 1.diff
patch queue now empty

キューから削除することで、各パッチを入れ替えることができる。
言い換えると、キューにenqueueする順序を変えることがコミットの順序を入れ替えることになる。
では、キューにパッチをpushする順番を変更するにはどうすればいいのか?
パッチ化したものは.hg/patches以下に保存されている。

$ ls .hg/patches
1.diff  2.diff  3.diff  4.diff  series  status

パッチの順序はこの'series'ファイルで管理されている。
したがって、ファイルの中身をエディタで書き換えることでコミットの順序を並び替えられる。
コミットの順序を正しくする(A->B->C)にはリビジョン2と3を入れ替える。

$ cat .hg/patches/series
1.diff
2.diff
3.diff
4.diff
$ vim .hg/patches/series
$ cat .hg/patches/series
1.diff
3.diff
2.diff
4.diff

このあとに、パッチをpush(適用)して、パッチをもとのコミットに戻す。

$ hg qpush --all
$ hg qfinish --all
$ hg glog --style=compact

普通はhistedit拡張を使う

MQ拡張で操作すると少し手順が多くなる(慣れると気にならなくなるが)ので、histedit拡張を使うともっと簡単にコミットを並び替えられる。
これはgit rebase -i のように対話的にコミットの歴史を変更できる。
histeditはMercurialにbundleされていないので、bitbucketからcloneして~/.hgrcに設定を書いておこう。 ~/.hgrcに

[extensions]
histedit = /path/to/cloned/histedit/hg_histedit.py

とライブラリのパスを記述しておく。
histedit拡張の使い方はいたって簡単で、コミットの歴史を変更したいチェンジセットのrevsetsを指定するだけでよい。
先ほど、MQ拡張でコミットを並び替えたものを、HistEdit拡張で並び替えてみよう。

$ hg histedit -r tip:1

するとtipからリビジョン1のコミットの情報が1行ごとに書き込まれた状態でエディタが起動する。

pick 52119b06ce60 A
pick d1f359253a3a C
pick 6170a8d1a63b B
pick faf26eeb0bb2 D

# Edit history between 52119b06ce60 and faf26eeb0bb2
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  f, fold = use commit, but fold into previous commit
#  d, drop = remove commit from history
#

Gitとほぼ同じであり、コミットの順序を並び替えたいのであれば、並び替えたいコミット行を入れ替える。
今回はコミット'C'とコミット'B'を入れ替えるので、それぞれの行を入れ替える。
また、左端のカラムはhisteditの操作を意味しており、コミットを並び替える以外の操作も可能だ。

  • e edit: コミットメッセージを変更する
  • f fold: 一つ前の親コミットに含める(エディタ上だと上のものが親にあたる)
  • d delete: そのコミットを破棄する

編集し終わったら保存してエディタを終了してコミットを並び替えが完了する。
histeditが便利なので、普段はこっちを使うけどMQを使ってやってることをラップしているので、より複雑な操作をしたい場合はMQ使うことになるので、MQでの操作も知っておいたほうがいい。

fastthreadがthread.rbよりどれだけ高速なのか

最近MacBookにPassengerの環境を作ろうとして、gem経由でインストールするとfastthreadというgemがインストールされたので、気になったのでなんだろうとコードを読んでみた。
どうやら標準添付ライブラリ(thread.rb)で提供されているQueueやSizedQueueをそのまま、Ruby拡張したものだ。
つまり、Cで記述されている。

そこで、thread.rbとfasttreadを比較するbenchmarkを計測してみた。
よくある、Queueを使う簡単なConsumer/Producerパターンを検証コードとした。

# coding: utf-8

require 'rubygems' if RUBY_VERSION  < "1.9"
require 'benchmark'

N = 1000000

def consumer_producer
  queue = Queue.new
  producer = Thread.new do
    N.times do |i|
      queue << i
    end
  end
  consumer = Thread.new do
    loop {
      break if !producer.alive? && queue.empty?
      queue.pop
    }
  end
  consumer.join
end

Benchmark.bmbm do |benchmark|
  require 'thread'
  benchmark.report("thread:") {
    consumer_producer
  }
  require 'fastthread'
  benchmark.report("fastthread:") {
    consumer_producer
  }
end

検証環境はWindows(cygwin)とMac(Core2 Duo 2.2GHz)で、実行すると次のような結果になった。

cygwin$ <ruby thread.rb
Rehearsal -----------------------------------------------
thread:       1.154000   0.031000   1.185000 (  1.193000)
fastthread:   1.373000   0.094000   1.467000 (  1.473000)
-------------------------------------- total: 2.652000sec

                  user     system      total        real
thread:       1.342000   0.000000   1.342000 (  1.380000)
fastthread:   1.373000   0.015000   1.388000 (  1.381000)

Macでの実行結果。

osx$ <ruby thread.rb
Rehearsal -----------------------------------------------
thread:       2.450000   0.020000   2.470000 (  2.463525)
fastthread:   2.310000   0.050000   2.360000 (  2.380355)
-------------------------------------- total: 4.830000sec

                  user     system      total        real
thread:       2.200000   0.010000   2.210000 (  2.206227)
fastthread:   2.180000   0.010000   2.190000 (  2.196245)

fastthread使っても、threadのままでもあんまりかわらない。誤差の範囲ぽい。