ソフトウェアエンジニア現役続行

雑多なことを綴ります

Stashでdiffビューの文字化けを解消する方法

Transcode diffs を有効にして文字化けを解消

Stash 3.1より前のバージョンでは、UTF-8 以外のファイルは diff ビューで文字化けを起こしていました。Stash 3.1以降では、「Repository details(リポジトリ詳細)」の「Transcode diffs(diffをトランスコードする)」を有効にすることで、この文字化けを解消できます。詳しくは Using diff transcoding in Stash に記載されています。

なぜ Transcode diffs はデフォルトで無効?

上記リンクによると、Transcode diffs を有効にすると Stash は文字化けを回避するためにファイルを textconv で UTF-8 に変換してから git diff を行います。ファイルのサイズなどによってはこの処理が重くなってしまうため、パフォーマンスの観点から Transcode diffs はデフォルトで無効になっています。

そもそも、なぜ UTF-8 に変換してから diff ?

これは Stash ではなく Git が UTF-8 しかサポートしていないのが原因です。Stash のソースコードを確認してみると、Stash は内部で JGit という Java の Git ライブラリを使っています。

私の思う Bamboo の良いところ

Bamoo はブランチによる開発を協力にサポート

Git の登場により、機能ブランチ(フィーチャーブランチ)や git-flow といった開発のワークフローが提唱されています。典型的なワークフローは以下のような形でしょうか。

  1. 機能ブランチを作成してそこで開発
  2. 開発が終わったら CI ツールで結合テストをして、ブランチ元へプルリクエスト
  3. (メンバーのレビューを経て)ブランチ元へマージ
  4. 開発者用サンドボックスや QA サーバーへデプロイしてエージング
  5. しかるべきタイミングで本番環境へリリース

アトラシアン社が提供する CI(継続的インテグレーション)・CD(継続的デリバリー)ツールである Bamboo を使うと、開発者は上記のプロセスの中で開発作業に集中でき、それ以外の大部分を自動化することができます。UI は英語ですが、無料お試しの30日間でいろいろ試すことができると思います。私の感じた Bamboo の特長は以下です:

  • 自動ブランチ検知
  • 安全で簡単なデプロイ
  • メンバーへのタイムリーな通知
  • 他のアトラシアンツールとのコラボレーション

 特長1: 自動ブランチ検知

ソースコードリポジトリとして Git や Mercurial のサーバーを使っている場合、Bamboo はリポジトリ上でブランチが作成されたことを自動的に検知して、そのブランチ用のプランを自動的に作成してくれます。GitHub, Bitbucket, Stash はもちろん、Git, Mercurialリポジトリサーバーなら何でも連携できます。

そのため開発者が 1) リポジトリでブランチを作成し、2) コーディングを終えて、3) そのリポジトリにコミットするだけで、Bamboo が自動的にブランチの作成とコミットを検知して、ビルドやテストを行ってくれます。私の知るかぎり、自動ブランチ検知を提供している CI ツールは他に見当たりません。

f:id:Oswald:20140509120525j:plain

なお Git や Mercurial ではなくて Subversion など他のリポジトリサーバーを使っている場合、自動ブランチ検知はできませんが、Bamboo 上で手動でブランチ用のプランを作ることで、同じようにブランチのソースコードのビルドやテストを行うことが可能です。

特長2: 安全で簡単なデプロイ

デプロイで私が気に入っている Bamboo の機能は以下です:

  1. master にコミットされたソースがビルドに成功したら、開発者用サンドボックスや QA サーバーに自動デプロイしてほしい(本番環境には自動デプロイしてほしくない)
  2. リリース物を間違えることなく確実に本番環境にデプロイしたい

特に2つ目はリリースオペレーターの人にはとてもうれしい機能なのではないでしょうか。どんなにしっかりテストをしてきたものでも、本番リリースにはある程度のプレッシャーを感じます。また、リリース物を間違えてしまうような失敗も絶対に避けたいです。Bamboo はテストでビルドしたものや QA サーバーにデプロイしたものと同一のものを本番環境へリリースすることができます。

f:id:Oswald:20140509104228j:plain

f:id:Oswald:20140509104235j:plain

特長3: メンバーへのタイムリーな通知

他の CI ツール同様、Bamboo もビルドの結果やデプロイの開始/終了を通知する機能があります。Eメール、アトラシアン製品である HipChat のほか、XMPP プロトコルをサポートするIMアプリへ通知が可能です。これによって、メンバー全員がビルドやデプロイの状況をリアルタイムに知ることができます。

特長4: 他のアトラシアンツールとのコラボレーション 

これがアトラシアン製品の肝だと思いますが、JIRA や Stash など他のアトラシアン製品を使っていれば、Bamboo におけるビルドやデプロイの状況をこれらのツールから知ることができます:

  • Stash でブランチを切るときに、ブランチ元がビルドをパスしていることが分かる
  • Stash でプルリクエストのレビューをする際に、ビルドをパスしているかがで分かる
  • JIRA 上で、その機能のビルドやデプロイの状況が一目で分かる

プロジェクト進捗会議のたびに開発者、テスター、リリースオペレーターなど各担当者に状況を聞いて回ってエクセルにまとめる、なんて苦い経験をさせられることはもうありません。

f:id:Oswald:20140509121137j:plain

f:id:Oswald:20140509120326j:plain

f:id:Oswald:20140509120335j:plain

Confluence で lucene-kuromoji のユーザー辞書を使えるようにする

注意

以下の内容はConfluence 5.5.6からConfluenceのソースコードに反映されました(ブログの内容とは少し違う形で)。Confluence 5.5.6以降でユーザー辞書を使う方法はユーザー辞書を使って検索精度を向上させる方法を参照してください。

Confluence のカスタム日本語は lucene-kuromoji を使っている

Confluence の検索機能は Lucene を使っています。Lucene の日本語プラグイン lucene-kuromoji も搭載されていて、これを使うには Confluence の管理画面でインデックス作成言語を「カスタム日本語」に設定します。

ユーザー辞書による lucene-kuromoji の検索精度向上

lucene-kuromoji は内部で持っている辞書を使って分かち書きを行って検索インデックスを作成するため、辞書にない単語は正しく分かち書きをすることができません。例えば Confluence で「マイグレーションツール」という単語の含まれたページを作成したとき、これを「マイグレーション」や「ツール」という言葉で検索してもページがヒットしません。これは「マイグレーションツール」が「マイグレーション」と「ツール」の合成語だという情報が lucene-kuromoji の辞書に存在しないことが原因です。この場合、ユーザー辞書でこの合成語を正しく登録をすることで問題を解決することができます。

Confluence で lucene-kuromoji のユーザー辞書を使えるようにする

現状、Confluence ではユーザー辞書が使えるようになっていませんが、ソースコードが公開されているので、ソースコードを修正してユーザー辞書を使えるようにしてみました。手順は以下のとおりです:

  1. Confluence のソースコード をダウンロードしてローカルに展開する
  2. Gist にアップロードしているパッチ を適用する
  3. build.sh または build.dat でビルドする (*)
  4. ユーザー辞書ファイルを <confluence-home>/config/userdict_ja.txt に置く
  5. ビルドされた confluence.war をサーブレットにデプロイする

(*) ビルド方法は Building Confluence From Source Code に記載されていますが、そのとおりにビルドしても失敗する場合は、Confluence のソースコードをビルドする方法 で解決するかも知れません。

ユーザー辞書ファイルでは、例えば以下のように「マイグレーションツール」を合成語として単語登録します。

$ cat userdict_ja.txt 
マイグレーションツール,マイグレーション ツール,マイグレーション ツール,カスタム名詞

そして Confluence 管理画面からインデックスの再構築を行うと、以下のように「マイグレーション」や「ツール」でヒットするようになりました。

f:id:Oswald:20140504152436j:plain

Luke (Lucene インデックスブラウザ) の lucene-4.x 対応版

Luke は Lucene のインデックスブラウザで、とても便利なツールだったのですが、lucene のバージョンが 4.x になってから使うことができなくなっていました。ところが探してみると、4.x に対応したものが GitHub 上に公開されていました (tarzanek/luke · GitHub)。これは Luke オリジナル (http://luke.googlecode.com/svn/からコードフォークさせて、Lucene 4.x に対応したものです。使い方などは全く同じです。オリジナルのほうは開発が止まっていて今も Lucene 4.x に対応しておりません。

tarzanek/luke をダウンロードしてビルドする

簡単です。

$ git clone https://github.com/tarzanek/luke.git

でコードをダウンロードして、

$ ant

でビルドが完了します。dist ディレクトリ配下に jar ファイルができているのを確認してください。

tarzanek/luke を使ってみる

$ java -classpath dist/lukeall-X.Y.Z.jar org.getopt.luke.Luke

GUI が起動するので、GUI から Lucene のインデックスディレクトリを開けば OK です。

ここでは例として以下のプログラムを実行します。

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

import java.io.File;
import java.io.StringReader;

public class IndexString {

  private IndexString() {}

  public static void main(String[] args) throws Throwable {
    String indexPath = "index";
    String sampleString = "This is a sample program to test lucene and luke.";
    Directory dir = FSDirectory.open(new File(indexPath));
    Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_47);
    IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_47, analyzer);
    iwc.setOpenMode(OpenMode.CREATE);
    IndexWriter writer = new IndexWriter(dir, iwc);
    Document doc = new Document();
    doc.add(new TextField("contents", new StringReader(sampleString)));
    writer.addDocument(doc);
    writer.close();
  }
}

lucene-core-4.7.Xlucene-analyzers-common-4.7.X のライブラリに依存しているので、Maven Repository などでライブラリを入手してください)

このプログラムを実行すると、sampleString 変数で定義された文字列がインデキシングされ、その結果が indexPath 変数で定義したディレクトリに格納されます。

そして Luke を起動してインデックスの格納されたディレクトリを選ぶと、先ほどの sampleString 変数の中身がインデキシングされていることが確認できます。

f:id:Oswald:20140429183306j:plain

Confluence のソースコードをビルドする方法

コラボレーションツールとして人気があるアトラシアン社の Confluence はソースコードが公開されているので、ソースコードからビルドしてサーバーにデプロイしてみました。やり方はアトラシアン社の Building Confluence From Source Code にまとまっていて、warファイルをビルドするだけならそのページの中の Building WAR Destribution のセクションに書かれているとおりに 1) ソースコードをダウンロードして、2) ファイルを展開して、3) ビルドスクリプト (build.sh または build.bat) を実行すればOKです。

ですが実際にやってみると、何度も maven の依存関係が解決できなくて何度もビルドにつまづいたので、解決方法をここに記載しておきます。なお私がビルドしたのは 2014年4月27日時点で安定版で最新の 5.4.4 です。

追記: 2014年4月29日にリリースされた 5.5 でも下記の方法でビルドできました

足りない jar ライブラリを手動でインストールしたのにビルドが通らない

ビルドスクリプトは、ソースコードパッケージに同梱されている maven を使って、依存しているライブラリをダウンロードしながらビルドを進めていきます。ですが、いくとかのライブラリを見つけることができずにエラーになります。私が Confluence 5.4.4 のソースコードをビルドしたときは、以下のライブラリが見つからなくてエラーになりました:

  • jms-1.1.jar
  • mail.jar
  • activation-1.0.2.jar

上記のライブラリを Oracle の公式サイトなどから入手して、エラーメッセージに書かれているとおりに以下のコマンドを実行して再ビルドしても、同じエラーが出て、前に進められませんでした。

$ mvn install:install-file -DgroupId=javax.jms -DartifactId=jms -Dversion=1.1 -Dpackaging=jar -Dfile=/path/to/jms-1.1.jar

$ mvn install:install-file -DgroupId=javax.mail -DartifactId=mail -Dversion=1.3.3 -Dpackaging=jar -Dfile=/path/to/mail.jar

$ mvn install:install-file -DgroupId=javax.activation -DartifactId=activation -Dversion=1.0.2 -Dpackaging=jar -Dfile=/path/to/activation-1.0.2.jar

調べたところ、ビルドスクリプトから呼び出される maven コマンドが利用するローカルリポジトリ、自分のマシンの標準リポジトリMACLinux なら $HOME/.m2/repository)ではなくて、ソースコードパッケージが展開された場所の localrepo ディレクトリでした。ですので上記の mvn install:install-file コマンドは以下のように -Dmaven.repo.local オプションでこの localrepo ディレクトリを指定して実行する必要があります。

$ mvn install:install-file -DgroupId=javax.jms -DartifactId=jms -Dversion=1.1 -Dpackaging=jar -Dfile=/path/to/jms-1.1.jar -Dmaven.repo.local=/path/to/localrepo

$ mvn install:install-file -DgroupId=javax.mail -DartifactId=mail -Dversion=1.3.3 -Dpackaging=jar -Dfile=/path/to/mail.jar -Dmaven.repo.local=/path/to/localrepo

$ mvn install:install-file -DgroupId=javax.activation -DartifactId=activation -Dversion=1.0.2 -Dpackaging=jar -Dfile=/path/to/activation-1.0.2.jar -Dmaven.repo.local=/path/to/localrepo

(-Dfile にはローカル上の実際の jar ファイルへのパス、-Dmaven.repo.local にはソースコードパッケージが展開された場所の localrepo ディレクトリを指定します)

テストに必要な jar ライブラリが見つからない

上記でビルドがすべて通るかと思ったら、以下の2つのライブラリがどうしてもインターネット上で見つけられませんでした。

  • db2jcc4-9.7.jar
  • oracle-jdbc15-11.2.0.1.0.jar

上記のファイルはビルド時に実行されるテストスクリプトでのみ必要なライブラリのため、このテストをスキップさせることにしました。具体的には、confluence-project/confluence-build/confluence-test-runner/pom.xml ファイルの <modules> の中の <module>confluence-multi-test</module> をコメントアウトしました。

rubygems の riak-client-1.4.3 が ruby-1.8.7 で Syntax Error

7つのデータベース 7つの世界という書籍でデータベースについて勉強中です。特にNoSQLやKey Value Storeと言われるデータベースを実際にインストールして動かしながら学ぶには非常に良い本だと思います。ちなみに理論だけで良ければNOSQLの基礎知識という書籍がオススメです。

さて、この7つのデータベース 7つの世界で紹介されている7つのデータベースの1つが Riak という Key Value型 のデータベースです。この書籍では、Ruby の Riakドライバを使って1万件のデータを Riakサーバー に格納する作業があり、Rubygems の riak-client を使っています。

ですが、ここで書籍に書かれているとおりに

$ gem install riak-client

でRiakドライバーをダウンロードして、以下のRubyコードを実行すると、

# generate loads and loads of rooms with random styles and capacities
require 'rubygems'
require 'riak'
STYLES = %w{single double queen king suite}
client = Riak::Client.new(:http_port => 8091)
bucket = client.bucket('rooms')
# Create 100 floors to the building
for floor in 1..100
current_rooms_block = floor * 100
puts "Making rooms #{current_rooms_block} - #{current_rooms_block + 100}"
# Put 100 rooms on each floor (huge hotel!)
for room in 1...100
# Create a unique room number as the key
ro = Riak::RObject.new(bucket, (current_rooms_block + room))
# Randomly grab a room style, and make up a capacity
style = STYLES[rand(STYLES.length)]
capacity = rand(8) + 1
# Store the room information as a JSON value
ro.content_type = "application/json"
ro.data = {'style' => style, 'capacity' => capacity}
ro.store
end
end

3行目の「require 'riak'」で以下のようなRubyの構文エラー(Syntex Error)が発生してしまいます。

/usr/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:36:in `gem_original_require': /var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak/client/http_backend.rb:195: syntax error, unexpected ':', expecting kEND (SyntaxError)
          stream_opts = options.merge keys: 'stream'
                                           ^
/var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak/client/http_backend.rb:198: syntax error, unexpected ':', expecting kEND
          list_opts = options.merge keys: true
                                         ^
/var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak/client/http_backend.rb:209: syntax error, unexpected ':', expecting ')'
...ist_path(options.merge(stream: true)), &BucketStreamer.new(b...
                              ^
/var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak/client/http_backend.rb:209: syntax error, unexpected ')', expecting kEND
/var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak/client/http_backend.rb:302: syntax error, unexpected ':', expecting ')'
...index.wrong_backend', backend: match[1])
                              ^
/var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak/client/http_backend.rb:302: syntax error, unexpected ')', expecting kEND
/var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak/client/http_backend.rb:347: syntax error, unexpected kDO_BLOCK, expecting kEND
            response = get(200, luwak_path(filename)) do |chunk|
                                                        ^
/var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak/client/http_backend.rb:348: syntax error, unexpected tIDENTIFIER, expecting kEND
/var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak/client/http_backend.rb:352: syntax error, unexpected kENSURE, expecting kEND
/var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak/client/http_backend.rb:397: syntax error, unexpected kDO_BLOCK, expecting kEND
        {}.tap do |result|
                 ^
/var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak/client/http_backend.rb:401: syntax error, unexpected kDO_BLOCK, expecting kEND
            result['docs'] = json['response']['docs'].map do |d|
                                                            ^
/var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak/client/http_backend.rb:413: syntax error, unexpected $end, expecting kEND
    from /usr/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:36:in `require'
	from /var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak/client.rb:11
	from /usr/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:36:in `gem_original_require'
	from /usr/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:36:in `require'
	from /var/lib/gems/1.8/gems/riak-client-1.4.2/lib/riak.rb:3
	from /usr/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:59:in `gem_original_require'
	from /usr/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:59:in `require'
	from hotel.rb:3

 

とりあえずコマンドで古い v1.2.0 の riak-client をインストールし、Syntax Error を回避しました。

$ gem install riak-client -v 1.2.0

Riak-1.0.2 で make devrel ができない

7つのデータベース 7つの世界という書籍でデータベースについて勉強中です。特にNoSQLやKey Value Storeと言われるデータベースを実際にインストールして動かしながら学ぶには非常に良い本だと思います。ちなみに理論だけで良ければNOSQLの基礎知識という書籍がオススメです。

さて、この7つのデータベース 7つの世界で紹介されている7つのデータベースの1つが Riak という Key Value型 のデータベースです。書籍のとおりに Riak の v1.0.2 のソースコードgithubのリポジトリ からダウンロードしてビルドするべく

$ make devrel

を実行しても、以下のようなエラーメッセージが出てビルドができません。

mkdir -p dev
(cd rel && ../rebar generate target_dir=../dev/dev1 overlay_vars=vars/dev1_vars.config)
==> rel (generate)
{"init terminating in do_boot","Illegal library /home/ubuntu/riak/rel/../deps: no such file or directory"}

Crash dump was written to: erl_crash.dump
init terminating in do_boot (Illegal library /home/ubuntu/riak/rel/../deps: no such file or directory)
make: *** [dev1] Error 1

そこで2014年3月16日時点で Riakの公式サイト にホストされている最新リリース版の v1.4.8 を使ったところ、うまくビルドできました。そのまま読み進めていますが、バージョンが違うことによる大きな問題はありません。riakプロセスが Listen するデフォルトポートが 8091 から 10018 に変わったことくらいです。