背景
9/5に開催された弊社ミライトデザインが運営しているペチオブ勉強会に参加しました。
みんなで話し合ったGit運用の中のブランチ戦略パターン(Git Flow, GitHub Flow, GitLab Flow, Trunk Based Development)について勉強して、個人的に情報を整理したいなと思ったので記事にしました。
イベントの動画や議事録もconnpassのリンクから確認できます。
PR
弊社ミライトデザインでは、他にも様々なお役立ち記事や動画、勉強会を公開しています。よかったらミライトデザインOrganizationページ|Qiita、MIRAITO CHANNEL|YouTube、ペチオブ|connpassも覗いてみてください!
また、ミライトデザインでは一緒に働く仲間も募集しています!興味をお持ちいただける方は、こちらまで!
Git、GitHub、GitHub Actionsシリーズ記事のまとめ
目的
ブランチをどう運用するのかはチームごとに異なります。
数人のチームなのか、数十人のチームなのか規模によっても最適な運用ルールは変わります。
何が私たちにとって最適なのか、みんなで考えたいと思います。
他のチームではどういった運用をされてるのか知りたいのでぜひコメントしてください。
ブランチ戦略とは
Gitのブランチを何のために、どういうタイミングでいつ何のためにマージするのかを考えるのがブランチ戦略です。
コラム: main
と master
2021年6月7日以降に作成されるGitリポジトリは、デフォルトブランチ名が master
から main
に変わりました。
Gitのブランチ名のデフォルトが master
でした。この用語は一部の人々から不快になるため名称が main
に変更となりました。
GitHub Flow
まずはシンプルなブランチ戦略なGitHub Flowから紹介します。
-
main
ブランチ -
feature
ブランチ
main
ブランチから feature
ブランチを作成し、開発が完了したら main
ブランチへPull Requestを作ってマージする。
main
ブランチは常にデプロイ可能な状態に保ちます。
main
ブランチへ直接コミットするのではなく、新しい機能や修正を feature
ブランチで行ったらPull Requestを作成して main
ブランチへマージします。
作業が完了したらプルリクエストを作成し、コードレビューとフィードバックを行い、承認されたら main
ブランチにマージします。
メリット
GitHub Flowのメリットはとにかくシンプルさ。
feature
ブランチの寿命が短く、頻繁に main
ブランチにマージされるため大きなコンフリクトの発生する可能性が減ります。
シンプルな構成のためCI/CD(継続的インテグレーション/継続的デプロイメント)ツールと連携しやすく自動テストや自動デプロイが実行されるため、開発プロセス全体の効率が向上します。
主に小規模なチームやスタートアップな開発に適しています。
デメリット
長期間の開発が必要な大規模なプロジェクトや複数のリリースを並行して進める必要がある場合、GitHub Flowは適していません。
main
ブランチが常に最新の安定版であることを前提としているため、大規模な新機能やリファクタリングを進行中の状態で保持することが難しいです。
また開発環境、ステージング環境、本番環境など複数の環境を使い分ける場合はブランチ戦略がシンプルすぎるため環境ごとのデプロイ管理が煩雑になる可能性があります。
未完成の機能をデプロイしないようにするため、機能フラグや環境ごとに分岐する等を行う場合はこれを管理する手間になる可能性があります。
開発規模が大きくなる場合は何らかの対応を行う必要があるブランチ戦略です。
もしくは次に紹介するGit Flowの導入を検討してみましょう。
個人的な感想
GitHub Flowは個人的に一番好きなフローではあるが、実務で使用するには少しシンプル過ぎる。小規模開発にはとてもオススメです。
プロジェクト開始時はこの運用で進めて、規模が大きくなってきたら develop
ブランチを導入する、もしくは環境が増えたらGitLab Flowへ移行すると良いと思います。
コラム: コミットのメッセージと粒度
- 1 Commit は1つの意味あるまとまりであるべき
- 差分が大きくなっても意味があるまとまりなら問題なし
- 意味の異なる変更が含まれる場合はタスクを分けるべき
- 1 Issue、1 Pull Request、1 Commit が理想
- 1 Issue 1 Commit となるようなタスクの切り方が大事
- タスク管理ツールによっては特定のブランチ名やコミットメッセージで紐付けができるので活用したい
INVESTの原則
Issue を適切な大きさで切り出すためにはスクラムのガイドラインであるINVESTの原則を参考にすると良いです。
頭 | 単語 | 意味 |
---|---|---|
I | Independent | 独立している |
N | Negotiable | 交渉可能 |
V | Valuable | 価値がある |
E | Estimatable | 見積もり可能 |
S | Small | 小さい |
T | Testable | テスト可能 |
1つのIssueが大きくなると1 Pull Requestで大量の差分が発生します。
そうなるとレビュワーに負担がかかり、コンフリクトの可能性も高まり、コードレビューを効率よく進めることができません。
このINVEST原則を守ることでチームはより効果的に作業を進め、柔軟に対応して開発を進めることができます。
Git Flow
Git Flowは5種類(main
, hotfix
, release
, develop
, feature
)のブランチを運用するブランチ戦略です。
2010年に提唱された有名なブランチ戦略です。
オンラインサービスのように継続的デリバリーするコードを想定して作られた戦略ではないです。
-
main
ブランチ- 常にリリースできる状態を保つ
-
hotfix
,develop
へ切り出す - このブランチへの直pushはNG
-
hotfix
ブランチ- バグ修正など緊急時に対応するためのブランチ
-
main
ブランチから分岐する -
main
とdevelop
ブランチへPull Requestを作成してマージする - マージ後はブランチを削除する
- このブランチへの直pushはOK
-
release
ブランチ- リリースに向けた最終的な調整を行うブランチ
- バグ修正、ドキュメントの更新、バージョン番号のインクリメント等
- 主に安定化を目的としているため、新機能の追加は行われません
- 通常はバージョン番号をブランチ名に付けます(例:
release/1.0.0
)
-
develop
ブランチから分岐する -
main
とdevelop
ブランチへPull Requestを作成してマージする- リリースブランチが作成された段階でリリースの準備がすでに整っており、特に追加の修正や調整が不要な場合は
develop
ブランチと差分がないためマージしない場合もあります
- リリースブランチが作成された段階でリリースの準備がすでに整っており、特に追加の修正や調整が不要な場合は
- マージ後はブランチを削除する
- このブランチへの直pushはOK
- リリースに向けた最終的な調整を行うブランチ
-
develop
ブランチ- 開発用のブランチ
-
main
ブランチから分岐する -
feature
,release
ブランチへ切り出す - マージ後はブランチを削除しない
- このブランチへの直pushはNG
-
feature
ブランチ-
feature
は英語で「機能」や「特徴」を意味し、新しい機能やバグ改修を実装するために用いられる -
develop
ブランチから分岐する -
develop
ブランチへPull Requestを作成してマージする - マージ後はブランチを削除する
- このブランチへの直pushはOK
-
-
support
ブランチ- 旧版をサポートするためのブランチ
- オプションなので使用しない場合が多い
メリット
-
main
は常にリリース可能な状態を保てるため、品質が保証される - 原則、
release
ブランチからマージされるため、履歴がシンプルで追跡しやすい - リリース準備中も
develop
ブランチでの開発を並行して行えるため、意図せず開発中の機能をリリースしてしまうリスクが減る
デメリット
- 学習コストが高い
- ブランチ運用のルールが多く、初心者や小規模チームにとっては理解と実践に時間がかかる
- リリースブランチの長期維持の負担
- 長期間にわたるリリースブランチの維持は、
develop
ブランチとの同期が必要となり、管理が煩雑になる可能性がある
- 長期間にわたるリリースブランチの維持は、
- マージをする人の負担が大きい
-
feature
ブランチが大きくなりがちなため、大きい差分を確認する負担が大きい
-
- 複数リリースの並行管理が難しい
- 複数のリリースが並行して進行する場合、Git Flowではリリースブランチ間の管理が難しい
- 複数のブランチからのマージが発生する
- 特にdevelopブランチは
feature
,release
,hotfix
からマージされる - CI/CDの実行時間が長くなり、GitHub Actionsなど無料利用枠を超えるとジョブの実行時間により課金される可能性が増える
- 設定が複雑になり、設定ミスの可能性が増える
- 特にdevelopブランチは
-
feature
ブランチが長期間生存することになるため、ビッグバンマージが発生しやすく、コンフリクトになる可能性も高い
提唱者の Vincent Driessen さんが 2020年に書かれた反省のメモに書かれています。
Web アプリは通常、ロールバックされることなく継続的に配信され、実際に実行されているソフトウェアの複数のバージョンをサポートする必要がありません。
Git Flow をチームに無理やり押し込もうとするのではなく、はるかに単純なワークフロー (GitHub Flow など) を採用することをお勧めします。
ただし、明示的にバージョン管理されたソフトウェアを構築している場合、またはソフトウェアの複数のバージョンをサポートする必要がある場合は、過去 10 年間の人々にとってそうであったように、Git Flow は今でもチームに適している可能性があります。
個人的な感想
プロジェクトでよくこの形を見かけはするが、これ通りに運用されてるのはあまり見ない。
hotfixブランチも結局 develop
→ release
→ main
とマージしてるのも見るし、release
ブランチを作らず develop
→ main
とマージするのも見る。
毎日デプロイするとか、リリースサイクルが早いプロジェクトではさすがにしんどいですね。週1でもデプロイコストがなかなか高い気がします。
Vincent Driessen さんの反省メモにも書かれている通り、WebアプリにおいてGit Flowはオーバースペックになる場合があるため、導入する際はチームに適した形を一度検討すると良いでしょう。
GitLab Flow
GitLab Flowは「GitHub Flow + 各環境用のブランチ」という感じです。
-
main
ブランチ- 常にリリースできる状態を保つ
- このブランチへの直pushはNG
- 各環境 ブランチ
-
staging
,production
など各環境に対応したブランチを作成する - マージ後はブランチを削除しない
-
- feature ブランチ
- 新しい機能や特定の作業を進めるために使用するブランチ
-
main
ブランチから分岐する -
main
ブランチへPull Requestを作成してマージする - マージ後はブランチを削除する
メリット
- 環境ごとのブランチ運用が可能
- 明確に分けることでデプロイやテストがしやすくなる
デメリット
- 環境ごとにブランチがあるため、運用がしっかりしていないと混乱の恐れがある
Trunk Based Development
https://trunkbaseddevelopment.com
https://www.atlassian.com/ja/continuous-delivery/continuous-integration/trunk-based-development
-
main
ブランチ- まさにメインで開発を進めるブランチ。トランクベース開発では直pushもOKとする
- コミットがpushされたら、自動ビルド&テスト
-
feature
ブランチ- 新しい機能や特定の作業を進めるために使用するブランチ
- このブランチへの直pushはOK
- コミットがpushされたら、自動ビルド&テスト
-
main
ブランチから分岐する -
main
ブランチへPull Requestを作成してマージする- ペアプロ、モブプロを行ってコードレビュー済みであればPR作らずマージしても良い
- できれば1日以内にマージする。最長でも3日以内にマージする制約がある
- マージ後はブランチを削除する
-
release
ブランチ- リリースのためのブランチ
- Git Flowの
hotfix
ブランチの代わり
- Git Flowの
-
main
ブランチから分岐する -
release
ブランチにコミット、マージは行わない- バグ修正は
main
ブランチにコミットを積む-
release
ブランチで行ってしまうとmain
ブランチへのマージバックが忘れやすいため
-
- 必要なコミットは
cherry-pick
でrelease
ブランチに持ってくる
- バグ修正は
- リリースのためのブランチ
トランクベース開発はトランク(main
ブランチ)へPull Requestを使わずにどんどんコミットしていこうという大胆な戦略です。
チーム人数が増えると現実的じゃないのでその時は feature
ブランチを作ってPull Requestを使ってマージします。
(この場合はGitHub Flowに近い運用です)
ただ、この場合においてもPull Requestが作成されてからマージされるまで基本的に1日で行います。長くとも3日以内に行う制約があります。
長期間生存するブランチは main
ブランチのみです。
とにかく feature
ブランチの生存期間をできるだけ短くして、ソースコードの大規模なマージを避けて、コンフリクトを減らし、開発スピードを上げることが目的になっている戦略です。
想像してみましょう。半年、数年かけて育てた feature
ブランチをリリースが近づき、develop
にマージするタイミングで発生する大量のコンフリクト...
もし手作業で数百ファイルに及ぶコンフリクトを解消するとなると解消の規模、工数の予測ができなくなる恐怖😱
長期の開発中に他のブランチでリファクタリングなどが行われているとこのようなことが発生します。
トランクベース開発で main
に積極的にマージを行うことで、多少のミスは発生しやすくなります。
マージの規模は最小限で済むため、大きい問題が発生する可能性は低くなります。
自動テスト
トランクベース開発は自動テストが前提のブランチ戦略です。
開発者はプロダクションコードとテストコードを合わせてコミットします。
CIでエラーが発生したら開発を即止めて修正を行います。
コードレビュー
トランクベース開発はコードレビューのプロセスが重要です。
- レビュープロセスを重くしない
- 複数の機能をまとめない
- 機能追加とリファクタを同時に行わない
- etc
- マージ前にレビューする場合は、依頼を受けたら即対応する
- 1日でマージするため依頼があれば即対応する
- 自動テストが運用できていればマージした後にレビューしても良い
- CIでエラーが発生したら即対応するため安心してマージして良い
- あとからリファクタリングで良い
- ペアプロ、モブプロ
- ペアプロで開発しているのであればその場でレビュー済みであるため、
main
ブランチへ直接pushでも良い - どのみちCIでエラーが出たら即対応する
- ペアプロで開発しているのであればその場でレビュー済みであるため、
- AIコードレビューを活用する
- レビューの負担を軽減できる
- AIが必ず正しい訳ではないので、最終的には開発者が取捨選択する
- AIレビューサービス
デプロイとリリースの分離
ビジネスの判断により、この日までは機能を見せたくないといった場合はもちろん発生します。
トランクベース開発は main
ブランチにマージするため、デプロイとともに開発中の機能がリリースされてしまう問題があります。
コード自体は本番環境へデプロイするが、リリースは行わないようにします。
- API先行開発
- APIの部分だけ先行で開発を進めてリリース日までは画面の実装は進めない
- DIによる差し替え
- DI(依存性の注入)を利用してリリース日まではモックが動くようにします
- フィーチャーフラグ(機能フラグ)
- フィーチャーフラグを導入して機能のオン・オフが行えるようにする
- フィーチャーフラグサービス
- AWS AppConfig
-
Laravel Pennant
- Laravelエンジニアだったのでご紹介してます
つまり、開発環境、検証環境、本番環境へは同じコードがデプロイされていて、フィーチャーフラグを用いて必要な環境で機能を個別に有効化していくという考えになります。 ※すべての環境へ同時にデプロイするという訳ではない。
リリース
トランクベース開発ではリリースするタイミングで、main
ブランチから release
ブランチを切り出します。
Git Flowにおける hotfix
ブランチの代わりとなります。
main
ブランチはリリース準備中もどんどんマージが進むため、リリースするタイミングでは開発中のコミットを含めたくないです。
release
ブランチではコミット、及びマージは行いません。
バグ修正のコミットは main
ブランチへpushします。
必要なコミットはマージではなく、cherry-pick
で release
ブランチへ取り込みます。
release
ブランチでバグ修正コミットを行うと main
ブランチへのマージバックが忘れやすくなるためです。
release
ブランチを切り出す時は最新の main
ブランチではなく、安定してるコミットから切り出しても良いです。
release
ブランチ上で手動テスト、検証を行い、問題なければデプロイします。
テスト
- ユニットテストをデータベースなどにボトルネックになる外部コンポーネントには依存させない
- テストをステートレスにしてコンテナ環境で並列実行させる
- テストグループを作成してローカルで関連テストのみ実行して開発者の負担を減らす
コラム: 継続的インテグレーションとは?
継続的インテグレーションとは?
https://aws.amazon.com/jp/devops/continuous-integration/
継続的インテグレーションは、開発者が自分のコード変更を定期的にセントラルリポジトリにマージし、その後に自動化されたビルドとテストを実行する DevOps ソフトウェア開発の手法です。
main
ブランチへマージされた直後に行われる自動ビルド&テストのことを継続的インテグレーションと呼び、feature
ブランチを用いて、自動ビルド&テストすることは継続的インテグレーションとは呼ばれません。
- CB: 継続的ビルド(Continuous Build)
- コードをpushしたら自動的にビルド&テスト
- CI: 継続的インテグレーション(Continuous Integration)
- コードを
main
ブランチにマージしたら自動的にビルド&テスト
- コードを
- CD: 継続的デリバリー(Continuous Deliver)
- 継続的インテグレーションのあと自動的にテスト環境または運用環境 (あるいはその両方) にデプロイ
コラム: なぜブランチ戦略が必要なのか?
ブランチ戦略を導入することでメリットがあります。
- 作業の並行化
- 本番用のコードに影響を与えず、並行して開発を進められる
- ブランチ命名ルールの統一
- ブランチ名から目的の意図が汲み取れるためブランチの内容理解が短縮する
- オンボーディングの効率化
- ルールに沿ったブランチ戦略が運用されていることで新規参入メンバーが迅速に作業に取り掛かれる
- リリースタイミングの調整
- リリースのためのブランチと開発のためのブランチなど目的ごとに用意することで開発が効率良く進められる
- コードレビュー
- プルリクエストを利用してブランチをマージするのでチーム内でコードレビューを行う機会を提供できます
- コードの品質が向上し、チーム内の知識共有が促進される
- CI/CD
- ルールに沿って運用されることで自動化スクリプトやCI/CDツールの導入が容易になる
コラム: ブランチの名称について
-
main
ブランチ- 歴史的な経緯から
master
,trunk
と呼ばれることがあります -
Trunk(トランク)はSubversion時代の
main
ブランチの呼び名です
- 歴史的な経緯から
-
feature
ブランチ-
topic
ブランチとも呼ばれることがあります
-
コラム: デプロイタイミングについて
そのプロジェクトによるし、ブランチ戦略によるし、色々あるとは思いますが、個人的なおすすめとしては本番環境はタグを付けたタイミングで自動デプロイをお勧めします。
プロジェクトによってどんな環境が用意されるか様々...呼び方も様々...予算も様々...ありますが、Git Flowの場合の例をご紹介します。
ローカル環境、開発環境、評価環境、ステージング環境、本番環境の5つあるケースです。
- ローカル環境 (Local Environment)
- 用途: 各開発者が個別に使用する環境です
- タイミング: 各開発者が自由にローカルブランチを切り替えます
- 開発環境 (Local Environment)
- 用途: チーム全体での開発作業を行うための共有環境です。ローカルで開発された機能を統合し、全体の動作確認を行います。コードレビューを行ったり、CIを実行する環境として使われたりします。チームで取り合いにならないようにチーム数の数だけ環境があると良いです
-
タイミング:
develop
ブランチにマージされたタイミングで自動デプロイします。また、feature
ブランチを一時的にデプロイする場合もあります
- 評価環境 (Testing/QA Environment)
- 用途: テスト担当者が機能の動作確認やバグの検出を行う環境です。統合テスト、システムテストなどが行われます
-
タイミング:
main
ブランチにマージされたタイミングで自動デプロイします
- ステージング環境
- 用途: 本番リリース前の最終確認を行う環境です。ここでは実際の本番環境に極めて近い設定(データ件数等も揃えるため本番データを個人情報部分をマスキングして本番になるべく合わせる)で、リリースするコードが問題ないかの最終確認をします。ステークホルダーによる承認や、受け入れテストもここで行います
-
タイミング: GitHubのリリース機能の
pre-release
のタイミングで自動デプロイ
- 本番環境
- 用途: 本番環境!
-
タイミング: GitHubのリリース機能の
release
のタイミングで自動デプロイ
こんな感じの環境が理想なのですが、これだけの環境を用意するにはそれなりの規模、それなりの予算がある案件でないと実現難しそうです。これまでは本番環境しかない案件もありましたし、開発環境が5つも用意されている潤沢な案件もありました。
コラム: Gitクライアントツールは何を使うのがいい?
Gitクライアントツールは何を使いますか?
コマンド(CUI)、TUI(ターミナル)、画面(GUI)。
これは個人の話なので好きなものを使えばいいと思います。
私はCUIとlazygit というTUIを併用しています。
CUIだとファイル差分を確認したり、部分的にステージングしたい時に不便なのでステージングの上げ下げする時にTUIを使っています。
CUIを使うメリットとしては、他の人に共有したり説明しやすいからです。
また、GUIをインストールできない環境もあるので普段からコマンドに慣れておきたいというのもあります。
Gitクライアントツールはたくさんあるのでお気に入りのツールを見つけましょう。
https://git-scm.com/downloads/guis
GUIだとGitUpとか好きです。
コラム: マージとリベースはどちらを使うのがいい?
結論: どちらも使うので使い分けが必要です。
マージとリベースの説明は省きます。
マージのデメリットとしてはコミット履歴が時系列通りに並ぶため、複数のブランチがマージされた後に履歴を遡ると読みづらい。
リベースのデメリットとしてはコミット履歴が書き換わるので、複数人が触るブランチでは使用できない。
リベースは個人の作業ブランチのみで利用し、マージはそれ以外の時に利用する。
個人の作業ブランチにマージコミットが増えると読みづらいため。
リベースを行うタイミングとしては次の2回で良いと思います。
- Pull Requestを作成してレビュー依頼を行う前
- Pull Requestのレビューが完了(Approveもらった後)し、マージする前
- Approve後のリベースはチームの運用によっては許可されない場合もある
レビュー途中にリベースは行わないこと。コミット履歴が変更されるとレビュワーが困るため。こうしてマージするとコミットがきれいに並ぶため、コミット履歴を追いやすくなります。
コラム: GitHubのマージボタンはどれを使うべき?
Pull Requestのマージは3種類あります。
https://docs.github.com/ja/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/merging-a-pull-request
-
Create a merge commit
- マージコミットが作られる
- コミットはそのまま
-
Squash and merge
- マージコミットは作られない
- コミットが1つにまとめられる
-
Rebase and merge
- マージコミットは作られない
- コミットはそのまま
Create a merge commit
ボタンで良いと思ってます。
マージコミットがあるとPR単位で revert
しやすいからです。
Squash and merge
コミットが一つにまとめられると、まとめる前の状態に戻せないところが注意点ですね。
コミットが一つなため、 revert
しやすい利点はある。
Squash コンフリクト 問題も起こる。
Rebase and merge
マージコミットがないため、PR単位での revert
が難しくなる。
コミット履歴が綺麗になるメリットはあるが、デメリットの方が大きいので選択しなくて良さそう。
さいごに
ブランチ戦略は何を使用すればいいのか?
機能フラグの導入や、しっかりしたCI/CDを組み、開発スピードに重きを置くのであれば Trunk Based Development なのかなと思います。
次におすすめするのはGitHub Flowです。シンプルなのが良いですね。
その後、開発進んでデプロイ環境が増えてきたらGitLab Flowに移行すると良いと思います。
大規模な開発だったりリリース管理が重要なプロジェクトであればGit Flowをおすすめします。