Webシステム

Git運用のベストプラクティス|ブランチ戦略・コミット規約・レビュー基準の決め方

Git運用のベストプラクティス|ブランチ戦略・コミット規約・レビュー基準の決め方

Gitの運用ルールは、決めること自体は簡単でも、守られないまま形骸化するのが実際のところです。原因の多くは、ルールが「心がけ」の水準で書かれていて、リポジトリの設定に落ちていないことにあります。この記事では、ブランチ戦略の選定基準、コミットメッセージの書式、プルリクエストの粒度、レビュー基準の4点を、git-flow考案者が2020年に自ら付けた但し書きやDORAの調査結果といった一次情報を根拠に決めていきます。あわせて、決めたルールをGitHubの設定ファイルとRulesetsで強制するところまで具体化します。

まとめ

Git運用で最初に固定すべきは、ブランチ戦略・コミット書式・PRの粒度・レビュー基準の4点です。ブランチ戦略はチームの規模ではなくリリース形態で決まり、複数バージョンを並行サポートしないならgit-flowは過剰です。これは外部の批判ではなく、git-flowを考案したVincent Driessen自身が2020年3月5日に元記事へ追記した判断です。

コミットとPRの品質は、文章で頼むより配布するほうが速く定着します。commit.template で雛形を全員に配り、.github/pull_request_template.md でPR本文の初期値を固定すれば、記載漏れの指摘そのものが減ります。レビュー基準は文書ではなくRulesetsとCODEOWNERSでリポジトリ側に強制し、人間のレビューは設計判断に集中させます。運用が回っているかの判定には、DORAが2016年・2017年のデータ分析から挙げた「アクティブブランチ3本以下」「1日1回以上trunkへマージ」を使えます。

Git運用ルールで先に決める4項目と判断の順序

運用ルールを書き出すと、コミット粒度、ブランチ命名、レビュー人数、マージ方式と論点が際限なく増えます。ただし依存関係があるため、決める順序を間違えると後戻りします。ブランチ戦略が決まらないとマージ方式もリリース手順も決まらないので、必ずここから着手します。

決める順序 項目 判断を決める要因 強制する手段
1 ブランチ戦略 リリース形態・並行サポート版数 Rulesets(対象ブランチ指定)
2 コミット書式 変更履歴の自動生成の要否 commit.template・Rulesets
3 PRの粒度 レビュー所要時間の許容値 PRテンプレート・CI
4 レビュー基準 コードの所有区分 CODEOWNERS・必須チェック

右端の「強制する手段」の列が埋まらない項目は、運用ルールとして書いても守られません。「意図が伝わるコミットメッセージを書く」のような設定で表現できない粒度の記述は、規約本文から削って構いません。Gitの基本操作そのものに不慣れなメンバーがいる場合は、規約とGitコマンド一覧|用途別早見表とよく使う基本コマンドの使い方を解説を併せて配布すると、規約の説明が操作説明に埋もれずに済みます。

ブランチ戦略の選定基準|git-flow・GitHub Flow・トランクベースの適用条件

ブランチ戦略の比較記事は3方式を等価に並べがちですが、実際には適用条件がはっきり分かれています。判断軸はチーム規模ではなく、リリース形態と、市場に出ている版を同時に何本サポートするかです。

git-flowを選ぶ条件と、考案者が2020年に付けた但し書き

git-flowは、develop・release・hotfixを含む複数の長命ブランチを使い分けるモデルです。考案者のVincent Driessenは2020年3月5日、2010年の元記事の冒頭に追記を加えました。そこで示された適用条件は、明示的に版が切られている(explicitly versioned)ソフトウェアであること、または市場に出た複数バージョンを並行してサポートする必要があること、の2つです。

そのうえで、ソフトウェアを継続的にデリバリしているチームに対しては、git-flowを無理に当てはめるのではなくGitHub flowのようなはるかに単純なワークフローを採用するよう勧めています。条件は「継続的デリバリをしているかどうか」であり、Webアプリに限定されてはいません(追記はその代表例としてWebアプリを挙げています)。追記は「万能薬は存在しない、自分たちの文脈を考慮せよ」という趣旨で締められています。SaaSやWebサービスを常時デプロイしているチームがgit-flowを採用する根拠は、考案者本人の記述に照らしても存在しません。逆に、パッケージソフトや組込み、オンプレミス製品のようにv2系とv3系を同時に保守する製品では、git-flowのhotfixブランチが素直に機能します。

GitHub Flowが機能するリリース形態と前提条件

GitHub Flowは、mainから作業ブランチを切り、PRを出し、レビューを通してmainへマージし、そのままデプロイする単純な流れです。長命ブランチはmainだけになります。

単純さの代償として、mainが常にデプロイ可能でなければ成立しません。したがってCIによる自動テストと、マージ前の必須ステータスチェックが前提条件になります。この前提を用意せずにブランチだけGitHub Flowにすると、壊れたmainからのデプロイを人力で止め続けることになり、git-flowより運用負荷が上がります。必須チェックの設定手順はRequire status checks to passとは?GitHubの必須ステータスチェックを設定する方法に、CI自体の組み方はGitHub Actionsとは?できること・使い方とCI/CD自動化の活用例をわかりやすく解説にまとめています。

トランクベース開発の定義と、成立に必要なコミット頻度

トランクベース開発は、trunkと呼ぶ単一ブランチで開発者が協働し、それ以外の長命ブランチを作る圧力に抗するモデルです。短命なフィーチャーブランチはレビューとCIのために許容されます。公式サイトはトランクベース開発を、継続的インテグレーションの中核要件である「チーム全員が24時間に1回以上trunkへコミットする」を満たしやすくするものとして位置づけています。つまり24時間はトランクベース固有の掟ではなく、継続的インテグレーションが成立するための下限です。

DORAは2016年と2017年のデータ分析にもとづき、リポジトリ内のアクティブブランチを3本以下に保つこと、1日1回以上ブランチをtrunkへマージすること、コードフリーズと統合フェーズを持たないこと、これらを実践しているチームはソフトウェアデリバリとオペレーションのパフォーマンスが高い、と結論づけています。この3つは運用ルールの合否ラインとしてそのまま使えます。アクティブブランチが常時10本以上あるなら、名目上どの戦略を採用していても実態は長命ブランチ運用です。

3方式の適用条件の比較

方式 長命ブランチ 適する形態 必須の前提 破綻しやすい条件
git-flow main・develop 版番号付き製品・複数版の並行保守 リリース計画 常時デプロイ
GitHub Flow mainのみ Webサービス・SaaS CIと必須チェック テスト不足
トランクベース trunkのみ 高頻度デプロイ 1日1回のマージ 長期の大改修

ブランチ線で描くと、長命ブランチの本数の差がそのまま運用コストの差になることが分かります。

git-flow          main ----o---------o----  (リリースのみ)
                  develop -o--o--o--o----
                  feature      o--o

GitHub Flow       main ----o----o----o----  (マージ即デプロイ)
                  feature   o--o

トランクベース     trunk ---o-o-o-o-o-o----  (全員が24時間以内に合流)

乗り換えを検討する場面で最も多いのは、git-flowを採用しているのに実態としてreleaseブランチが使われておらず、developとmainが常に同一内容になっているケースです。この状態はGitHub Flowへ移行してdevelopを廃止すると、マージ経路が半分になります。逆に、長期の大規模改修を抱えている時期にトランクベースへ移すと、未完成コードがtrunkに乗り続けます。AWSの規範ガイダンスはこの場面でフィーチャートグルを使い、機能を本番で無効化したままmainの安定性を保つ方法を挙げています。

コミットメッセージ規約の書式と、テンプレート配布による定着

コミットメッセージの規約は、書き方の心得ではなく機械可読な書式として定めると、変更履歴の自動生成やバージョン採番に接続できます。事実上の標準はConventional Commitsです。

Conventional Commits 1.0.0の書式と破壊的変更の示し方

仕様1.0.0が定める構造は次のとおりです。型は必須、スコープは任意、説明は必須で、本文とフッターは空行で区切ります。

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

破壊的変更の示し方は2通り規定されています。ひとつはフッターに BREAKING CHANGE: と書く方法、もうひとつは型またはスコープの直後、コロンの直前に ! を置く方法です。つまり feat!:feat(api)!: はいずれも破壊的変更を表します。この2通りを混在させても仕様上は有効ですが、リリースノート生成ツールの設定を単純に保つため、チーム内ではどちらかに寄せる決めを置くほうが扱いやすくなります。

仕様が意味を定めている型は featfix の2つだけで、それ以外は慣例(Angularの規約に由来する型群)です。運用で使う型は次の程度に絞ると、バージョン採番との対応が崩れません。

用途 採番への影響 仕様上の位置づけ
feat 機能追加 MINOR 仕様が規定
fix バグ修正 PATCH 仕様が規定
docs ドキュメントのみ なし 慣例
refactor 挙動を変えない整理 なし 慣例
test テストの追加・修正 なし 慣例
chore ビルド・依存・雑務 なし 慣例

日本語と英語のどちらで書くかは仕様の範囲外です。型と説明の区切りが機械可読であれば自動処理は成立するため、説明部分を日本語にしても支障はありません。実務では、型は英語の予約語のまま、説明は日本語という組み合わせが最も摩擦が少なくなります。

commit.templateによる雛形の配布

書式を決めても、各自がエディタで白紙から書く限り抜けは出ます。Gitには雛形を初期表示する設定があるので、リポジトリに雛形ファイルを置いて設定を1行入れます。

$ cat .gitmessage
<type>: 要約を50字以内で

# なぜ必要か(背景・課題)
# Refs: #<issue番号>

$ git config commit.template .gitmessage
$ git config --get commit.template
.gitmessage

スコープを使う運用なら1行目を <type>(<scope>): にします。仕様上スコープは任意なので、雛形の側で括弧を必須にすると、スコープを持たない変更で空括弧が残ります。

-m を付けずにコミットするとエディタに雛形が展開され、# 始まりの行はコミット時に除去されます。雛形を一文字も編集せずに保存した場合は Aborting commit; you did not edit the message. と表示されて終了コード1で中断するため、雛形のままのコミットが履歴に混入する心配はありません。この設定はGit 2.50.1で動作を確認していますが、古くからある設定で特定の版に依存しません。issue番号を書く欄を雛形に含めておくと、コミットとIssueの相互リンクが自然に張られます。Issue側の記述粒度はGitHub Issueとは?使い方・書き方とベストプラクティス【2026年版】にまとめています。

なお git config commit.template はローカル設定なので、リポジトリをクローンしただけでは有効になりません。セットアップ手順書に含めるか、初期化スクリプトで設定する必要があります。書式そのものを強制したい場合は、後述のRulesetsでコミットメッセージのパターンを検査する方法があります。

プルリクエストの粒度基準とテンプレートによる記載漏れの防止

プルリクエスト(PR)は、作業ブランチの変更を取り込み先ブランチへマージしてよいかをレビューにかけるための依頼で、差分・コミット・レビューコメントが1画面にまとまります。GitLabではマージリクエストと呼びますが、役割は同じです。PRが大きすぎるとレビューは形式的な承認に変わるので、粒度の基準は感覚ではなくブランチの寿命で置くと運用しやすくなります。

1日以内に閉じるサイズという基準

AWSの規範ガイダンスは、規模の大きい作業には短命ブランチを使うとしたうえで、その寿命を「典型的には1日未満」に保ち、できるだけ早くdevelopまたはmainへマージするよう挙げています。理由も明示されていて、小さく頻繁なマージとレビューのほうが、1回の大きなマージリクエストよりチームが処理しやすいためです。

1日で閉じられないPRは、機能が大きすぎるか、レビュー待ち時間が長いかのどちらかです。前者は変更を分割し、未完成部分をフィーチャートグルで隠して先にマージします。後者はレビュー担当の割り当てが属人化している兆候なので、次節のCODEOWNERSで担当を分散させます。ブランチを頻繁に切り替える運用では作業ディレクトリの退避が負担になりますが、Git Worktree(ワークツリー)とは|使い方・削除(add/remove/prune)とSubmoduleとの違いで扱う複数チェックアウトを使うとstashを介さずに並行作業できます。

pull_request_template.mdの配置場所と複数テンプレートの使い分け

PR本文の初期値はリポジトリ側で固定できます。GitHubが認識する配置場所は、ルート直下の pull_request_template.mddocs/pull_request_template.md.github/pull_request_template.md の3か所です。バグ修正と機能追加でテンプレートを分ける場合は、PULL_REQUEST_TEMPLATE/ サブディレクトリ(ルート・docs/.github/ のいずれの直下でも可)に複数ファイルを置き、PR作成URLの template クエリパラメータで指定します。テンプレートはデフォルトブランチにマージされてから有効になるため、作業ブランチに置いた段階では反映されません。

テンプレートに載せる項目は、レビュアーが判断に使う情報だけに絞ります。変更の背景、影響範囲、確認済みの動作、未確認の残件の4項目があれば足ります。「テスト実施」のようなチェックボックスは、CIで自動判定できるならテンプレートから外してステータスチェックへ移すほうが、形式的なチェックの付け合いを避けられます。

レビュー基準の統一と、リポジトリ設定によるルールの強制

レビュー基準を文書化しただけで統一される、ということはありません。文書は読まれない前提に立ち、機械が判定できる部分をすべてリポジトリ設定に移します。

Rulesetsによるルールの強制とbranch protection ruleとの違い

GitHubにはブランチを保護する仕組みが2系統あります。従来のbranch protection ruleと、後発のRulesetsです。GitHubのドキュメントはbranch protection ruleを非推奨とは記載しておらず、両者は併存して適用されますが、Rulesetsには運用上の差があります。

  • 同一ブランチに複数のrulesetを同時適用できる(branch protection ruleは1本しか適用されない)
  • rulesetを削除せずに適用状態だけを変更できる
  • リポジトリへのread権限があれば誰でも有効なrulesetを閲覧できる
  • コミットメッセージや著者のメールアドレスといったコミットのメタデータを制御できる

実務で効くのは3番目と4番目です。閲覧に管理者権限が要らないため、開発者が「なぜマージできないのか」を自分で確認でき、監査時にも管理権限の貸し出しが不要になります。4番目のコミットメタデータ制御は、前節のConventional Commitsの書式を検査に落とせることを意味します。テンプレート配布が「守りやすくする」施策なのに対し、こちらは「守らないとマージできない」施策で、規約の定着速度が変わります。

ブランチ命名規則も同じ扱いにできます。ruleset編集画面のRestrictionsセクションにある「Restrict branch names」で、feature/*hotfix/* といったパターンに合致しないブランチ名を弾けます。命名規則を規約本文に書いて周知するより、パターンを1本登録するほうが確実です。コミットメッセージの書式を検査したい場合は、同じセクションの「Restrict commit metadata」を使います。

CODEOWNERSによるレビュー担当の割り当て

レビュー基準の統一で最初に壊れるのは、誰がどのコードを見るかの部分です。CODEOWNERSファイルにパスと所有者を書くと、該当パスを変更したPRに自動でレビュアーが割り当てられます。

# 既定の所有者
*                       @org/dev-team

# 領域ごとの所有者
/infra/                 @org/sre
/src/payment/           @org/payment-team
*.sql                   @org/dba

所有者を細分化しすぎると、少人数の領域がレビューのボトルネックになります。まずは既定の所有者を1行置き、レビュー観点が明確に違う領域、たとえばインフラ定義や課金処理、スキーマ変更だけを分けるところから始めるのが現実的です。

機械判定に落とせないレビュー観点の切り分け

レビュー基準が長文になるのは、機械で判定できる項目と人間にしか判定できない項目が同じ文書に混ざっているからです。次の表の「機械化」列が可のものはCIとRulesetsへ移し、レビュー依頼の時点で満たされている前提にします。

観点 見るもの 機械化
設計の妥当性 責務の置き場所・依存の向き 不可
要件との一致 Issueの受入条件との対応 不可
命名とコメント 意図が読み取れるか 不可
失敗時の挙動 異常系・リトライ・ロールバック 一部
テストの妥当性 境界値・異常系の有無 網羅率のみ
後方互換 APIとスキーマの破壊的変更 一部
書式・静的解析 lint・型検査・シークレット

最終行のシークレット混入のように機械が確実に判定できる項目は、gitleaksとは?シークレット検出の仕組みとv8.30時点の使い方・設定のような検出ツールをCIに組み込み、レビュアーの確認項目から外します。上の3行は仕様と設計の理解が要るため自動化できず、ここにレビュー時間を寄せます。「テストの妥当性」を網羅率だけで代替しないことも重要で、カバレッジ100%でも境界値と異常系が入っていないテストは通ります。

rebaseとmergeの使い分けと、コンフリクトを減らす作業分割

マージ方式の議論は好みの問題として扱われがちですが、履歴の形が変わるとリバートの手順とバグ追跡の難易度が変わります。方式ごとの結果を先に確認しておきます。

git rebaseが履歴に対して行う操作

rebaseは、分岐後に自分のブランチで作ったコミットをいったん外し、取り込み先ブランチの先頭に付け直す操作です。mergeがマージコミットを1つ足して2つの履歴を合流させるのに対し、rebaseは元のコミットを捨てて同じ内容の別コミットを作り直すため、コミットIDが変わります。

$ git switch feature/login
$ git rebase main
Rebasing (1/1)
Successfully rebased and updated refs/heads/feature/login.

付け替えの途中でコンフリクトが起きた場合は、解消して git add したうえで git rebase --continue で再開し、やり直すなら git rebase --abort で実行前の状態に戻します。コミットIDが変わるという性質が、後述するpushの扱いと、共有ブランチでrebaseを避ける理由の両方につながります。

ff-only・no-ff・squashが残す履歴の違い

Git 2.50.1で、mainとfeatureブランチが双方に進んだ状態から git merge --ff-only を実行すると、マージは中断して終了コード128を返します。

$ git merge --ff-only feature/login
hint: Diverging branches can't be fast-forwarded, you need to either:
hint:
hint: 	git merge --no-ff
hint:
hint: or:
hint:
hint: 	git rebase
hint:
fatal: Not possible to fast-forward, aborting.

失敗を示すのは末尾の fatal: 行で、その上の hint: 行は代替手段の助言です(助言は advice.diverging を無効にすると表示されません)。ファストフォワードは、取り込む側のブランチが分岐後に一切進んでいない場合にだけ成立します。したがって merge.ff = only を既定にする運用は、マージ前に必ずrebaseまたはpullで最新を取り込む手順とセットでなければ機能しません。

一方 --no-ff は必ずマージコミットを作るため、同じ状況で次のような履歴が残ります。

*   d577882 Merge branch 'feature/login'
|\
| * dbf3a93 feat(auth): ログイン画面を追加
* | 06d28ba docs: READMEを補足
|/
* d895ff1 feat(a): a.txt を追加

マージコミットが残ると、機能単位でのリバートが1コミットで済みます。対してsquashマージは複数コミットを1つに潰すため、mainの履歴はPR単位の直線になりますが、作業過程の粒度は失われます。PRを1日以内のサイズに保っている前提なら、squashで潰しても失う情報は多くありません。逆にPRが大きいチームがsquashを使うと、1コミットの差分が肥大してbisectでの原因特定が難しくなります。

rebase後のpushで使うforce-with-leaseとforce-if-includes

共有ブランチをrebaseすると履歴が書き換わるため、通常のpushは拒否されます。ここで --force を使うと、他人がその間にpushしたコミットを消す事故が起きます。--force-with-lease はリモートの参照が自分の把握しているものと一致する場合にのみ上書きするため、この事故を防げます。

ただし --force-with-lease だけでは足りない場合があります。手元でfetchはしたが取り込んではいない状態だと、リモートの参照が更新済みなので条件を満たしてしまうためです。この抜け道を塞ぐのが --force-if-includes で、Git 2.50.1の git push のマニュアルに記載があります。共有ブランチをrebaseする運用を許すなら、この2つを併用する前提で手順書に書きます。

もっとも、共有ブランチのrebaseを許すこと自体が事故の温床です。rebaseは自分しか触っていない作業ブランチをmainの先頭に付け替える用途に限り、共有ブランチにはmergeを使う、と線を引くほうが手順は単純になります。

コンフリクトを構造で減らす作業分割

コンフリクトは、同じファイルの近い行を複数のブランチが同時に変更したときに発生し、Gitが自動でどちらを採用すべきか判断できない状態です。解決の手順を磨くより、発生条件そのものを減らすほうが効果があります。

最も効くのは、AWSの規範ガイダンスが挙げる「典型的には1日未満」までブランチの寿命を縮めることです。同時変更の窓が1日で閉じれば、同じ行に別の変更が乗る確率そのものが下がります。次に、大きなリファクタリングと機能追加を同じ期間に走らせない順序付けが効きます。最後に、全員が末尾に追記するファイル、たとえば .gitignore、ルーティング定義、環境変数のサンプル、依存関係のロックファイルは、追記位置を末尾に固定するかファイルを分割しておくと、行単位の衝突が構造的に減ります。ロックファイルのように機械生成されるものは、コンフリクト時に手で直さず再生成するルールにしておくほうが安全です。

運用ルールが形骸化していないかの点検指標

運用ルールは導入した時点が最も守られ、そこから緩みます。定期的な見直しを掲げるだけでは緩みは検出できないので、観測できる指標に落とします。

最も早く異常が出るのは、アクティブブランチ数とPRのオープン期間です。ブランチ数はGitHubのブランチ画面のActiveタブで一覧できますが、GitHubのActiveは「デフォルトブランチを除き、過去3か月以内に誰かがコミットしたブランチ」という定義なので、DORAの基準に当てるならマージ待ちのブランチだけに絞って数え直します。PRのオープン期間の中央値は標準の画面には出ないため、gh pr list --state open --json createdAt で作成日を取得して算出します。

指標が悪化したときにルールを増やすのは逆効果です。守られていないルールがある状態で新しいルールを足すと、規約全体の拘束力が下がります。優先すべきは、既存のルールのうち機械で強制できていないものをRulesetsや必須ステータスチェックへ移すことで、規約本文はむしろ短くなります。見直しのたびに規約が長くなり続けているなら、運用は改善していないと考えたほうが実態に合います。

よくある質問

git-flowとGitHub Flowはどちらを選べばよいですか?

判断軸はチーム規模ではなく、市場に出た版を同時に何本サポートするかです。複数版を並行保守するならgit-flow、単一の最新版だけを出し続けるならGitHub Flowを選びます。git-flow考案者のVincent Driessenも2020年3月5日の追記で、ソフトウェアを継続的にデリバリしているチームにはGitHub flowのようなより単純なワークフローを勧めています。

コンフリクトが起きたらどう解消しますか?

Gitは衝突した箇所を <<<<<<< HEAD=======>>>>>>> ブランチ名 の3つのマーカーで囲んでファイルに書き出します。マーカーごと消して最終的な内容を書き、git add でステージしてから git merge --continue(rebase中なら git rebase --continue)で再開します。取り消すなら git merge --abort で実行前に戻せます。

commitとpushの違いは何ですか?

commitは手元のリポジトリに変更を記録する操作で、pushはその記録をリモートリポジトリへ送る操作です。commitだけではリモートに何も反映されず、他のメンバーからは見えません。逆にpushは、まだcommitしていない作業中の変更を送ることはできません。

Gitのブランチとは何ですか?

特定のコミットを指す軽量な参照で、そこから履歴を枝分かれさせ、本流に影響を与えずに作業するための仕組みです。ファイルの複製ではなく参照が1つ増えるだけなので、作成と切り替えの負荷はほとんどありません。この記事で扱うブランチ戦略は、この枝をいつ作りいつ本流へ戻すかの取り決めを指します。

Git運用ルールはどこまでドキュメント化すべきですか?

リポジトリ設定で強制できない、判断の余地がある部分だけです。必須ステータスチェックやCODEOWNERS、Rulesetsで表現できるルールは設定に移し、規約本文からは削ります。設定で表現できないのは、変更の設計判断が妥当かどうかといったレビュー観点の部分で、そこにドキュメントを集中させます。

関連記事

資料請求

RELATED POSTS 関連記事