0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【図解でわかるGit 第2回】ブランチとHEAD ― 「枝分かれ」の正体を理解する

0
Posted at

株式会社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行書かれているだけ です。

02_branch_is_pointer.png

ブランチが複数あるとき

$ cat .git/refs/heads/main
a1b2c3d...   # コミットC3を指す

$ cat .git/refs/heads/feature
d4e5f6a...   # コミットC5を指す

02_head_points_to_branch.png

ブランチは コミット履歴を複製しているわけではなく、それぞれが1つのコミットを指しているだけ です。だからこそ、Gitのブランチ作成は一瞬で終わります。


2. HEADとは ― 「今どこにいるか」を示すポインタ

HEADの役割

HEAD は「今自分がどのブランチで作業しているか」を記録する特別なポインタです。

.git/HEAD ファイルの中身を見てみましょう。

$ cat .git/HEAD
ref: refs/heads/main

HEADはブランチ名(への参照)を保持しています。つまり、HEADはブランチを指し、ブランチはコミットを指す という二段構えの構造です。

02_head_points_to_branch.png

なぜ二段構えなのか

新しいコミットを作ると、以下のことが起きます。

  1. 新しいコミットオブジェクトが作られる(親コミットへの参照を持つ)
  2. 現在のブランチのポインタが、新しいコミットに自動で移動する
  3. HEADはブランチを指したままなので、結果的にHEADも新しいコミットを指すことになる
# コミット前
HEAD → main → C3

# コミット後
HEAD → main → C4(新しいコミット。親はC3)

02_commit_moves_branch.png

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

02_git_branch_create.png

ブランチの切替: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

02_git_switch.png

加えて、ワークツリーの内容がそのブランチのコミットが指す状態に更新されます。

featureブランチでコミット

# featureブランチ上で作業してコミット
$ git commit -m "新機能を追加"
# featureだけが前に進む。mainは動かない
C1 ← C2 ← C3  ← main
              ↖
               C4  ← feature  ← HEAD

02_commit_on_feature.png

ブランチの削除: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 switchgit 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

02_detached_head.png

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の内部動作の視点で整理します。

02_internal_overview.png

操作 内部で起きること
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 switchgit checkout switchはブランチ操作専用。checkoutの役割を分離したもの(Git 2.23〜)
detached HEAD HEADがブランチではなくコミットを直接指す状態。コミットが失われるリスクあり

次回予告

第3回では 「マージとリベース」 を図解で解説します。2つのブランチを統合する方法には mergerebase がありますが、コミットグラフにどんな違いが生まれるのか を図で比較しながら、使い分けの基準を学びます。


シリーズ目次

# テーマ
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 トラブルシューティング ― 「やらかした!」を救うコマンド集

参考


「この記事が役に立った」と思ったら、LGTM とストックをお願いします。

@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?