(English version is here)
キンドリル社で開発やインフラ基盤の管理を行っている南波と申します。オープンソースの手法を利用して、社内のサービス管理等のシステムを構築・デプロイ・継続的改善をしています。その中で、gitやgithubの実際の利用から、それぞれに問題がありこまってました。我々の内部的な取り組みですが、課題を解決した状態が一通りまとまり、整理をしましたので、この記事で紹介したいと思います。
git-flowとGithub Flowのそれぞれの問題
github等を用いたソースコード管理やリリース管理の手法を行う際に、有名な2つの手法として「git-flow」と「Github Flow」があります。この章ではgitやgithubの実際の利用から、それぞれに直面した課題を整理します。
当記事は、git-flowとGithub flowの知識を前提にしており、その運用で困っている方を対象にしています。または、github等を用いたソースコード管理やリリース管理の手法を検討している方を対象にしていますので、前提知識については、下記の記事を参考にしたうえで、お読みいただけると助かります。
参考文献
-
git-flowの紹介記事
-
Github Flowの紹介記事
-
類似の課題と対策の記事
git-flowの問題
git-flowでの開発はdevelopブランチを中心に行います。新しい機能はフィーチャーブランチを作成してdevelopにマージで戻すことで、対象プロダクトの品質を向上していきます。リリース向けには、やはりdevelopからリリース用のブランチを分岐して、そこでリリースに向けたアジャストを行った後にmainブランチに対してマージして反映します。
これは、開発全体が一つのリリースに向かって一体となってシンプルに進んでいる場合には問題がありませんが、複数のリリースを管理する際に運用上は次の課題が起きてしまいました。
リリースにいれたくない変更の混入
個々のフィーチャーの開発は、いつでもdevelopからフィーチャーブランチを分岐して行え、マージも制限はありません。これ自体はシンプルなのでわかりやすいのですが。developには、いつのリリースにいれるのか決定してもいないものが随時入ってしまいます。例えば、現在はバージョン1.0だったとして、次のバージョン1.1の次のバージョン1.2に入れようとしていた機能(1.2A)がdevelopの最新に含まれた状態が生じます。
さて、他方、バージョン1.1で必要となる機能(1.1B)のために、新しくdevelopからフィーチャーブランチを分岐しますと、バージョン1.2に入れようとしていた変更の上にバージョン1.1の変更をすることになります。この状態で、バージョン1.1のリリース用ブランチに機能(1.1B)のフィーチャーブランチをマージすると、意図せず、機能(1.2A)が含まれてしまうわけです。
ブランチポイントの不明瞭
上記の混入を避けるためには、リリース1.2A向けの機能を含んだ時点のdevelopブランチからの分岐を避け、それよりも前のブランチからの分岐が必要になります。シンプルだったフィーチャー用ブランチの分岐が面倒になるわけです。どこの時点のdevelopブランチが「安全」なのか調べる手間が開発者に生じます。一般的に「git-flow」の運用は複雑になりがち…という具体的なポイントかもしれません。
リリースへの機能の採用コントロールへの複雑さ
リリースに含める機能(フィーチャー)をいれるかの判断はビジネス上のニーズ等で決まることもあり、開発の技術的視点での管理とは別の理由で発生します。計画通りに進まないのは、人の世の常ですから仕方がありません。
すでに述べたdevelopブランチのマージ状態を前提とすると、純粋にリリースへの必要物を整理するために、rebaseやcherry-pick等を駆使したコントロールと再テストが必要になります。開発全体を見通しての「シンプルさ」を維持できません。
全体として、参考文献[5]でも触れられているようにKISS原則(Keep It Simple Stupid)を守れない残念なバカもの(Stupid)状態を生じてしまいます。
Github Flowの問題
「git-flow」は複数のブランチの目的定義や使い分け、リリースに向けたコントロールが整理されておりますが、定義自体から必然として複雑になります。その代案としての「Github Flow」はシンプルであり、最低限のルールを示しつつニーズを満たす選択肢です。
しかしながら、多くの人数が関わるチームでの開発においては、不分律でのコントロールは成立しません。私が属するグローバル規模の協業開発では日本・アジア・豪・インド・東欧・南北米と多種多様なバックグラウンドの貢献者がいますので、「git-flow」に近しいレベルの整理されたルール(と、それに伴う必要性の範囲での複雑さ)は必要になる認識です。
リリース以外の整合チェック
これはGithub Flowでは「早くリリースをしてデプロイを回して、Fail Fastで課題を刈り取る」というポリシーによるものです。シンプルな機能単位のプロダクトやWeb画面的な対象では良いかもしれませんが、リリースや全体の整合を担保するワークフローの充実は望まれます。参考文献[4]でも、テストの場所の確保が課題としてあげられています。
解決方法
結果、独自で決めた手法を実践しています。2つの良いところ取りをしていますので、git-github-flowと呼んでいます。実際のところ、Github Flowの派生とも言えますが、git-flowの考え方を踏襲しているところもありますので、融合と呼んでいます。実際のところ、参考文献[5]と類似しています。
やりたかったこと
事後での整理になりますが、実際にやりたかったことを整理すると以下になります(要件定義的に)。
- developブランチでの統合は大事なので継続したい
- git-flowのようにきちんと管理したい
- 特にリリースを管理したい
- Github Flowのシンプルさは維持したい
実際の定義
git-flowに対して以下の変更を加えます。作業の流れで記載してますので、変更しない部分も記載します。
- フィーチャー用やリリース用のブランチはdevelopブランチではなくmainブランチの最新から分岐させます(変更部分)
- フィーチャーが完成したら、フィーチャー用ブランチをdevelopにマージします(これまで通りの部分)
- developへのマージはmainブランチと同じく「Pull Request」でゲートして整合性を取ります(これまで通りの部分)
- フィーチャーのリリースへの反映は、フィーチャー用ブランチをリリース用ブランチにマージします。ただし、条件としてdevelopへのマージが成功裏に行われたことを前提にします。(変更部分)
- リリースブランチ上の最新でリリース内容が確認されたら、mainブランチに「Pull Request」でゲートして整合性を取ります(これまで通りの部分)
- リリースブランチの内容をリリースしたら、mainの内容はdevelopにマージします(変更部分)
フローの解説
全体像をgit-flowの記載方法に似せて記載すると以下の図になります。フィーチャー用ブランチの完成時にマージの線がdevelopと、リリース用ブランチの両方に伸びているところが、上記3,4のステップを表しています。
特にリリース1.1.0に入れたい開発(feature AとB)とリリース1.2.0に入れたい開発(feature C)が同時にdevelopブランチにマージされていても、混乱を起こさないで済む状態が分かります。
特に上記4のステップで、developブランチへの成功裏のPull Requestマージ(左側への黄色のコミット)やテスト・確認(破線の間の作業)が行われたことを確認した後に、リリース用ブランチにマージ(右側の緑色のコミット)します。
右側のリリース用ブランチへのマージにPull Requestを必要とするかは、リリースの管理をどこまで厳格に行うか、リリースマネージャーが手動でMergeを行うかで異なる対応になる認識です。
やりたかったことの実現確認
以下、目標に対する対応を以下に記載します。
- 「developブランチでの統合は大事なので継続したい」のため、フィーチャー用ブランチでの変更は全てdevelopに集め、整合性が取れるようにします。
- 「git-flowのようにきちんと管理したい」のため、原則的には「git-flow」の考え方を踏襲してます。
- 「特にリリースを管理したい」のためリリース用ブランチの利用も継続します。フィーチャーの取り込みをリリース用ブランチへのPull Reuestやマージで行います。developブランチからの意図しないフィーチャーの混入を避けます。
- 「Github Flowのシンプルさは維持したい」のため「mainから分岐し、mainに戻る」という「Github Flow」のシンプルさを採用し、何でもマージされているdevelopブランチをリリースに反映しない形にします。
やりたかったことは実現できています。
ワークフローの比較
git-flow, Github Flow, 当記事で紹介のフロー(git-github-flow)を整理すると以下のようになります。
管理フロー | git-flow | Github Flow | git-github-flow |
---|---|---|---|
mainブランチ | プロダクトの正式リリース物 | プロダクトの正式リリース物 | プロダクトの正式リリース物 |
developブランチ | 使用する | 特に定めない | 使用する |
フィーチャー用ブランチ | 使用する | 使用する | 使用する |
リリース用ブランチ(Hotfix用ブランチ含む) | 使用する | 特に定めない | 使用する |
フィーチャー用ブランチの分岐元 | develop | main | main |
フィーチャー用ブランチのマージ先 | develop | main | - develop - リリース用ブランチ |
developブランチへのPull Request必要性 | N/A (github等を前提としていないため) | N/A (使用を定めていないため) | 必要 |
実装結果
当初、git-flowでのプロジェクト内の開発フローを実施していましたが、短い期間で複数のバージョンリリースを実施し、機能とバージョンの組み合わせや入れ替え戦を実施していましたが、前述するような課題を生じていました。
git-flowとGithub Flowの両フローの良いとこ取りをした当定義を使用することで、シンプルでわかりやすい運用に繋がりました。ここでは、実際の実装時に行っている追加の工夫を紹介して終わりにしたいと思います。
実装上の工夫
- developブランチは直接のpushを制限しPull Requestを必須にしました
- developへのPull Requestの依頼時にGithub Actionsにてビルドとテストマシン上へのデプロイが自動で実施されるようにしました
- 新しいrelease毎に共通でコードベースに行う定型変更があるため、mainブランチにリリースのタグを打つタイミングでmainから新ブランチを作成して定型変更のコミットを作り、該当バージョン以降のフィーチャー用ブランチのブランチ分岐ポイントとして明確になるようにしました。
まとめ
git-flowとGithub Flowの両フローの良いとこ取りをしたフローを作り紹介しました。そもそものコードのモジュール分解やタグの打ち方、スクリプトの作り込み等、様々な工夫での対処方法があるとの意見もあるかもしれません。
同じ様な課題に直面されている場合は、単一レポジトリ構成でのシンプルな流れながら、git-flowの考え方を踏襲しつつ、Github FlowやGithubのメリットを活かした方法として、皆様のお役に立てますよう願っております。
多サイト等での紹介の際は、「git-github-flow」の名称とこのサイトへのリンクをお願いできれば…と思います。