株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。
Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
▶コーポレートサイト
はじめに
「ブランチを切る」「HEADが指している」「detached HEAD になった」 ―― Git を使っていると当たり前のように出てくるこれらの言葉、正確に説明できますか?
ブランチは「枝分かれした作業の流れ」というイメージで語られがちですが、Gitの内部では もっとシンプルな仕組み で実現されています。その正体を知ると、ブランチ操作で迷うことが格段に減ります。
この記事では、ブランチとHEADの内部構造を 図解付き で解説します。
この記事は 「図解でわかるGitの仕組み」シリーズ の第2回です。
Gitの内部構造を、1テーマ1記事で図解していきます。
1. ブランチの正体 ― コミットを指す「ポインタ」
よくある誤解
「ブランチ=作業の履歴を丸ごとコピーしたもの」と思っていませんか?
それは誤解です。 Git のブランチは、特定のコミットを指す 40文字のSHA-1ハッシュが書かれたただのテキストファイル です。
ブランチの実体を見てみよう
.git/refs/heads/ ディレクトリを覗くと、ブランチの正体がわかります。
# mainブランチの実体を確認
$ cat .git/refs/heads/main
a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
たったこれだけです。ブランチ名に対応するファイルの中に、コミットのSHA-1ハッシュが1行書かれているだけ です。
ブランチが複数あるとき
$ cat .git/refs/heads/main
a1b2c3d... # コミットC3を指す
$ cat .git/refs/heads/feature
d4e5f6a... # コミットC5を指す
ブランチは コミット履歴を複製しているわけではなく、それぞれが1つのコミットを指しているだけ です。だからこそ、Gitのブランチ作成は一瞬で終わります。
2. HEADとは ― 「今どこにいるか」を示すポインタ
HEADの役割
HEAD は「今自分がどのブランチで作業しているか」を記録する特別なポインタです。
.git/HEAD ファイルの中身を見てみましょう。
$ cat .git/HEAD
ref: refs/heads/main
HEADはブランチ名(への参照)を保持しています。つまり、HEADはブランチを指し、ブランチはコミットを指す という二段構えの構造です。
なぜ二段構えなのか
新しいコミットを作ると、以下のことが起きます。
- 新しいコミットオブジェクトが作られる(親コミットへの参照を持つ)
- 現在のブランチのポインタが、新しいコミットに自動で移動する
- HEADはブランチを指したままなので、結果的にHEADも新しいコミットを指すことになる
# コミット前
HEAD → main → C3
# コミット後
HEAD → main → C4(新しいコミット。親はC3)
HEADが直接コミットではなくブランチを指しているからこそ、「今いるブランチだけが前に進む」 という動作が実現されます。
3. コミットグラフの読み方
Git の履歴は 有向非巡回グラフ(DAG: Directed Acyclic Graph) で表現されます。以下のようなグラフの読み方を押さえましょう。
C1 ← C2 ← C3 ← C4 ← main ← HEAD
↖
C5 ← C6 ← feature
読み方のポイント
| 要素 | 意味 |
|---|---|
C1, C2 ... |
各コミット(丸いノード) |
← |
親コミットへの参照(矢印の向きに注意:子→親) |
main, feature
|
ブランチ(コミットを指すポインタ) |
HEAD |
現在のブランチを指すポインタ |
矢印の向きに注意
コミットグラフの矢印は 子から親 に向かいます。コミットは「自分の親は誰か」を知っていますが、「自分の子は誰か」は知りません。これはGitが過去の履歴を辿る設計になっているためです。
git log --oneline --graph --all を実行すると、ターミナル上でこのグラフを確認できます。
$ git log --oneline --graph --all
* d4e5f6a (feature) ユーザー検索機能を追加
* b3c4d5e ユーザーモデルにemail属性を追加
| * a1b2c3d (HEAD -> main) READMEを更新
|/
* 9f8e7d6 初期コミット
4. ブランチの作成・切替・削除を図で追う
ブランチの作成:git branch
$ git branch feature
このコマンドは「ポインタを1つ追加するだけ」 です。コミット履歴のコピーは一切行いません。
内部的には git update-ref refs/heads/feature <現在のコミットのSHA-1> と同等の処理が実行されます。
# 実行前
C1 ← C2 ← C3 ← main ← HEAD
# 実行後(featureが追加された。HEADはmainのまま)
C1 ← C2 ← C3 ← main ← HEAD
↖
feature
ブランチの切替:git switch / git checkout
$ git switch feature
HEADの参照先が変わるだけ です。
# 実行前
$ cat .git/HEAD
ref: refs/heads/main
# 実行後
$ cat .git/HEAD
ref: refs/heads/feature
# 実行前
C1 ← C2 ← C3 ← main ← HEAD
↖
feature
# 実行後(HEADがfeatureを指すようになった)
C1 ← C2 ← C3 ← main
↖
feature ← HEAD
加えて、ワークツリーの内容がそのブランチのコミットが指す状態に更新されます。
featureブランチでコミット
# featureブランチ上で作業してコミット
$ git commit -m "新機能を追加"
# featureだけが前に進む。mainは動かない
C1 ← C2 ← C3 ← main
↖
C4 ← feature ← HEAD
ブランチの削除:git branch -d
$ git switch main
$ git branch -d feature
ポインタが削除されるだけ です。コミット自体は(他のブランチから到達可能であれば)削除されません。
# 削除前
C1 ← C2 ← C3 ← main ← HEAD
↖
C4 ← feature
# 削除後(featureポインタが消えただけ)
C1 ← C2 ← C3 ← main ← HEAD
↖
C4 ← (どのブランチからも到達できないコミット)
到達不能なコミットの行方
どのブランチやタグからも辿れなくなったコミットは、git gc(ガベージコレクション)が実行されると最終的に削除されます。ただし、git reflog に一定期間(デフォルトで90日間)記録が残るため、すぐには消えません。
5. git branch / git checkout / git switch の違い
Git 2.23(2019年8月リリース)で git switch と git restore が導入されました。それまで git checkout が担っていた 「ブランチ切替」と「ファイル復元」という2つの異なる役割 が、専用コマンドに分離されました。
コマンド比較表
| やりたいこと | 従来のコマンド | 現在の推奨コマンド |
|---|---|---|
| ブランチの一覧表示 | git branch |
git branch |
| ブランチの作成(切替なし) | git branch feature |
git branch feature |
| ブランチの切替 | git checkout feature |
git switch feature |
| ブランチの作成+切替 | git checkout -b feature |
git switch -c feature |
| 特定コミットに切替(detached HEAD) | git checkout <SHA> |
git switch --detach <SHA> |
| ファイルの復元 | git checkout -- file.txt |
git restore file.txt |
なぜ分離されたのか
git checkout は1つのコマンドで以下をすべてこなしていました。
- ブランチの切替
- ブランチの作成+切替
- 特定のコミットへの移動
- ファイルの復元
機能が多すぎるため、意図しない動作を引き起こすリスクがありました。例えば、feature という名前のブランチとファイルの両方が存在する場合、git checkout feature がどちらを指すのか曖昧になります。
git switch(ブランチ操作専用)と git restore(ファイル復元専用)に分離することで、誤操作のリスクが低減 されました。
git checkout は非推奨ではなく、引き続き使用できます。ただし、新たに学ぶ場合は git switch / git restore を使う方が意図が明確になります。
6. detached HEAD ― HEADがブランチを指さない状態
detached HEADとは
通常、HEADはブランチを指しています(ref: refs/heads/main のように)。しかし、ブランチではなくコミットを直接指す状態 になることがあります。これが detached HEAD です。
# 特定のコミットを直接チェックアウト
$ git switch --detach a1b2c3d
# HEADの中身がブランチ参照ではなくSHA-1になる
$ cat .git/HEAD
a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
detached HEADになるケース
| 操作 | コマンド例 |
|---|---|
| 特定のコミットをチェックアウト | git switch --detach a1b2c3d |
| タグをチェックアウト | git switch --detach v1.0.0 |
| リモートブランチを直接チェックアウト | git switch --detach origin/main |
| rebase中の各コミット適用時 | (自動的にdetached HEADになる) |
detached HEADの危険性
detached HEAD 状態でコミットすると、そのコミットはどのブランチにも属しません。
# detached HEAD状態でコミットした場合
C1 ← C2 ← C3 ← main
↖
C4 ← HEAD(ブランチなし!)
この状態で git switch main すると、C4はどのブランチからも到達できなくなり、最終的にガベージコレクションで削除されます。
detached HEADからの復帰方法
# 方法1: 既存のブランチに戻る
$ git switch main
# 方法2: 今いるコミットから新しいブランチを作る(コミットを残したい場合)
$ git switch -c new-branch
Gitは detached HEAD 状態に入るとき、ターミナルに警告メッセージを表示します。この警告が出たら、意図してその状態にいるのかどうかを確認 しましょう。意図せず入っていた場合は、作業をコミットする前にブランチに戻るのが安全です。
7. ブランチ操作の内部動作まとめ
ここまで学んだ内容を、Gitの内部動作の視点で整理します。
| 操作 | 内部で起きること |
|---|---|
git branch feature |
.git/refs/heads/feature に現在のコミットSHA-1を書き込む |
git switch feature |
.git/HEAD の内容を ref: refs/heads/feature に書き換え、ワークツリーを更新する |
git commit |
新しいコミットオブジェクトを作成し、現在のブランチの参照先を新コミットに更新する |
git branch -d feature |
.git/refs/heads/feature ファイルを削除する |
git switch --detach <SHA> |
.git/HEAD にSHA-1を直接書き込む(ref: プレフィックスなし) |
確認コマンド集
# 現在のHEADが指す先を確認
$ git symbolic-ref HEAD
refs/heads/main
# HEADが指すコミットのSHA-1を確認
$ git rev-parse HEAD
a1b2c3d4e5f6...
# 各ブランチが指すコミットを一覧表示
$ git branch -v
* main a1b2c3d READMEを更新
feature d4e5f6a ユーザー検索機能を追加
# detached HEAD状態かどうかを確認
$ git symbolic-ref HEAD 2>/dev/null || echo "detached HEAD"
git symbolic-ref HEAD は、HEADがブランチを指しているときはブランチ名を返し、detached HEAD状態のときはエラーになります。この性質を利用して、上記のようにdetached HEADかどうかを判定できます。
8. まとめ
この記事で学んだポイントを整理します。
| 項目 | ポイント |
|---|---|
| ブランチの正体 | コミットのSHA-1ハッシュが書かれたテキストファイル(ポインタ) |
| HEADの正体 | 現在のブランチへの参照を持つ特別なポインタ(.git/HEAD) |
| コミット時の動作 | 現在のブランチのポインタだけが新しいコミットに移動する |
| ブランチ作成 | ポインタを1つ追加するだけ(履歴のコピーは行わない) |
git switch と git checkout
|
switchはブランチ操作専用。checkoutの役割を分離したもの(Git 2.23〜) |
| detached HEAD | HEADがブランチではなくコミットを直接指す状態。コミットが失われるリスクあり |
次回予告
第3回では 「マージとリベース」 を図解で解説します。2つのブランチを統合する方法には merge と rebase がありますが、コミットグラフにどんな違いが生まれるのか を図で比較しながら、使い分けの基準を学びます。
シリーズ目次
| # | テーマ |
|---|---|
| 1 | Gitの全体像 ― ワークツリー・ステージ・リポジトリの3層構造 |
| 2 | ブランチとHEAD ― 「枝分かれ」の正体を理解する(この記事) |
| 3 | マージとリベース ― 統合戦略の違いを図で比較 |
| 4 | リモートとローカル ― push/pull/fetchの裏側 |
| 5 | コンフリクト解消 ― なぜ起きるのか、どう直すのか |
| 6 | reset/revert/checkout ― 「戻す」操作を正しく使い分ける |
| 7 | stash/cherry-pick/rebase -i ― 実務で頼れる便利コマンド |
| 8 | .gitignore・フック・設定 ― Gitをカスタマイズする |
| 9 | Git Flow vs GitHub Flow ― ブランチ戦略の選び方 |
| 10 | トラブルシューティング ― 「やらかした!」を救うコマンド集 |
参考
- Git - Git References(Git公式ドキュメント)
- Git - Branches in a Nutshell(Git公式ドキュメント)
- Git - git-switch Documentation(Git公式ドキュメント)
- Git - git-branch Documentation(Git公式ドキュメント)
- Git - git-checkout Documentation(Git公式ドキュメント)
「この記事が役に立った」と思ったら、LGTM とストックをお願いします。
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!








