第7章 ブランチを切る

ブランチはポインタである

ブランチは「コードの派生線」と説明されることが多いが、Git の実装レベルでは単なる「コミットハッシュを指している名札」である。 名札が貼り変わるだけで、コミット本体は不変のまま履歴に残り続ける。

ブランチがコミットを指すポインタであることを示す図
main は C3 を、feature は C4 を指している。HEAD はいま feature を見ている

図の例では、main ブランチは C3 というコミットを、feature ブランチは C4 を指している。 HEAD は「現在チェックアウト中のブランチ」を指す特別なポインタで、いまは feature を向いている。 この状態でコミットを作ると、feature のポインタが新しいコミットへと進み、main は動かない。

MEMO

.git/refs/heads/main というテキストファイルを覗くと、そこに 40 文字のコミットハッシュがただ 1 行書かれている。 ブランチの実体は「1 行のテキストファイル」と言い切ってしまってもよいくらい軽い存在である。 だから Git ではブランチをいくら切っても、ほぼ容量コストがゼロで済む。

detached HEAD に注意

git switch --detach <コミットハッシュ> のように、特定のコミットに直接チェックアウトすると、 HEAD がどのブランチも指していない detached HEAD 状態になる。 この状態でコミットを作ると、そのコミットはどのブランチにも属さず、あとで辿りつけなくなる危険がある。 過去コミットの中身を一時的に確認したいだけなら問題ないが、作業を積み上げたいときは必ず git switch -c <new-branch> で新しいブランチを切ってから動くこと。

ブランチを作る、切り替える

ブランチの一覧と現在地

Bash
# ローカルブランチの一覧 (現在地に * が付く)
git branch

# リモート追跡ブランチも含めて表示
git branch -a

新しいブランチを作る

git branch <name> は「作るだけ」で、現在のブランチは切り替わらない。 ほとんどの場合は作ったら即そこに入りたいので、git switch -c (作成 + 切り替えを同時) が実用的である。

Bash
# 作成のみ (現在地は変わらない)
git branch feature/add-user-login

# 作成 + 即切り替え (推奨)
git switch -c feature/add-user-login

既存のブランチに切り替える

Bash
git switch main
git switch feature/add-user-login

# 直前にいたブランチへ戻る (cd - と同じ感覚)
git switch -

git switch - は地味だが便利で、mainfeature/xxx を行ったり来たりするときにブランチ名を打ち直さずに済む。

POINT

年季の入ったチュートリアルだと git checkout -b feature/add-user-login という書き方をよく見かける。 checkout は歴史的に「ブランチ切替」と「ファイル復元」を兼ねる万能すぎるコマンドで事故が多かったため、 Git 2.23 以降では switch (ブランチ切替) と restore (ファイル復元) に役割分担された。 いまから覚えるなら switch / restore に寄せるのがおすすめである。

リモートのブランチに切り替える

GitHub 側にすでに存在するブランチに手元で乗り換えたいときは、git switch <name> を打てば Git が賢く origin/<name> を検出して追跡してくれる。

Bash
# origin/feature/add-user-login が存在する前提
git fetch
git switch feature/add-user-login
# => Switched to a new branch 'feature/add-user-login'
#    branch 'feature/add-user-login' set up to track 'origin/feature/add-user-login'

ローカルのブランチを GitHub に公開する

逆に、手元で作ったブランチをまだ GitHub が知らない状態で、共有可能な場所に送りたいときは、初回 push のときに -u で upstream を紐づける。 これは第 6 章で触れた main の初回 push と同じパターンである。

Bash
# 手元で作った feature/add-user-login を GitHub に送る
git push -u origin feature/add-user-login

# 2 回目以降は引数なしで OK
git push

ブランチを切る動機と命名

ブランチを切る理由はだいたい次のどれかに収まる。

命名に厳密なルールはないが、実務では一貫性を保つための緩やかな慣習が存在する。 読み手 (チームメンバー、将来の自分) がブランチ名だけで「何をしようとしているか」を数秒で把握できる命名が望ましい。

用途別プレフィックス

先頭に用途プレフィックスをつけて、スラッシュで概要と分ける形式が広く使われる。

概要部分の書き方

スラッシュ以降の概要部分には、おおむね次のような慣習がある。

良い例と避けたい例

以下の 2 つを見比べると、命名の差がそのまま「あとで見返したときの読みやすさ」の差になっているのが分かる。

良い例:

避けたい例:

POINT

本教材ではこれ以降、サンプルのブランチ名に feature/add-user-login を使う。 「動詞 + 目的語 + kebab-case」でブランチ名に情報を載せる流儀に慣れておくと、実務で命名に迷う回数が減る。

main と master — 2 種類の「本流」ブランチ

Git でデフォルトブランチに相当するものには、プロジェクトによって mainmaster の 2 つの名前が登場する。 最初に触ったリポジトリで見かけた方と違う名前に出くわして戸惑うことは少なくないので、両者の関係を整理しておく。

歴史的な経緯

Git は長らく、新規リポジトリの最初のブランチを master という名前で作る仕様だった。 2020 年に GitHub が新規リポジトリのデフォルトを main に変更したのを皮切りに、GitLab や Bitbucket など主要ホスティングサービスも追随し、 Git 本体もデフォルトブランチ名の設定 (init.defaultBranch) 未指定時に警告を出すようになった。

その結果、新規プロジェクトは main、歴史あるプロジェクトは master のまま、という現状の二分が生まれている。

技術的な違いは「無い」

両者は単にブランチ名が違うだけで、機能・挙動・操作方法にはいっさい差がない。 git switch maingit switch master は「そのリポジトリにどちらの名前のブランチが存在しているか」だけの違いである。

既存リポジトリを master から main に揃えたい場合

チームの合意が取れているなら、改名して統一するのも手軽にできる。

Bash
# ローカルで改名
git branch -m master main

# 新しい名前で GitHub に push して upstream を張り直す
git push -u origin main

# GitHub 上のリポジトリ設定で Default branch を main に変更
# (Settings → Branches → Default branch)

# 旧 master を削除
git push origin --delete master
MEMO

個人開発なら好きな方を選べばよい。既存プロジェクトに参加する場合は、そこで採用されている名前に合わせる。 本教材では第 2 章で init.defaultBranch = main に揃える想定で進めているが、手元のリポジトリが master だった場合は読み替えてほしい。

統合ブランチ (main と develop) の使い分け

複数人で開発していると、全員が main をそのまま作業場にすると不安定になりがちである。 そこで多くのプロジェクトでは、「完成した機能を積み上げていく統合用のブランチ」を別に用意する運用が採られる。それが develop ブランチである。

典型的な役割分担

流れとしては feature/xxxdevelop (統合) → main (リリースのタイミングで) の順にマージしていく。 これにより main は「いつ見ても動く状態」が維持され、開発途中の未完成機能が混ざらない。

MEMO

この 3 層 (さらに release/*hotfix/* を足した 5 層) を定式化したものに Git-flow という有名なパターンがある。 一方、GitHub 自身が薦める GitHub Flowmain + feature/* の 2 層だけでシンプル。 プロジェクトの規模・リリースサイクル・チーム人数によって向き不向きが分かれるので、これもチームの流儀に合わせる。

ブランチの改名

切ったあとに命名を誤ったと気づいたときは、改名で対処できる。

Bash
# 現在いるブランチを改名
git branch -m feature/new-name

# 別ブランチを改名 (現在地は動かない)
git branch -m feature/old-name feature/new-name
注意

既に GitHub に push 済みのブランチを改名した場合、リモート側には旧名のブランチが残り続ける。 改名後に git push -u origin feature/new-name で新名を push し、 古い方は git push origin --delete feature/old-name で明示的に消す必要がある。

ブランチ同士を比較する

マージや PR を出す前に、対象ブランチに「main より何が増えたか」を眺めておくと安心である。A..B という書き方で、 A にはないが B にはあるコミットを抽出できる。

Bash
# main にないが feature/add-user-login にはあるコミット一覧
git log main..feature/add-user-login --oneline

# 上と同じ範囲のコード差分 (patch)
git diff main..feature/add-user-login

# ファイル名だけ一覧したい場合
git diff main..feature/add-user-login --stat

逆向きの feature/add-user-login..main にすれば、「feature 側から取り残されている main の新着コミット」が見える。 マージ前に「自分のブランチが遅れていないか」の確認に使える。

ブランチの削除

作業が終わって本流 (たいていは main) にマージしたら、役目を終えたブランチは消してしまってよい。 マージ前に消そうとすると Git は警告してくれる。

Bash
# マージ済みブランチを削除 (安全)
git branch -d feature/add-user-login

# 未マージでも強制削除 (使いどころは慎重に)
git branch -D feature/add-user-login
注意

ローカルを削除しても、GitHub 側のブランチは残ったまま。GitHub 側も消したいなら git push origin --delete feature/add-user-login を別途実行する。

まとめ