2010年10月30日土曜日

A successful Git branching model を翻訳しました

Vincent Driessenさんの "A successful Git branching model" を翻訳しました。
元記事はこちら: http://nvie.com/posts/a-successful-git-branching-model/
(翻訳の公開と画像の利用は本人より許諾済みです)

このブランチモデルの導入を補助してくれる、git-flowというGit用プラグインがあるそうです。

翻訳の間違い等があれば遠慮なくご指摘ください。



A successful Git branching model



 この記事では、私のいくつかのプロジェクト(仕事でもプライベートでも)で約一年ほど導入して、とてもうまくいくことがわかった開発モデルを紹介する。しばらく前からこれについて書くつもりだったんだが、今まですっかりその時間を見つけられずにいた。ここでは私のプロジェクトの詳細については書かず、ブランチ戦略とリリース管理についてだけ述べよう。


以下では、ソースコードのバージョニングのためのツールとしてのGitに注目する。(ところで、Gitに興味があるのなら、我々の会社GitPrimeがソフトウェア開発に関する素晴らしいリアルタイムデータ分析を提供できるので、是非。)


なぜGitか?


 中央管理型のソースコード管理システムと比べた場合のGitの利点と欠点に関する議論については、webを見てみるといい。そこではたくさんのディスり合いが続いている。さておき、開発者としての私は、こんにちの他の全てのツールよりGitが好きだ。Gitは開発者たちのマージとブランチについての考え方を本当に変えてしまった。私のいた古きよき CVS/Subversion の世界では、マージ/ブランチングはちょっと怖いものと考えられていたし(マージの衝突に用心しろ、噛み付かれるぞ!)、それをする機会はたまにしかなかった。

しかしGitでは、そういった作業は非常に手軽かつシンプルで、全くの日常的なワークフローの一部と考えられている。例えば、 CVS/Subversion の書籍では、ブランチングとマージは後ろの方の章(先進的ユーザのための章だ)で扱われているが、どの Gitの 本でも、3章(基本的な章だ)では既に扱われている。

その気軽さゆえに、ブランチングとマージはもはや恐れるような何かではない。バージョン管理ツールはまず何より、ブランチング/マージを手助けするためのものなのだ。


それでは、ツールではなく開発モデルに触れていこう。なお、ここで私が紹介しようとしているモデルは、ソフトウェア開発プロセスを管理されたものにするために各々のチームメンバが従うべき手続きの集まり以上のものではない。


分散だが中央もある


このブランチングモデルを機能させるためには、中央に「正統な」リポジトリをセットアップする。このリポジトリは、単に中央であると仮定しただけ、ということに注意して欲しい(Gitは分散バージョン管理システムだから、技術的なレベルでは、中央リポジトリのようなものは存在しない)。私たちはこのリポジトリを origin として参照する。この名前が Git ユーザーには馴染みがあるからだ。


それぞれの開発者は、 origin から pull または push を行う。だが中央管理的な push-pull の関係の他にも、各開発者はサブチームを形成し、他の同僚から変更を pull することもある。例えば大きな新機能を作る時、origin に対して作業中のものを push してしまうのではなく、事前に2人以上で共同作業できたら便利だろう。上の図の中では、 Alice と Bob 、 Alice と David 、そして Clair と David のサブチームがある。

技術的には、これは Alice が "bob" という名前の、 Bob のリポジトリを指す Git の remote を定義したことを意味する(逆に Bob は Alice のリポジトリを "alice" という名前で Git に定義している)。


メインブランチ


 開発モデルのコアを成すのは、多くの既存のモデルたちにとてもインスパイアされたものだ。中央リポジトリは永遠の生涯ずっと、2つのメインブランチを保持する:

 ・master
 ・develop

origin の master ブランチは全てのGitユーザーに馴染みがあるはずだ。そして master ブランチに平行する、 develop と呼ばれるもうひとつのブランチが存在する。

origin/master は、製品として出荷可能な状態を常に反映する、ソースコードの HEAD のありかであるメインブランチだと考える。

origin/develop は、次のリリースのための最新の開発作業の変更を常に反映する、ソースコードの HEAD のありかであるメインブランチだと考える。これは「統合ブランチ」と呼ぶこともある。これは自動ナイトリービルドのビルド元にもなる。

develop ブランチのソースコードが安定し、リリースの準備ができたとき、 develop ブランチの全ての変更は master ブランチへマージされ、リリース番号をタグ付けされることになる。(これをどうやるかの詳細は、あとで述べる。)

したがって、 master へ変更がマージされる時はいつも、その定義からして新しい製品リリースの時だ。私たちはここで非常に厳密になる傾向にあり、そのため理論的には、 master にコミットがあるときは毎回Gitのフックスクリプトで自動ビルドを行い、そしてプロダクションサーバにソフトウェアをロールアウトする。


サポートブランチ


 master と develop のメインブランチの隣で、私たちの開発モデルはチームメンバ間の平行開発を助ける様々なサポートブランチを用い、機能の追跡、製品リリースの準備、製品に起きた問題をすばやく修正すること、などを容易にする。メインブランチと異なり、これらのブランチは寿命が決まっており、使い終わったら最終的には削除される。

私たちが使う異なるタイプのブランチたちは以下の通り:

 ・Feature branches
 ・Release branches
 ・Hotfix branches

それぞれのブランチは特定の目的を持ち、どのブランチから分岐するか、またどのブランチへマージされるのか、という厳密なルールと結びついている。すぐにそれらをお見せできるだろう。

技術的な見地からすると、これらのブランチは決して「特別な」ものではない。ブランチの種類は、私たちがそれをどう使うかで分類されたものだ。もちろんそれらはどれも、普通のGitブランチだ。

フィーチャーブランチ

分岐元: develop
マージ先: develop
ブランチ名の慣習: master, develop, release-*, hotfix-* 以外なら全てOK

 フィーチャーブランチ(またはトピックブランチとも呼ばれる)は、今度のリリースに入る、または遠い将来のリリースに入るような新しい機能を開発するのに使われる。ある機能を開発し始めるとき、その時点ではその機能を含めるべきリリースがどれなのか不明であるはずだ。フィーチャーブランチの本質は、機能を開発している限りは存在しているが、結局は develop にマージされる(新機能を次のリリースに追加すると決める)か、捨てられる(実験が期待はずれの場合)ということだ。

フィーチャーブランチは典型的には開発者のリポジトリにだけ存在し、 origin には存在しない。

フィーチャーブランチの作成

 新しい機能の作業を始める時、develop ブランチから分岐する。

$ git checkout -b myfeature develop
Switched to a new branch "myfeature"

開発済みの機能を develop ブランチに取り込む

 確実に今度のリリースに追加する、開発済みの機能は、develop ブランチにマージされる:

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff myfeature
Updating ea1b82a..05e9557
(Summary of changes)
$ git branch -d myfeature
Deleted branch myfeature (was 05e9557).
$ git push origin develop

--no-ff フラグは、たとえマージがfast-forwardで実行できるとしても、新しいコミットオブジェクトを作成する。これは、履歴にフィーチャーブランチが存在したという情報を失うのを避けるのと、機能の追加に使った全てのコミットをひとまとめにしておける。比べてみよう:


右の場合、ある機能を実装したコミットオブジェクトをGitの履歴から見つけられない――あなたは全てのコミットログメッセージを手動で見なければならなくなるだろう。機能の全て(例えば、一連のコミット)を revert しなきゃいけないなら、右の状況では本当に頭を痛くさせる。だがもし --no-ff フラグを使用したなら、簡単に終わるのだ。

確かに、これだとより多くの(空の)コミットオブジェクトを作成するはめになるが、そこから得られるものはコストよりずっと大きい。

残念ながら、 --no-ff をGitのマージのデフォルトの動作にする方法をまだ見つけてないけれど、それは本当に必要なことだ。

(2013年3月24日追記)Git1.7.6からは、以下のコマンドで --no-ff をデフォルトに出来ます。システム全体ではなくそのリポジトリだけに適用したいならば、 --global をはずしてください。
git config --global merge.ff false


リリースブランチ

分岐元: develop
マージ先: develop と master
ブランチ名の慣習: release-*

 リリースブランチは新しい製品リリースの準備をサポートする。それらは最後の瞬間の詰めをしっかりと行わせてくれる。その上、マイナーなバグフィクスや、リリースのためのメタデータ(バージョン番号、ビルド日時、他)の準備までさせてくれる。これらの全ての作業をリリースブランチ上で行うことで、 develop ブランチは次の大きなリリースのための機能を受け取るために、キレイな体でいられる。

develop から新しいリリースブランチを分岐する主要なタイミングは、develop ブランチが新しいリリースの望ましい状態を(ほぼ)反映しているときだ。少なくとも、そのリリースのビルドのターゲットとされる全ての機能は、この時点で develop にマージされていなければならない。将来のリリース向けの全ての機能は違う――それらはリリースブランチを分岐させるまでは、まだマージを待っていなければいけない。

正確には次のリリースがバージョン番号を割り当てられた時が、リリースブランチを始める時だ――それより早くてはいけない。その瞬間までずっと、 develop ブランチは「次のリリース」のための変更を反映するが、リリースブランチを始めるまで、「次のリリース」が結局0.3なのか1.0なのかは不明確だ。その決定はリリースブランチを始める時に、プロジェクトのバージョン番号を増加させるルールに則って、執り行われる。

リリースブランチの作成

 リリースブランチは develop ブランチから作成される。例えば、現在の製品リリースがバージョン1.1.5で、大きいリリースが近づいているとしよう。develop が「次のリリース」への準備が出来ている状態で、それをバージョン1.2(1.1.6や2.0ではなく)と決めた。そしたら、ブランチを切って、それに新しいバージョン番号を反映させた名前をつけるんだ。以下のように。

$ git checkout -b release-1.2 develop
Switched to a new branch "release-1.2"
$ ./bump-version.sh 1.2
Files modified successfully, version bumped to 1.2.
$ git commit -a -m "Bumped version number to 1.2"
[release-1.2 74d9424] Bumped version number to 1.2
1 files changed, 1 insertions(+), 1 deletions(-)

新しいブランチを作ってそれにスイッチした後、バージョン番号を増加させる。ここでは、 bump-version.sh はワーキングコピーのいくつかのファイルを、新しいバージョン番号を反映させるために変更を行うシェルスクリプトだ(もちろんこれは手動でも可能だ――このポイントは、何かしらのファイルを変更しているってことだ)。それから、バージョン番号の増加をコミットする。

リリースが確実にロールアウトするまで、この新しいブランチはしばらく存在するかもしれない。この間、バグフィックスがこのブランチに適用される場合もある(develop ブランチではなく)。ここで新しい大きな機能を加えるのは、厳禁だ。それらは develop ブランチにマージする、つまりその次の大きなリリースを待たなくちゃいけない。

リリースブランチを終える

 リリースブランチが本当にリリースされてもよい状態になったら、いくつかの動作が実行される必要がある。まず最初に、リリースブランチは master にマージされる(その定義から、master にあるコミットは全て新しいリリースだということを思い出そう)。次に、そのマージコミットにはタグをつけて、後で簡単に見直せるようにしなければならない。最後に、リリースブランチで行われた変更を develop にマージしなくちゃいけない。リリースブランチでやったバグフィックスなんかを将来のリリースに含めるためにね。

最初の2つのステップはGitだと:

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2

リリースは完了し、そして後日のためにタグづけされる。
注: -s または -u <key> フラグを使って、タグに署名をした方が良いでしょう。

リリースブランチで作られた変更をまた使い続けたいから、 それらを develop にマージする必要があった。Gitでは:

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)

このステップは、多分マージのコンフリクトを起こすだろう(ここでは私たちはバージョン番号を変更したから、起きる)。もしそうだったら、修正してコミットしよう。

今完全にリリースが終わり、もう必要ないので、リリースブランチは削除される:

$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).

ホットフィックスブランチ

分岐元: master
マージ先: develop と master
ブランチ名の慣習: hotfix-*

 ホットフィックスブランチは、新しい製品リリースへの準備であるという意味でリリースブランチに似ているが、計画されて行われるわけではない。それらは、現在の製品バージョンの望まざる状態への必要性から発生する。製品バージョンにあるクリティカルなバグがすぐに解決されなければならないとき、ホットフィックスブランチは、そのバージョンのタグがつけられている master のコミットから分岐されることになるだろう。

その本質は、(develop ブランチ上で)作業しているチームメンバーが作業を続けられながら、別の人間が製品のすばやい修正を準備できることにある。

ホットフィックスブランチの作成

 ホットフィックスブランチは master ブランチから作成される。例えば現在の製品バージョンが1.2で、深刻なバグがトラブルを起こしているとしよう。だが develop はまだ不安定だ。ならばホットフィックスブランチを切って、問題を修正し始めるんだ:

$ git checkout -b hotfix-1.2.1 master
Switched to a new branch "hotfix-1.2.1"
$ ./bump-version.sh 1.2.1
Files modified successfully, version bumped to 1.2.1.
$ git commit -a -m "Bumped version number to 1.2.1"
[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1
1 files changed, 1 insertions(+), 1 deletions(-)

ブランチを切ったあと、バージョン番号を増加させるのを忘れないように!

それからバグを修正して、一つ以上のコミットを行う。

$ git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)

ホットフィックスブランチを終える

 修正が終わった時、そのバグフィックスを次のリリースにもちゃんと含められるように保護するために、 master にマージされるだけでなく develop にもマージされる必要がある。これはリリースブランチを終える時ととてもよく似ている。

最初に、master をアップデートして、タグをつける。

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2.1

注: -s または -u <key> フラグを使って、タグに署名をした方が良いでしょう。

次に、バグフィックスを develop にも含めさせる。

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)

ここで一つルールの例外があって、リリースブランチがこの時に存在していたら、ホットフィックスの修正を develop の代わりにリリースブランチにマージしなきゃいけない。リリースブランチにバグフィックスをマージすると、リリースブランチを終えたときに結局 develop にも含まれることになる(develop での作業にこの修正がすぐに必要で、リリースブランチを終えるのを待てないなら、今ここでも develop にマージする場合もある)。

最後に、一時的なブランチを削除しよう:

$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).


まとめ


 このブランチモデルには驚くような新しいことは一つもないが、この記事の最初の「大きな絵」は、私たちのプロジェクトにものすごく役立つと判明した。それは理解しやすいエレガントなメンタルモデルを形成し、チームメンバーにブランチングと、リリースプロセスの理解の共有を発展させる。

高品質な図のPDF版はここに用意した。持って行って、そしていつでもクイックリファレンスとして壁に掛けておいて欲しい。



更新:それと誰かがリクエストしていたので:ここに gitflow-model.src.key として主な図の画像を置く(Apple Keynote)。





Gitに関する翻訳記事はこちらもどうぞ: 【翻訳】あなたの知らないGit Tips

2010年10月9日土曜日

Ruby1.9.2で日本語を含むYAMLを出力する時、バイナリ文字列化されないようにする

RubyのYAML

Ruby(1.8以上)ではYAMLライブラリが標準添付ライブラリです。
そのため、YAMLを扱うには「require 'yaml'」とするだけです。

これでYAML.load(YAML形式から読み込み)、YAML.dump(YAML形式への出力)、
さらに様々なオブジェクトで to_yaml メソッドが使えるようになります。


日本語を含むYAMLを出力

しかしこのままでは、日本語文字列を含んだオブジェクトを
YAMLドキュメントとして出力すると、日本語部分がバイナリ文字列になってしまいます。
これでは人間にも読みやすいというYAMLの特徴が台なしです。

# -*- coding: utf-8 -*-
require 'yaml'

puts YAML.dump({'あ' => 'い'}) 


# 【出力結果】
# --- 
# "\xE3\x81\x82": "\xE3\x81\x84"

これを解消する方法を軽く検索してみると、
gemで違うYAMLライブラリを入れるとか、パッチをあてるなどの情報が出てきます。

Ruby1.9.2では:Psych

Ruby1.9.2からは、PsychというYAMLパーサも標準添付ライブラリになっています。
(以前からあるのは、Syckというライブラリ)
このPsychは「require 'yaml'」するだけでは有効にならないので、以下のようにします。

# -*- coding: utf-8 -*-
require 'yaml'

YAML::ENGINE.yamler = 'psych'

puts YAML.dump({'あ' => 'い'})


# 【出力結果】
# ---
# あ: い

今度は日本語がバイナリ文字列化されませんでした。


この方法はTwitter上で @sakuro さんに教えて頂きました。
ありがとうございました!

(2010年10月9日夜に追記↓)

YAML::ENGINE.yamlerを使わなくとも、「yamlの前にpsychがロードされていればOK」とのことです。
つまり以下の方法でも良いということになります。

# -*- coding: utf-8 -*-
require 'psych' # 追加
require 'yaml'

puts YAML.dump({'あ' => 'い'})


# 【出力結果】
# ---
# あ: い

上記の追記はTwitterで @n0kada さんに教えて頂きました。感謝です!

ちょっと実験

require 'psych'だけではどうでしょうか。

# -*- coding: utf-8 -*-
require 'psych'

puts YAML.dump({'あ' => 'い'})

# 【出力結果】
# uninitialized constant Object::YAML (NameError)

エラーになります。YAMLは使えません。

ではrequire 'yaml'が先で、require 'psych'が後だったら?

# -*- coding: utf-8 -*-
require 'yaml'
require 'psych'

puts YAML.dump({'あ' => 'い'})

# 【出力結果】
# --- 
# "\xE3\x81\x82": "\xE3\x81\x84"

Syckが使われてしまうようです。

1.9.2なのにPsychが使えないよ?

Ruby1.9.2なのにPsychが使えない場合は、 libyaml-dev を入れてからRubyを再コンパイルしましょう(パッケージ名は、Ubuntu 等の場合)。

Psychという名前

PsychであってPsyckじゃないんですね。
前のライブラリがSyckなのでちょっと紛らわしい。

Ruby1.9.3では(2011年11月28日追記)

コメントで教えて頂いた情報によると、
Ruby1.9.3からは、Psychの方がデフォルトになっているようです。
ですので、require 'yaml' 以外に余計な事を書かなくても、日本語を正しく扱うことができます。

逆に Syck を使いたい場合は、YAML::ENGINE.yamler='syck' としなければなりません。

フォロワー