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か?


 中央管理型のソースコード管理システムと比べて、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 を定義した(逆もまた同様)ことを意味する。


メインブランチ


 開発モデルのコアを成すのは、多くの既存のモデルたちにとてもインスパイアされたものだ。中央リポジトリは永遠の生涯ずっと、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のマージのデフォルトの動作にする方法をまだ見つけてないけれど、それは本当に必要なことだ。

リリースブランチ

分岐元: 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

0 コメント:

コメントを投稿

読者

ブログ アーカイブ