Gitの運用ルールが形骸化する原因の多くは、ルールが「心がけ」の水準で書かれ、リポジトリの設定やフックに落ちていないことです。この記事では、ブランチ戦略・ブランチ命名・コミットメッセージ・プルリクエスト・レビューの5項目の決め方を、git-flow考案者の2020年の追記、DORAの調査、GitHubのドキュメントを根拠に示し、どこまで機械で強制できるかをGitHubのプラン別に整理します。
まとめ
ブランチ戦略はチームの人数ではなく、リリース形態で決めます。継続的にデリバリしていて、明示的な版番号も複数版の並行保守も要らないなら、git-flowは過剰です。これはgit-flowを考案したVincent Driessen自身が2020年3月5日に元記事へ追記した判断です。運用が回っているかは、DORAが挙げる「アクティブブランチ3本以下」「1日1回以上trunkへマージ」で点検できます。
ブランチ名は小文字・数字・ハイフンと区切りの /、版番号の . に限ります。Gitには feature と feature/login を同時に作れない制約があり、大文字小文字だけが違う名前はmacOSやWindowsで衝突します。コミットメッセージはConventional Commits 1.0.0の書式にし、雛形とcommit-msgフックで配ります。ただしローカルのフックは --no-verify で素通りできるので、最終的な強制はサーバー側に置きます。GitHubのRulesetsでmainへの直接pushとforce pushは止められますが、コミットメッセージとブランチ名を正規表現で検査するメタデータ制限はEnterpriseプランの機能です。Free・Pro・TeamではCIで検査します。
Git運用ルールで決める5項目と決める順序
項目どうしに依存関係があるため、順序を間違えると後戻りします。ブランチ戦略が決まらないと命名のプレフィックスもマージ方式も決まらないので、ここから着手します。
| 順序 | 項目 | 判断を決める要因 | 強制する手段 |
|---|---|---|---|
| 1 | ブランチ戦略 | リリース形態・並行保守する版数 | Rulesets(対象ブランチ) |
| 2 | ブランチ命名 | 戦略の種別・Issue連携 | CI・Enterpriseはメタデータ制限 |
| 3 | コミット書式 | 変更履歴の自動生成の要否 | 雛形・フック・CI |
| 4 | PRの粒度 | レビュー所要時間の許容値 | PRテンプレート |
| 5 | レビュー基準 | コードの所有区分 | CODEOWNERS・必須チェック |
右端の列が埋まらない項目は、書いても守られません。「意図が伝わるコミットメッセージを書く」のような設定で表現できない記述は、規約本文から削って構いません。
ブランチ戦略の選定基準|git-flow・GitHub Flow・GitLab Flow・トランクベース
判断軸は、市場に出ている版を同時に何本サポートするかと、デプロイの頻度です。
git-flowを選ぶ条件と、考案者が2020年に付けた但し書き
git-flowはmainとdevelopの2本を長命ブランチとして持ち、feature・release・hotfixの補助ブランチで開発とリリースを分ける方式です。考案者のVincent Driessenは、2010年の原典記事の冒頭に2020年3月5日付の「Note of reflection」を加え、git-flowが教条や万能薬として扱われていることを指摘しました。
追記の判断は明快です。ソフトウェアを継続的にデリバリしているチームには、git-flowを無理に当てはめるよりGitHub flowのようなはるかに単純なワークフローを勧め、明示的に版番号を付けるソフトウェアや、複数の版を並行してサポートする必要がある場合はgit-flowが今も合う、としています。条件は継続的デリバリをしているかどうかで、Webアプリはその代表例にすぎません。逆に、パッケージソフトや組込み製品のようにv2系とv3系を同時に保守するなら、hotfixブランチが素直に機能します。各ブランチの役割と手順はdevelopブランチとは?featureブランチの意味とrelease・hotfixとの違い【Git Flow】で扱っています。
GitHub Flowの前提となる、常にデプロイ可能なmain
GitHub Flowは、mainから作業ブランチを切り、PRでレビューしてmainへマージし、そのままデプロイする流れで、長命ブランチはmainだけです。その代わりmainが常にデプロイ可能でなければ成立せず、CIによる自動テストとマージ前の必須ステータスチェックが前提になります。前提を欠いたまま移ると、壊れたmainからのデプロイを人力で止め続けることになります。必須チェックの設定はRequire status checks to passとは?GitHubの必須ステータスチェックの設定とPendingで止まる原因に、CIの組み方はGitHub Actionsとは?できること・使い方とCI/CD自動化の活用例をわかりやすく解説にまとめています。
GitLab Flowの環境ブランチとリリースブランチ
GitLab Flowは、GitHub Flowにデプロイ先ごとのブランチを足した方式です。GitLabの解説ページによると、すべての機能と修正はmainへ入れ、そのうえでproductionやstableのブランチを持ちます。main→test→acceptance→productionのように本番前のブランチを必要な数だけ置き、コミットは下流へ一方向に流します。公開APIのように複数の版を保守する場合は、v1・v2のようなリリースブランチを並べる使い方も示されています。
向いているのは、mainへのマージと本番反映の間に検証環境での確認や承認、決まったリリース時刻といった待ちが挟まるチームです。GitHub Flowは「マージ=デプロイ」なので、この待ちを表現できません。検証環境が1つで承認も不要なら、マージ経路が少ないGitHub Flowのほうが軽く済みます。
トランクベース開発とDORAの3つの基準
トランクベース開発の解説サイトは、trunkと呼ぶ単一ブランチで開発者が協働し、長命ブランチを作らないモデルと定義しています。レビューとCIのための短命なフィーチャーブランチは許容され、継続的インテグレーションの中核要件である「全員が24時間に1回以上trunkへコミットする」を満たしやすくする方式と位置づけられています。
DORAのトランクベース開発のページは、2016年と2017年のデータ分析にもとづき、アクティブブランチを3本以下に保つ、1日1回以上trunkへマージする、コードフリーズと統合フェーズを持たない、の3つを実践するチームはデリバリとオペレーションのパフォーマンスが高いとしています。この3つは、どの方式でも運用の合否ラインとして使えます。
4方式の適用条件の比較
| 方式 | 長命ブランチ | 適する形態 | 必須の前提 | 破綻しやすい条件 |
|---|---|---|---|---|
| git-flow | main・develop | 版番号付きリリース・複数版の保守 | リリース計画 | 常時デプロイ |
| GitHub Flow | mainのみ | Webサービス・SaaS | CIと必須チェック | テスト不足 |
| GitLab Flow | main・環境別 | 承認や検証の待ちがある | 下流への一方向マージ | 環境ブランチの乱立 |
| トランクベース | trunkのみ | 高頻度デプロイ | 1日1回のマージ | 長期の大改修 |
git-flowなのにreleaseブランチが使われず、developとmainが常に同じ内容なら、GitHub Flowへ移ってdevelopを廃止すると、feature→develop→mainの2段のマージがfeature→mainの1段になります。長期の大改修を抱えたままトランクベースへ移ると未完成コードがtrunkに乗り続けますが、AWSの規範ガイダンスはこの場面で機能トグルを使い、本番で機能を無効化したままmainの安定性を保つ方法を挙げています。
ブランチ命名規則の決め方とGitが拒否する名前
命名規則は好みの問題に見えますが、Git自体の制約とOSのファイルシステムの差で実害が出ます。
種別プレフィックスとIssue番号の付け方
実務で扱いやすいのは「種別/Issue番号-要約」の形です。種別はブランチ戦略に合わせ、GitHub Flowなら feature・fix・chore 程度、git-flowなら release・hotfix を足します。種別をコミットメッセージの型と揃えておくと、ブランチ名からPRの種類が読めます。
feature/123-login-form
fix/456-null-price
hotfix/2.4.1-payment-timeout
release/2.5.0
Issue番号の前に # を付ける形(fix/#456)はGitでは作れますが、zshでextendedglobを有効にしていると引用符なしの fix/#456 は bad pattern で失敗し、#456 のように単語の先頭に置くとbashでもコメントとして捨てられます。番号は数字だけで書くほうが事故がありません。
git check-ref-formatが拒否する名前
Gitの参照名の規則は git check-ref-format に定義されています。Git 2.50.1で git check-ref-format --branch に通した結果は次のとおりです。日本語は通りますが、空白・連続ドット・コロンなどは作成の時点で拒否されます。
| ブランチ名 | 結果 | 理由 |
|---|---|---|
| feature/login | 可 | – |
| feature/ログイン | 可 | 非ASCIIは禁止されていない |
| fix typo | 不可 | 空白 |
| fix..typo | 不可 | 連続ドット |
| fix:typo | 不可 | コロン |
| release/v1.0.lock | 不可 | 末尾が .lock |
| feature/ | 不可 | 末尾がスラッシュ |
| -hotfix | 不可 | 先頭がハイフン |
featureとfeature/loginが共存できない制約と大文字小文字の衝突
Gitはブランチを refs/heads/ 以下のパスとして扱うため、feature という名前のブランチがあると feature/login を作れません。逆の順でも同じです。Git 2.50.1での実行結果は次のとおりです。
$ git branch feature
$ git branch feature/login
fatal: cannot lock ref 'refs/heads/feature/login': 'refs/heads/feature' exists; cannot create 'refs/heads/feature/login'
種別プレフィックスを使う規約なら、プレフィックスと同じ名前の単独ブランチ(feature や release)は作らない、と1行入れておきます。
大文字小文字も危険です。macOSの標準のファイルシステム(大文字小文字を区別しない設定)で feature/login がある状態から Feature/Login を作ろうとすると、一覧には1本しかないのに fatal: a branch named 'Feature/Login' already exists で止まりました。大文字小文字を区別するLinuxのCIやリモートでは両方が作れてしまうため、メンバーの環境によって同じリポジトリの見え方が変わります。GitHubのドキュメントも、Windowsなど大文字小文字を区別しないファイルシステムとの互換のために、ブランチ名を数字・小文字・ハイフン・アンダースコアに限る正規表現の例を挙げています。この例は / を含まないので、種別プレフィックスを使うなら / を足して使います。規約では小文字だけと決めるのが確実です。
コミットメッセージ規約の書式と、雛形・フックによる定着
コミットメッセージの規約は、書き方の心得ではなく機械可読な書式として定めると、変更履歴の自動生成やバージョン採番につながります。事実上の標準はConventional Commits 1.0.0です。1コミットに何を含めるかという粒度の考え方はコミットとは?Gitが変更履歴を記録する仕組みと粒度・メッセージ設計を実装視点で解説で扱っています。
Conventional Commits 1.0.0の型と破壊的変更の示し方
書式は <type>[optional scope]: <description> の1行目に、空行を挟んで本文とフッターを続ける形です。仕様が意味を定めている型は feat(SemVerのMINORに対応)と fix(PATCHに対応)の2つだけで、それ以外の型は許容されているものの仕様上の効果はありません。破壊的変更は、フッターに BREAKING CHANGE: と書くか、型やスコープの直後に ! を置いて示し(feat!:・feat(api)!:)、MAJORに対応します。
| 型 | 用途 | 採番への影響 | 仕様上の位置づけ |
|---|---|---|---|
| feat | 機能追加 | MINOR | 仕様が規定 |
| fix | バグ修正 | PATCH | 仕様が規定 |
| docs | ドキュメントのみ | なし | 慣例 |
| refactor | 挙動を変えない整理 | なし | 慣例 |
| test | テストの追加・修正 | なし | 慣例 |
| chore | ビルド・依存・雑務 | なし | 慣例 |
feat・fix以外の型でも BREAKING CHANGE を含めばMAJORです。また仕様は型の大文字小文字を区別しないとしていますが、後述のフックは小文字の型だけを通すように仕様より厳しくしています。説明を日本語にするか英語にするかは仕様の範囲外で、型は英語の予約語、説明は日本語という組み合わせでも自動処理は成り立ちます。破壊的変更の2通りの書き方は混在しても有効ですが、リリースノート生成の設定を単純に保つため、どちらかに寄せます。
commit.templateとcommit-msgフックの配布
書式を決めても、白紙から書く限り抜けは出ます。リポジトリに雛形とフックを置き、core.hooksPath(Git 2.9.0で追加)でフックの置き場所をリポジトリ内のディレクトリに向けると、全員が同じ検査を使えます。次のcommit-msgフックは1行目が規約の型で始まらなければコミットを中断します。このフックは git merge でも呼ばれるため、マージとリバートの既定メッセージは先に通しています。この1行がないと Merge branch 't' が規約外として止まりました。フックは実行権を付けてコミットしておかないと、警告だけ出て無視されます。
$ cat .githooks/commit-msg
#!/bin/sh
head -n1 "$1" | grep -Eq '^(Merge|Revert) ' && exit 0
head -n1 "$1" | grep -Eq '^(feat|fix|docs|refactor|test|chore)(\([a-z0-9-]+\))?!?: .+' && exit 0
echo "コミットメッセージが規約外です: $(head -n1 "$1")" >&2
exit 1
$ cat .gitmessage
# <type>(<scope>): <description> type=feat|fix|docs|refactor|test|chore
# 本文: なぜ変更したか/Refs: #<Issue番号>
$ chmod +x .githooks/commit-msg
$ git config commit.template .gitmessage
$ git config core.hooksPath .githooks
$ git commit -m "ログイン修正"
コミットメッセージが規約外です: ログイン修正
$ git commit -m "fix(auth): ログイン修正"
[main 11f73e2] fix(auth): ログイン修正
Git 2.50.1で確認したところ、git commit --no-verify を付けるとフックは実行されず、規約外の wip もそのまま記録されました。githooksのマニュアルもcommit-msgフックは --no-verify で迂回できると明記しています。また git config の設定はクローンしただけでは引き継がれないため、セットアップ手順か初期化スクリプトに含める必要があります。つまりローカルのフックは早く気づかせる仕組みであって、強制の仕組みではありません。強制は次章のサーバー側で行います。
プルリクエストの粒度基準とテンプレート
プルリクエスト(PR)は、作業ブランチの変更を取り込み先へマージしてよいかをレビューにかける依頼で、GitLabではマージリクエストと呼びます。作成の手順はGitHubのPull Requestの作り方:作成手順とマージ方式・レビュー必須化の決め方にまとめています。
1日未満で閉じるサイズという基準
AWSの規範ガイダンスは、大きな作業には短命ブランチを使い、その寿命を典型的には1日未満に保って、できるだけ早くdevelopまたはmainへマージするよう挙げています。小さく頻繁なマージとレビューのほうが処理しやすいためです。
1日で閉じられないPRは、機能が大きすぎるか、レビュー待ちが長いかのどちらかです。前者は変更を分割し、未完成部分を機能トグルで隠して先にマージします。後者は次章のCODEOWNERSで担当を分散させます。寿命が短いほど同じ行を別々に変更する時間の窓が狭まり、コンフリクトも減ります。仕組みと予防の設計はコンフリクトとは?Gitで変更が衝突する仕組みと解消手順・予防設計を実装視点で解説で扱っています。
pull_request_template.mdの配置場所
PR本文の初期値はリポジトリ側で固定できます。GitHubのドキュメントによると、認識される配置場所はルート直下の pull_request_template.md、docs/pull_request_template.md、.github/pull_request_template.md の3か所です。複数のテンプレートを使い分けるなら PULL_REQUEST_TEMPLATE/ ディレクトリに置き、PR作成URLの template クエリパラメータで指定します。テンプレートはデフォルトブランチにマージされてから有効になります。
載せる項目は、変更の背景・影響範囲・確認済みの動作・未確認の残件の4つで足ります。「テスト実施」のチェックボックスは、CIで判定できるなら必須ステータスチェックへ移します。
リポジトリ設定で強制するルールとGitHubのプラン条件
機械で判定できるルールはリポジトリ設定へ移します。GitHubには従来のbranch protection ruleと後発のRulesetsがあり、併存してどちらも適用されます。
mainへの直接push・force push・削除を止める基本のルール
Rulesetsはpublicリポジトリなら無料プランでも使え、privateリポジトリではPro・Team・Enterprise Cloudが必要です。GitHubのドキュメントによると、同じブランチに複数のrulesetを重ねられ、read権限があれば誰でも有効なrulesetを閲覧できるため、開発者が「なぜマージできないのか」を自分で確認できます。
mainに最低限かけるルールは次の4つです。「Block force pushes」と「Restrict deletions」はrulesetの作成時に既定で選択されています。
- Require a pull request before merging(直接pushを禁止し、承認数とコードオーナーのレビューを要求)
- Require status checks to pass(CIの合格を必須にする)
- Block force pushes(履歴の書き換えを禁止)
- Restrict deletions(ブランチの削除を禁止)
マージコミットを残さない運用にするなら「Require linear history」を加えます。このルールはsquashかrebaseによるマージを許可しているリポジトリでなければ有効にできません。
コミットメッセージとブランチ名の検査はEnterpriseプランのみ
コミットメッセージやブランチ名を正規表現で検査する「メタデータ制限」は、どのプランでも使えるわけではありません。GitHubのドキュメントの原稿は、Enterpriseプランの組織がコミットメタデータの書式を制御する追加のルールを使え、新しいブランチ名とタグ名の書式も制御できると記載しています(Enterprise Cloud版の利用可能なルール)。原稿でもこの節はEnterprise向けの条件付きで書かれており、Free・Pro・Teamのリポジトリ設定だけでは使えない前提で設計します。
Enterpriseで使う場合の注意点は2つです。squashマージの扱いはドキュメント内で記述が割れており、冒頭の注記は「ブランチ上の全コミットが要件を満たす必要がある」、詳細の節は「squash後の1コミットだけを検査する」としています。また、規約外のコミットは作成後に書き換えられないため、開発者はrebaseで履歴を作り直すことになります。導入時は適用状態を「Evaluate」にして、squashを含めた実際の挙動を確かめてから有効にします。
Free・Pro・Teamでは、PRのコミット一覧とブランチ名を検査するCIジョブを作り、そのジョブを必須ステータスチェックに登録します。前章のcommit-msgフックと同じ正規表現をCIでも使えば、ローカルで早く気づかせ、サーバーで止める二段構えになります。
CODEOWNERSと、機械判定できないレビュー観点
.github/・ルート・docs/ のいずれかに置いたCODEOWNERSファイルにパスと所有者を書くと、該当パスを変更したPRに自動でレビュアーが割り当てられます。Require a pull requestでコードオーナーのレビューを必須にすると、所有者の承認がないままマージされることもなくなります。
# 既定の所有者
* @org/dev-team
# 観点が違う領域だけ分ける
/infra/ @org/sre
/src/payment/ @org/payment-team
*.sql @org/dba
所有者を細かく分けすぎると、少人数の領域がボトルネックになります。既定の所有者を1行置き、観点が明確に違う領域だけを分けるところから始めます。
lint・型検査・シークレット混入は機械に任せ、シークレットはgitleaksとは?使い方・設定とBetterleaksへの移行判断【v8.30.1で実測】のような検出ツールをCIに組み込みます。人間が見るのは、責務の置き場所と依存の向き、Issueの受入条件との一致、異常系とロールバック、APIとスキーマの後方互換です。レビューの観点と進め方はコードレビューとは?目的・観点・進め方と実装現場の運用設計を解説で詳しく扱っています。
マージ方式の選び方と共有ブランチのrebaseを禁じる理由
マージ方式で履歴の形が変わると、リバートの手順と原因追跡の難しさが変わります。
| 方式 | mainの履歴 | リバートの単位 | 向く条件 |
|---|---|---|---|
| マージコミット(–no-ff) | 分岐と合流が残る | 機能を1コミットで | PRが大きめ |
| squash | PR単位の直線 | PRを1コミットで | PRが1日以内 |
| rebase | コミット単位の直線 | コミットごと | コミットが整理済み |
PRを1日以内のサイズに保っているなら、squashで作業過程を潰しても失う情報は多くありません。PRが大きいチームがsquashを使うと、1コミットの差分が膨らんで git bisect での原因特定が難しくなります。ローカルで merge.ff = only を既定にする運用は、分岐したブランチでは fatal: Not possible to fast-forward, aborting. で止まるため、マージ前に必ずrebaseで最新を取り込む手順とセットにします。
rebaseはコミットを作り直すのでコミットIDが変わり、push済みのブランチでは通常のpushが拒否されます。--force で上書きすると、その間に他人がpushしたコミットを消します。--force-with-lease と --force-if-includes の併用で防げますが、全員に守らせるより、rebaseは自分しか触らない作業ブランチに限り、共有ブランチではmergeを使うと線を引くほうが単純です。rebaseの操作はgit rebaseとは?使い方・mergeとの違い・コンフリクト解決とやり直しを解説、マージをやり直す手順はgit mergeの取り消し|abort・reset・revert -mの使い分けにまとめています。
運用ルールの形骸化を見つける点検指標
運用ルールは導入時が最も守られ、そこから緩みます。最初に異常が出るのは、アクティブブランチ数とPRのオープン期間です。GitHubのブランチ画面のActiveタブは「デフォルトブランチを除き、過去3か月以内にコミットがあったブランチ」なので、DORAの3本以下と比べるならマージ待ちのブランチだけに絞って数え直します。PRのオープン期間の中央値は標準の画面に出ないため、gh pr list --state open --limit 1000 --json createdAt で作成日を取って算出します(--limit の既定は30件で、古いPRが落ちると中央値が小さく出ます)。
指標が悪化したときにルールを足すと、守られていないルールが残ったまま規約全体の拘束力が下がります。先にやるべきは、機械で強制できていない既存のルールをRulesets・必須ステータスチェック・CIへ移すことで、規約本文はむしろ短くなります。CIの整備やGitHub運用の設計を外部に相談したい場合は、DevOps・CI/CD導入支援で対応しています。
よくある質問
git-flowとGitHub Flowはどちらを選べばよいですか?
市場に出た版を同時に何本サポートするかで決めます。複数版を並行保守するならgit-flow、最新版だけを継続的にデプロイするならGitHub Flowです。git-flow考案者も2020年の追記で、継続的デリバリのチームにはより単純なワークフローを勧めています。
ブランチ名に日本語や大文字は使えますか?
Gitの規則上、日本語は使えます(git check-ref-format を通ります)。規約では本文と同じく英小文字・数字・ハイフン・/・版番号の . に限ると、CIの検査を1本の正規表現で書けます。大文字小文字だけが違う名前は、macOSやWindowsなど大文字小文字を区別しないファイルシステムで衝突するため避けます。
commitとpushの違いは何ですか?
commitは手元のリポジトリに変更を記録する操作で、pushはその記録をリモートリポジトリへ送る操作です。commitだけでは他のメンバーからは見えず、commitしていない変更はpushできません。
コンフリクトが起きたらどう解消しますか?
Gitは衝突した箇所を <<<<<<< HEAD、=======、>>>>>>> ブランチ名 のマーカーで囲んでファイルに書き出します。マーカーを消して最終的な内容にし、git add してから git merge --continue(rebase中なら git rebase --continue)で再開します。取り消すなら git merge --abort で実行前に戻せます。
Git運用ルールはどこまでドキュメント化すべきですか?
リポジトリ設定やCIで強制できない、判断の余地がある部分だけです。必須ステータスチェック・CODEOWNERS・Rulesets・フックで表現できるルールは設定に移し、規約本文からは削ります。文書に残すのは、設計判断の妥当性のようなレビュー観点と、例外を認める条件です。