Git入門:バージョン管理のきほん

ブランチ戦略(GitHub Flow)

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

このレッスンで分かること

  • GitHub Flow の 5 つのステップと、その背後にある「main は常にデプロイ可能」という原則
  • ブランチ名の付け方と、マージ後に削除するまでの日々の手順
  • git-flow との違いと、なぜ今 GitHub Flow が選ばれやすいのか

ブランチ戦略はチームの交通ルール

ブランチの作り方も合流のさせ方も、ここまでのレッスンで揃いました。ただし技術が分かっていても、チーム全員がバラバラの流儀で使えば履歴は荒れます。誰がどのブランチをいつ作り、いつ消すか。この取り決めがブランチ戦略です。

戦略は好みではなく、リリースの仕方で決まります。1 日に何度でもデプロイできる Web サービスと、半年に一度だけ出荷して古いバージョンも保守するパッケージソフトでは、必要な型が違います。ここでは前者の代表である GitHub Flow を扱います。覚えることが少なく、学習中の個人開発から実務まで同じ形で使えます。

GitHub Flow の 5 ステップ

GitHub Flow はこれだけです。

  1. main から作業ブランチを切る
  2. そのブランチでコミットする
  3. プルリクエストを出す
  4. レビューを受けて直す
  5. main にマージして、ブランチを削除する

長く生きるブランチは main ただ 1 本です。それ以外はすべて数日で生まれて消える使い捨てになります。

図にするとこの形です。

プレーンテキスト

A---B feature/login-form / \ o---o---o-----------M---o main

main という 1 本の幹があり、そこから短い枝が生えては戻る。それを延々と繰り返すだけです。枝が幹から離れている時間が短いほど、この形はうまく回ります。

ブランチ寿命役割
main永続常にデプロイ可能な状態を保つ唯一の本流
作業ブランチ数時間から数日1 つの機能や修正。マージしたら削除する

main は常にデプロイ可能に保つ

GitHub Flow を支えている原則は 1 つです。main の最新は、いつ本番へ出しても壊れない状態でなければならない

この約束があると、いくつもの判断が自動的に決まります。

  • main に直接コミットしない。壊れた状態が一瞬でも本流に乗る余地をなくす
  • 動かないコードをマージしない。テストが通ってからマージする
  • 緊急の修正も main から切って、同じ流れでマージする。特別扱いの近道を作らない

逆に言えば、main が信用できないチームは、リリースのたびに「今の main って出して大丈夫でしたっけ」という確認が発生します。この確認が消えることが、この原則のいちばんの見返りです。

GitHub には main への直接 push を禁止する ブランチ保護 の設定があります。リポジトリの Settings から Branches を開き、main に対して「Require a pull request before merging」を有効にすると、プルリクエスト経由でしかマージできなくなります。習慣に頼らず仕組みで守れるので、チームで開発するなら最初に入れてください。

保護をかけた main に直接 push しようとすると、こう拒否されます。

ターミナル

$ git push remote: error: GH006: Protected branch update failed for refs/heads/main. remote: error: Changes must be made through a pull request.

このとき、コミット自体はローカルに残っています。作業ブランチを切ってそこへ移し、プルリクエストを出し直せば無駄にはなりません。

ターミナル

$ git switch -c fix/typo-in-readme $ git push -u origin fix/typo-in-readme

main に立ったまま切ったので、直前のコミットはそのまま新しいブランチに乗ります。あとは main を元の位置に戻せば片付きます。

もう 1 つ入れておきたいのが「Require status checks to pass before merging」です。テストが通ったプルリクエストしかマージできなくなり、main が壊れる経路が実質的に塞がります。テストの自動実行は GitHub Actions で自動化のさわり で扱います。

学習中の個人リポジトリでも、main に直接コミットする癖はつけないでください。1 人だと横着したくなりますが、チームに入ってから直すほうがずっと大変です。

作業ブランチを切る

必ず最新の main から切ります。古い main から切ると、その時点で他人の変更ぶんの遅れを背負います。

ターミナル

$ git switch main Switched to branch 'main' $ git pull Updating 4c2b70e..7e2d804 Fast-forward README.md | 5 +++++ 1 file changed, 5 insertions(+) $ git switch -c feature/login-form Switched to a new branch 'feature/login-form'

名前は他人が読んで何をするブランチか分かることが条件です。種類を頭に付ける形がよく使われます。

プレーンテキスト

feature/login-form fix/header-overflow docs/setup-guide chore/update-deps

wiptesttmpmyname のような名前は避けてください。1 週間後の自分も含めて誰も中身を思い出せません。チケット番号を運用しているチームなら feature/1234-login-form のように番号を混ぜます。ブランチ名はプルリクエストの一覧にそのまま並ぶので、チーム全員が毎日読む文字列だと思って付けてください。

大文字と小文字だけが違う名前は、Windows と Mac で扱いが食い違って事故のもとになります。全部小文字とハイフンに統一しておくのが無難です。

ブランチは小さく保ちます。目安は 1 ブランチ 1 目的、1 日から 3 日で終わる大きさです。長生きするほど main との差が開き、マージのときのコンフリクトが大きくなります。終わりが見えないときは、機能を分割して動く単位ごとにマージしてください。

プルリクエストとマージ

作業が終わったら push して、プルリクエストを出します。origin はリモートリポジトリにつけられた既定の呼び名です。

ターミナル

$ git push -u origin feature/login-form

-u は「このローカルブランチはリモートのこのブランチと対応する」という紐付けを保存するオプションで、初回だけ必要です。次からは git push だけで通ります。

プルリクエストは、コードを見せて意見をもらうための場所です。出し方と書き方は プルリクエストを出す、レビューのやり取りは コードレビューの受け方・出し方 で詳しく扱います。

レビュー中に main が進むこともあります。差が開いてきたら、作業ブランチ側で最新を取り込んでおきます。

ターミナル

$ git switch feature/login-form $ git fetch origin $ git rebase origin/main

一直線の履歴を保ちたいなら rebase、履歴の書き換えを避けたいなら git merge origin/main を使います。すでに push してレビュー中のブランチを rebase すると相手の手元と食い違うので、レビュアーがいる場合は merge を選ぶほうが無難です。

承認されたら main にマージします。GitHub の画面上でマージするのが基本です。ボタンには 3 つの選択肢があります。

マージ方法履歴の見え方
Create a merge commit合流点が残り、作業の単位が履歴に見える
Squash and merge全コミットが 1 つにまとまる。試行錯誤が消えて読みやすい
Rebase and merge一直線につながる。マージコミットは作られない

どれを使うかはチームの決めごとです。迷ったら Squash and merge が扱いやすく、main の履歴が「1 機能 1 コミット」で並ぶので後から追いやすくなります。

3 つの詳しい違いは マージの仕組み(fast-forward と マージコミット) で扱った内容と同じです。GitHub のボタンは、裏側で git mergegit merge --squashgit rebase を実行しているだけだと思ってください。

なお、緊急の本番修正も同じ流れを通します。main から fix/ のブランチを切り、プルリクエストを出し、レビューを受けてマージする。急いでいるからこそ、確認の目を 1 つ通す価値があります。急ぎだから直接 push、を一度でも許すと、その例外は必ず常態化します

マージしたらブランチを削除する

GitHub 上でマージすると「Delete branch」のボタンが出ます。押してください。ローカルにも残っているので、そちらも片付けます。

ターミナル

$ git switch main Switched to branch 'main' $ git pull $ git branch -d feature/login-form Deleted branch feature/login-form (was 9c4e71a).

-d はマージ済みかを確認したうえで削除するので、消し忘れの検出も兼ねます。リモートで削除済みのブランチの参照がローカルに残っている場合は、まとめて掃除できます。

ターミナル

$ git fetch --prune

消すことに抵抗を感じる必要はありません。マージ済みなら、そのコミットは main の履歴に残っています。ブランチ名は名札にすぎず、消しても中身は失われません。むしろ古いブランチが 30 本並んだ git branch は、どれが生きているのか分からず邪魔になります。

やりがちな失敗を 1 つ挙げます。作業が終わったブランチをそのまま置いて、次の作業をその上から切ってしまうパターンです。

ターミナル

$ git switch -c feature/logout-form

feature/login-form に立ったままこれを打つと、まだレビュー中の login の変更を丸ごと抱えた logout ブランチができます。プルリクエストには関係ない差分が混ざり、レビュアーが読めません。新しい作業は必ず main に戻ってから切る、と手順を固定してください。間違えて切ってしまった場合は、コミット前なら git switch -c をやり直すだけです。

git-flow との対比

もう 1 つ有名なのが git-flow です。maindevelop の 2 本を常時運用し、featurereleasehotfix の 3 種類を使い分けます。リリースを固定バージョンで出荷し、古いバージョンも並行して保守する製品には合理的な形です。

ただしブランチが 5 種類あり、どこから切ってどこへ戻すかの規則も多く、運用は重くなります。Web サービスのように「マージしたらその日のうちにデプロイする」開発では、developrelease はほとんど何も生みません。だから今は GitHub Flow のほうが選ばれます。

観点GitHub Flowgit-flow
常設ブランチmain のみmaindevelop
ブランチの種類作業ブランチのみfeature / release / hotfix
向いている開発いつでもデプロイする Web サービスバージョンを固定して出荷する製品
学習コスト低い高い

中間の形もあります。GitHub Flow に「リリース用のタグを打つ」だけを足す運用です。マージのたびにデプロイはせず、区切りのついた時点でタグを打って出荷する。ブランチは増やさずリリースの目印だけ残せるので、develop を用意するより軽く済みます。タグの打ち方は タグでリリースに印をつける で扱います。

まずは GitHub Flow を身体に入れてください。それで回らない事情が出てきたときに、初めて重い型を検討すれば十分です。戦略は増やすより減らすほうが難しいので、最小の形から始めるのが結局いちばん近道になります。

この章のポイント
  • GitHub Flow は「main から切る、コミット、プルリクエスト、レビュー、マージして削除」の 5 ステップ
  • 支えている原則は main を常にデプロイ可能に保つこと。直接 push はブランチ保護で禁じる
  • 作業ブランチは 1 目的で小さく、数日で終わらせる。マージしたら必ず削除する
  • git-flow は複数バージョンを保守する製品向け。運用が重いので今は選ばれにくい