読者です 読者をやめる 読者になる 読者になる

その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

最近のgitを使った開発フローについて

最近のgitを使ったWebアプリケーションのプロジェクトの開発フロー (主にブランチ運用) について記すものです.
なお前提としてGitHub Enterpriseを利用しています.

git-flow

大上段に構えたもののあまり特殊なことはしていなくて,基本的にgit-flowをそのまま踏襲しています.
git-flowについてはしっかりした解説記事がインターネット上に数多く存在しますからそれらを参考にしていただければと思いますが,ざっくり説明すると

  • masterブランチ,developブランチ,releaseブランチ,featureブランチ及びhotfixブランチがある
  • masterブランチは常にリリース可能な状態になっている (すなわち現在本番で稼働しているアプリケーションのコードと等しい)
  • developブランチは開発中の状態で,ステージング環境等に上がっている
  • releaseブランチはリリース準備の為のブランチで,developから派生しmasterにマージされる
  • featureブランチはdevelopブランチから切られるブランチで,新機能やバグ修正などが実装される
  • hotfixブランチはmasterブランチから切られるブランチで,緊急で修正しなければならないバグ修正などが実装される
  • 開発中はfeatureブランチがdevelopブランチにマージされる
  • 通常のリリースではdevelopブランチをmasterブランチにマージして,そのmasterブランチをデプロイする
  • hotfixリリースではhotfixブランチをmasterブランチにマージして,そのmasterブランチをデプロイする
    • hotfixブランチの内容は何らかの方法でdevelopブランチに反映する

という感じです (間違ってたら教えてください).

実際にそれぞれのパターンで或るブランチから或るブランチにマージする際はpull requestを発行し,それを経由してマージするという感じでやっています.
この時featureブランチをdevelopブランチにマージする場合や,hotfixブランチをmasterブランチにマージする場合はコードレビューを行い,適切なフィードバックや修正が行われてからマージされるという流れになっています.

なぜgit-flowのようになったかというと,developブランチが複数走るという状況が日常化してきたためです (後述).複数のコンポーネントが絡んでくるとリリース時期を調整したりなんなりをフレキシブルにやる必要があってですね……

実際のブランチ構成

  • master (1本)
  • develop (複数本, 後述)
  • release (複数本,後述)
  • backport (複数本,後述)
  • feature (複数本)
  • fix (複数本)
  • hotfix (複数本)

といった感じで存在しています.

featureとfixについては上記のgit-flowの説明的には「featureブランチ」にあたるものですが,それぞれ「新機能の実装」と「バグの修正」とで役割に応じてブランチを分けることで見通しを良くしています.
release, backportブランチについては後述.

ブランチの命名規則

ブランチの命名規則としては

  • develop/x.x.x (x.x.xはバージョン番号)
  • feature/<description>
  • fix/<description>
  • hotfix/<description>

という風に定めて運用しています.
かつてはfeature, fix及びhotfixブランチの<description>部分は詳細に記述すべきだとして,詳しく説明を書いていたのですが最近ではそこまで詳細じゃなくて良いかな〜という気持ちになっています.そもそもマージする時はpull requestを作るのでそのpull requestのタイトルでその内容が把握できるし,branch名長いとタイプする時とかめんどいやん? みたいな感じになったためです.
あとJIRAにGitHubプラグインが入っているとfeature/XXX-123のような風にJIRAのチケット番号をブランチ名に含めた上でpull requestを発行すると自動でJIRA側にトラッキングされて便利というのもあり,JIRAのチケット番号をブランチ名に含めることも多くなってきました.

リリース時

リリース時はdevelopブランチからrelease/x.x.x (x.x.xにはバージョン名が入る) というブランチを切り,そのreleaseブランチをmasterに向けたpull requestを出すというスタイルでやっています.
いちいちreleaseブランチを切る理由としてはreleaseブランチが切られた時点のスナップショットがmasterに取り込まれる事をはっきりさせたかったのと,リリース処理に係るコミット (例えばメタ情報が書かれているファイル中のバージョンを書き換えるなど) をdevelopに含めたくなかったという理由からです.
リリース後はmasterブランチから新たなdevelopブランチを作成し,そちらを今後の開発用ブランチにするという感じにしています.

hotfixリリース

hotfixリリースを行なう際,git-flowではmasterブランチから直接hotfixブランチを作成し,そのブランチ上で修正してmasterにマージしてリリースという流れが示されています.基本的にはこの方法でhotfixリリースを行なうことが多いです.
しかし一方で一旦ステージングで確認してからmasterに反映してhotfixリリースを行いたい場合もままあります.そうした時は

  1. developブランチからfixブランチを作成
  2. fixブランチ上で修正してdevelopブランチに向けてpull request作成 & マージ
  3. ステージング環境で確認
  4. masterからhotfixブランチ作成
  5. hotfixブランチ上で,fixブランチのcommitをcherry-pick
  6. あとは通常のhotfix flowに従う

という流れで対応しています.これで対応できない場合がまれにあるので (cherry-pick時にconflictしたり……) そういった場合は気合で対応しています.

なお,hotfix後にdevelopブランチへその内容を反映させる際は,masterブランチからbackport/x.x.xというブランチを作成し、それを対象developブランチへと向けたpull requestを作成し,マージするということでhotfix内容の反映・同期を行っています.

developブランチが複数存在していて並行に開発している場合

さて,プロジェクトがある程度の規模になってくるとdevelopブランチが複数走る状況になることがあると思います.実際に我々のプロジェクトについてもdevelopブランチが複数本存在する状況がままあるので,そのような時にどのように運用しているのかという事について以下に記します *1

複数のdevelopブランチが存在している場合はそれぞれのブランチでは独立してコミットを積んでゆき,可能であればそれぞれのdevelopブランチと対応するステージング環境を用意して開発を進めていきます.
それぞれのdevelopブランチ間での同期については,いずれかのdevelopブランチがリリースされた (つまりmasterにマージされた) タイミングで行っています.以下のような感じ (develop/1.1.0develop/2.0.0がある場合の例).

  1. develop/1.1.0がmasterに取り込まれる
  2. masterからbackport/1.1.0というブランチを作成する
  3. backport/1.1.0develop/2.0.0に向けたpull requestを作成
  4. Conflictしても泣かない,頑張って直す
  5. そのpull requestをマージして同期完了

とは言え,masterにマージする前にdevelopブランチ間で同期を取りたいこともあるとは思うので,その際はcherry-pickなりmergeなりをマニュアルでやって頑張りましょうという感じです.

hotfix時は上述のhotfixリリースのフローに従いつつ,複数のdevelopブランチにbackportするという流れを採っています.

その他雑感

マージされたブランチはどんどん消しています.リモートにゴミブランチがたまると良くない!

Ref:
moznion.hatenadiary.com

結び

以上のようなフローで最近は開発していますという話でした.
これらがオーバーキルっぽい場合はGitHub flowなども良いと思います.

追記

なぜdevelopブランチが複数あるのかという疑問が散見されたので,我々のケースについて書いておきます.
ざっくり言ってしまえばリリースサイクルの違いです.例えば大きめの新機能を実装しつつ既存コードの保守もするという時,その時点で稼働しているコード (master) から派生したdevelop/A (便宜上Aとします) の上で新機能の実装を入れてしまうと,保守のコードと新機能コードが混ざってしまい,保守のためのデプロイのタイミングで出て欲しくない新機能のコードまで露出してしまうことになるので,それを防ぐ (分離させる) という目的でdevelopを複数に分ける (例えばdevelop/Bで新機能の開発は行なう) というような形にしています.
保守の為のdevelopブランチでも,新機能の為のdevelopでも,どちらも独立したステージング環境では見ておきたいので……

*1:git-flowの解説ではdevelopブランチが複数存在している場合についてあまり言及されてない気がする……