31 Aug 2011
私は人々にGitを教えるためにあちこちを飛び回っているが、最近のほぼすべてのクラスやワークショップでgit-flowについてどう思うかを尋ねられた。私はいつも、git-flowは素晴らしいと思うと答えている。何百万ものワークフローを持ったシステム(Git)を提供し、ドキュメントもあるし、よくテストされている。フレキシブルなワークフローは、実に容易なやり方で多くの開発者の役に立つ。標準的なものになりつつあり、開発者はプロジェクトや企業の間を移動しつつこの標準的なワークフローに馴染むことができる。
しかしながら、それ故の問題も抱えている。新しいフィーチャーブランチをmasterではなくdevelopから開始するとか、hotfixesを扱う方法といったことを好まないような人たちから多くの意見を聞く。
私が考える大きな問題のひとつは、それが、ほとんどの開発者や開発チームが実際に必要とするよりも複雑すぎやしないか、ということだ。フローの遂行を支援するために開発された巨大なヘルパースクリプトであり、あまりに複雑すぎる。
クールかもしれないが、GitのGUIツールには強制することができず、コマンドラインでしか使えないという問題がある。すべての手順を手動で行う必要もある。そのための複雑なワークフローをしっかりと学ばなければならない人たちが、コマンドラインでの作業に不慣れな人たちとイコールでもある。これは大きな問題点だ。
これらの問題点は、手順をもっとシンプルにするだけで容易に解決できる。GitHubでは、git-flowを使っていない。私たちが使う手順、いつも使っている手順はとてもシンプルなGitワークフローだ。
そのシンプルさには多くのメリットがある。ひとつは、簡単に理解できるということ。より素早く作業ができ、何かを台無しにしてしまうとか間違ってしまった手順をやり直すといったこともめったに起こらない。他にも、プロセスを支援するためのラッパースクリプトが必要ないため、GUIプログラムも問題なく使えるというメリットもある。
さて、なぜGitHubではgit-flowを使わないのだろうか? 私たちが常にデプロイをするから、というのが主な理由ではある。 git-flowのプロセスは主として「リリース」を中心に設計されている。 私たちはプロダクション環境へのデプロイを毎日(たいていは日に何回も)行うため、「リリース」というものがない。 私たちはチャットルーム内のロボットを通じてデプロイをすることができ、そこにはCIの結果も表示される。 私たちはテストとデプロイの手順を可能な限りシンプルにするようにしており、それらをすべての従業員が安心して行うことができる。
定期的にデプロイを行うことにはいくつかの利点がある。 数時間毎にデプロイをすれば、大きなバグが沢山入るようなことはほぼありえない。 小さなバグが入ることはあるだろうが、そういったものは素早く修正して再デプロイすることができる。 本来なら「hotfix」ブランチや普段の手順とは違う形で修正を行おうとするだろうが、私たちの場合はそれも通常のプロセスの一貫でしかない。GitHubのやり方では、hotfixと小さな機能追加とに違いはまったくない。
四六時中デプロイすることのもうひとつの利点は、あらゆる種類の問題を迅速に解決することが可能になる点だ。 私たちは、セキュリティ上の問題や、小さいけれども重要な機能の要望にとても迅速に対応することができる。 そして、それらの変更に対処する際は、普段の開発や大きな機能の開発をする際に使うのとまったく同じプロセスを使うことができる。
すべてが同じプロセスであり、すべてがとてもシンプルだ。
GitHub Flowとは何だろうか?
master
ブランチのものは何であれデプロイ可能である- 新しい何かに取り組む際は、説明的な名前のブランチをmasterから作成する(例:
new-oauth2-scopes
) - 作成したブランチにローカルでコミットし、サーバー上の同じ名前のブランチにも定期的に作業内容をpushする
- フィードバックや助言が欲しい時、ブランチをマージしてもよいと思ったときは、プルリクエストを作成する
- 他の誰かがレビューをして機能にOKを出してくれたら、あなたはコードを
master
へマージすることができる - マージをして
master
へpushしたら、直ちにデプロイをする
これがフローのすべてだ。 とてもシンプルかつ効率的で、かなり大きなチームでも機能する。現在GitHubは35人で、そのうちの約15〜20人が一度に同じプロジェクト(github.com)で作業している(訳注:数字は2011年8月時点のもの)。 ほとんどの開発チーム(同時期に同じコードで作業をし、コンフリクトを発生させる可能性のある集団)はこれくらいか、もっと小さいと思う。 とりわけ、迅速に一貫したデプロイを行なうような進歩的なチームなら。
では、各ステップを順に見て行こう。
このシステムにおいて困難なルールは、おおむねこの点だけである。
明確に一貫した目的をもつ唯一のブランチがあり、それをmaster
と呼ぶ。
それは、そのブランチが既にデプロイされているか、または最悪の場合でも数時間以内にはデプロイされる、ということを意味する。
ブランチが巻き戻される(作業内容を取り消すためにブランチが古いコミットを指すようにする)ことは非常に稀である − もし問題が起きたら、コミットは取り消されるか(reverted)、問題を修正した新しいコミットが行われるが、ブランチ自身がロールバックすることはほとんどない。
master
ブランチは安定しており、常に、そう常にデプロイ可能かつそこから新しいブランチを作成できる状態になっている。
テストされていなかったり、ビルドを破壊するようなコードをmaster
にpushした場合には、開発チーム間におけるソーシャルな取り決めを破ることになり、ちょっと気まずい思いをすることになる。
我々がpushしたすべてのブランチではテストが実行され、その結果がチャットルームに報告される。もしテストを手元で実行していない場合には、サーバー上のトピックブランチ(たいていは1つのコミットだけのブランチ)にpushして、Jenkinsがその結果を教えてくれるのを待つこともできる。
デプロイを行った時だけ更新されるdeployedブランチを用意することもできるが、我々はそのようなことはしない。 我々は、現在デプロイされているSHA(ハッシュ)をWebアプリ経由で公開するようにし、比較が必要な場合はそれを単にcurlするだけにしている。
何か作業を始めたい時は、安定したmaster
ブランチから説明的な名前のブランチを作成する。
GitHubの今のコードでは、user-content-cache-key
やsubmodules-init-task
、redis2-transition
といった感じだ。
このやり方にはいくつか利点がある。
fetchすると他の皆が現在作業しているトピックを知ることができる、というのが1つ。
しばらくの間ブランチを放っておいて後からその作業に戻った時に、何をしていたかすぐに思い出せるという利点もある。
GitHubのブランチリストページでは、最近どんなブランチで作業がされているのか、どれくらいの作業をしているのかを大まかに知ることができるので、その点でも素晴らしい。
これは、もうすぐ実装される機能の一覧におおまかな現在の状況が付いているようなものだ。このページはとても素晴らしい − 現在選択しているブランチと比較して、それぞれのブランチがどのような固有の作業内容を含んでいるかだけが表示され、最も最近作業されたものが一番上に来る。興味を惹かれれば、Compareボタンをクリックして実際の差分を見たり、そのブランチ固有のコミット一覧を見ることもできる。
この文章を書いている時点では、我々のリポジトリにはまだマージされていない作業を含む44のブランチがあり、そのうち先週pushされたものが9個から10個ほどあることもわかる。
git-flowとの大きな違いのひとつが、名前を付けたブランチを定期的にサーバーにpushするという点だ。我々がデプロイの観点で本当に気にしているものはmaster
だけなので、サーバーへpushすることが誰かの手を煩わせたり、混乱を引き起こしたりすることはない − master
以外のものはすべて、単に作業中の何かだということに過ぎない。
それによって、ノートパソコンを紛失したりハードディスクが故障した場合でも作業内容が常にバックアップされていることも確実となる。より重要なこととして、皆が定期的にコミュニケーションをとるようになる。単なるgit fetch
が、皆が作業していることについてのTODOリストを与えてくれる。
$ git fetch
remote: Counting objects: 3032, done.
remote: Compressing objects: 100% (947/947), done.
remote: Total 2672 (delta 1993), reused 2328 (delta 1689)
Receiving objects: 100% (2672/2672), 16.45 MiB | 1.04 MiB/s, done.
Resolving deltas: 100% (1993/1993), completed with 213 local objects.
From github.com:github/github
* [new branch] charlock-linguist -> origin/charlock-linguist
* [new branch] enterprise-non-config -> origin/enterprise-non-config
* [new branch] fi-signup -> origin/fi-signup
2647a42..4d6d2c2 git-http-server -> origin/git-http-server
* [new branch] knyle-style-commits -> origin/knyle-style-commits
157d2b0..d33e00d master -> origin/master
* [new branch] menu-behavior-act-i -> origin/menu-behavior-act-i
ea1c5e2..dfd315a no-inline-js-config -> origin/no-inline-js-config
* [new branch] svg-tests -> origin/svg-tests
87bb870..9da23f3 view-modes -> origin/view-modes
* [new branch] wild-renaming -> origin/wild-renaming
さらにそれによって、他の皆が何をしているのかを知ったり、助けを必要としていないかを確認するためにGitHubのブランチリストページを見るよう、全員が動くことにも繋がる。
GitHubには、残念だが十分な人々には知られていない Pull Requests と呼ばれる素晴らしいコードレビューの仕組みがある。多くの人々はオープンソースでの活動 ― プロジェクトをフォークする、プロジェクトを更新する、メンテナーにプルリクエストを送る ― にそれを使っている。しかし、プルリクエストは内部コードレビューの仕組みとして簡単に利用することもできて、我々はそうしている。
実際、我々はプルリクエストよりもブランチでの会話ビューとしてもっとPull Requestsを使っている。GitHub上の1つのプロジェクト(パブリックまたはプライベート)において、あるブランチから他のブランチへプルリクエストを送ることができるので、「これをマージしてほしい」に加えて「これに助けやレビューを必要としているんだ」と言うのにプルリクエストを使うことができる。
JoshがBrianにレビューのためにCCして、Brianが何行かあるコードの1行へのアドバイスと共にコメントしたのが分かるだろう。さらにその下ではJoshがBrianの懸念に同意して、それらに取り組むためより多くのコードをプッシュしたことが分かる。
結局、我々はまだ試行フェーズ ― これはまだデプロイの準備ができたブランチではないということ ― におり、我々は実際にデプロイのために master
にマージしたいと思うよりずっと前からコードをレビューするためにPull Requestsを使っている。
もしあなたが機能やブランチの進捗で嵌っていて助けやアドバイスが必要なら、またはもしあなたが開発者であなたの作業のレビューをしてくれるデザイナーが必要なら、あるいはたとえあなたがほとんどまたは全くコードを持っていないがスクリーンショットや一般的なアイディアがあるなら、プルリクエストをオープンするのだ。GitHubのシステム上で @ユーザ名 を追加することで人々をccすることができるので、もし特定の人のレビューやフィードバックが欲しいなら、(上でJoshがやったのを見たように)プルリクエスト・メッセージ内で単純に彼らにccすればいいのだ。
プルリクエストの機能によりunified diffや1コミット、またはプルリクエスト自体の各行にコメントを入れられて、インラインの全てを1つの会話ビューに持ってこられるので、これはクールだ。それはまたブランチにプッシュし続けられるので、もしあなたが何かをやり忘れたりコードにバグがあると誰かがコメントすれば、あなたはそれを修正してブランチにプッシュし、GitHubが会話ビューに新しいコミットを表示して、こんな風にブランチに繰り返していられるのだ。
もしブランチがあまりに長くオープンになっていて、 master
ブランチと同期しないようになってきたと感じたら、あなたのトピック・ブランチを master
にマージして進み続けよう。ブランチが master
に最後に更新したのがいつか、プルリクエストの議論やコミットリストで簡単に分かる。
ブランチですべてが本当に完了し、もうデプロイしてもいい頃だと感じた時、次のステップに進むことができる。
我々は単純に master
で直接作業したり、トピック・ブランチで作業して完了したと思ったときにマージしたりはしない ― 我々は会社にいる他の誰かに締めくくってもらおうとする。これは一般に +1 や絵文字、 ":shipit:" コメントであるが、我々はは他の誰かにこれを見てもらうようにするのだ。
一度我々がこれをキメ、ブランチがCIをパスすると、我々はデプロイのためにこれをmasterにマージすることができ、それをプッシュしたときに自動的にプルリクエストをクローズする。
最後に、あなたの作業は完了しmasterブランチにマージされる。これは、たとえあなたがいまデプロイしなくとも、これを基に人々が新しい作業のベースにして、新しいデプロイが、これは数時間のうちに起こりそうであるが、押し出される。よって他の誰かにあなたの書いたものが壊してしまうような何かを本当にプッシュしてほしくないので、マージされた時には人々はそれが本当に安定していることを確かめたくなるし、彼らの変更をプッシュしたくなる。
我々のcampfire bot、hubotは、従業員の何でもをデプロイできるので、簡単な:
hubot depoy github to production
がコードをデプロイし、ダウンタイムなしで必要なすべてのプロセスをリスタートする。これがGitHubでどれだけありふれたものか分かる:
1日に約24時間、6人の異なる人々(サポート担当やデザイナーを含む)がデプロイしていることが分かるだろう。
私はこれを、1行の変更を含む1つのコミットでもって複数のブランチに行ったことがある。プロセスはシンプルで一本道、拡張性があって力強い。あなたはそれを、フィーチャー・ブランチに2週間かかる50コミットでやることことも、10分かかる1コミットでやることもできる。こんなにも単純で摩擦の無いプロセスなので、1コミットでさえ、これは変更がとても小さかったり大したことがないので問題にならない限りは、人々がめったにプロセスをスキップしたり迂回したりしようとしないことを意味するが、それをしなければならないか悩まなくてよい。
これは信じられないほど簡単でパワフルなプロセスだ。GitHubがとても安定したプラットフォームを持ち、もしイシューが起票されたならそれらは速やかに対処され、新しい機能は迅速なペースで導入されるということに、ほとんどの人々が同意してくれるものと考えている。我々はさらなるスピードや単純さ、より少ないプロセスが得られるように、品質や安定性への妥協がない。
Git itself is fairly complex to understand, making the workflow that you use with it more complex than necessary is simply adding more mental overhead to everybody’s day. I would always advocate using the simplest possible system that will work for your team and doing so until it doesn’t work anymore and then adding complexity only as absolutely needed.
For teams that have to do formal releases on a longer term interval (a few weeks to a few months between releases), and be able to do hot-fixes and maintenance branches and other things that arise from shipping so infrequently, git-flow makes sense and I would highly advocate it’s use.
For teams that have set up a culture of shipping, who push to production every day, who are constantly testing and deploying, I would advocate picking something simpler like GitHub Flow.