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

5 件のコメント:

  1. 小林と申します、はじめまして。
    すばらしいです。
    大変参考になりました。
    http://www.plowman.co.jp/school/Git/Git.html
    からこのページにリンクさせていただきました。
    問題有りましたら、kkoba@plowman.co.jpまでご連絡ください。

    返信削除
  2. フィーチャーブランチの説明のところで
    "残念ながら、 --no-ff をGitのマージのデフォルトの動作にする方法をまだ見つけてないけれど、それは本当に必要なことだ"
    という文がありました。

    最近のgitでは設定できるようです。便利ですね。
    http://stackoverflow.com/questions/2500296/can-i-make-fast-forwarding-be-off-by-default-in-git

    ご参考まで。

    返信削除
    返信
    1. 記事の方に反映させるのが大変遅れてしまい、すみません。
      追記しておきました。ご指摘ありがとうございました!

      削除
  3. 初めまして、箱崎と申します。
    現在、遅れ馳せながら、会社のVCSをSubversionからGitに移行するため調査をしております。
    ワークフローに関して理解が進まず困っていたのですが、このサイトが大変参考になりました。
    拝見させて頂くなかで、何点か疑問に思う部分がありましたので、私なりの変更案を考えてみました。ご検討頂ければ幸いです。


    [A successful Git branching model]
    line1: "some of my projects"が"全てのプロジェクト"となっているが、"いくつかのプロジェクト"ではないでしょうか?

    line4: "the branching strategy and release management"を"リリース管理のブランチ戦略"としているが、"ブランチ戦略とリリース管理"と原文に近い訳にする方が良いのではないでしょうか?

    line6: "Git as the tool"の"as"を訳出されていないようですが、色々な論点がある中で特にバージョニングのツールとしてのGitについて焦点を当てる、という意味合いで"としての"等の訳語を追加されては如何でしょうか?

    line6: ()内については意図的に削除されているのですよね?



    [Why git?]
    line1: "For a thorough discussion ... systems, see the web."の部分ですが、"中央管理型のソースコード管理システムと比べた場合のGitの長所・短所の徹底的な議論については(ここでは議論しないが、もし見たければ)webを見てください。"ということではないでしょうか?

    line12: "supposed"を"supported"と見間違えているような…。



    [The main branches]
    line1: "At the core, the development model is greatly inspired by existing models out there."は"中核をなす開発モデルは、既存のモデル群から大きな影響を受けている。"という感じでどうでしょうか?
    line3: "with an infinite lifetime"は"永遠の寿命を持つ"ではどうでしょうか?



    [Incorporating a finished feature on develop]
    line15: "it is impossible to see from the Git history which of the commit objects together have implemented a feature"は"一つの機能を実装しているのがどのコミットオブジェクトのグループなのかをGitの履歴から知ることができない"という感じでしょうか?

    line16: "Reverting a whole feature (i.e. a group of commits)"は"ある機能の全体(つまり一連のコミット)をrevertすること"ではどうでしょうか?



    [Hotfix branches]
    line9: "They arise from the necessity to act immediately upon an undesired state of a live production version."は"現在の製品バージョンの望ましからざる状態に即座に対応する必要から生じる。"ではどうでしょうか?

    line14: "The essence is that work of team members (on the develop branch) can continue, ..."は"その本質は、製品に対する応急処置を準備している間も、(developブランチ上での)チームメンバーの作業を継続することができるということです。"ではどうでしょうか?



    [Summary]
    line1: "big picture"はあえて「大きな絵」と訳されているのだとは思いますが、やはり"全体像"とした方が、日本語としては馴染む感じがします。

    line3: "allows team members to develop a shared understanding of the branching and releasing prodesses."は"チームメンバーにブランチとリリースの手順についての共通理解を持たせる"ぐらいでいいのではないでしょうか?


    返信削除
  4. >箱崎さま
    私の翻訳を参考にしてくださり、またこのように詳細な改善の提案をして頂きありがとうございます!

    なにぶんだいぶ前にガムシャラに訳したものなので、かなり粗があったかと思います。
    ちょっとこのブログをいじる時間がすぐ取れないのですが、箱崎さまの提案を受け入れる形で修正していきたいと思います。

    (Gitの記事なのにGit管理されていないせいで、直してもdiffで示せなくて申し訳ないです。。)

    返信削除

フォロワー