ブランチと HEAD
Git のブランチは、一つのコミットを指す移動可能な名前だ。HEAD は通常、チェックアウトしたブランチ名を保持し、次のコミットはそのブランチだけを進める。分離 HEAD はブランチを介さずコミットを直接指し、新コミットは HEAD 自体を動かす。
ブランチの作成はファイル、コミット、履歴をコピーしない。同じコミット ID を持つ名前をもう一つ書くだけ。その後のコミットが一方の名前を動かすと、履歴が分岐する。
ブランチと HEAD の仕組み
- コミットオブジェクトは不変で、ツリー、親の情報、識別情報、メッセージなどのバイト列を持つ。その内容からオブジェクト ID を求める。
- ブランチ参照は
refs/heads/mainのような名前で、一つのコミット ID を保持する。前進時にその ID を置き換える。 - HEAD は通常シンボリック参照で、現在のブランチ名を持つ。分離状態ではコミット ID を直接持つ。
接続状態では HEAD → refs/heads/main → 6f09690 の二段階で解決する。コミットはまず親を調べ、新しい不変オブジェクトを書き、その後 main を更新する。表示はオブジェクト書き込みと参照更新を分け、ブランチがまだ指していなくてもオブジェクトが存在しうることを示す。
git branch feature は現在の HEAD を解決し、同じ ID を refs/heads/feature へ書く。HEAD は切り替わらない。main が 6f09690 を指していれば、二つの名前が一つのオブジェクトを指す。
git switch feature はシンボリック HEAD を refs/heads/feature に変え、どちらのブランチ先端も動かさない。実際のリポジトリではインデックスや作業ツリーも更新しうるが、本レッスンは固定の空ツリーを使い、参照だけを観察する。
ブランチと HEAD のステップ
既定では六つのコマンドを実行する。
git commit --allow-empty -m "base"
git branch feature
git switch feature
git commit --allow-empty -m "feature-1"
git switch main
git commit --allow-empty -m "main-1"
最初は未誕生ブランチで、HEAD は既に main を名前に持つが、refs/heads/main に対象コミットはない。
- 最初のコミットには親がない。根
6f09690...を書いた後、main が初めてその ID を指す。 git branch featureが同じ6f09690...の参照を作る。コミットは一つのままで、HEAD は main を指す。git switch featureが HEAD の名前を main から feature に変える。両ブランチは同じ根に残る。feature-1はHEAD → feature → 6f09690...を解決し、子4395860...を書き、feature だけを進める。main は基点に残る。- main へ切り替えると HEAD が main 参照を選ぶ。main を feature のコミットまで引っ張ることはなく、先端は
6f09690...のまま。 main-1も同じ基点を親にして1dc9ff9...を作り、main だけを進める。共有する根と二つの子ができ、それぞれのコミットで別の参照が進んだため履歴が分かれる。
プレイヤーは一意な短い ID 接頭辞を表示し、エンジンは完全な 40 文字の SHA-1 形式 ID を保存する。コミットをスナップショットとして扱う前のレッスンと、固定の識別情報、空ツリー、直列化方法を共有する。コミット時刻は 1700000000 にそのコミットの通し番号(最初のコミットは 0)を足した値なので、コミットごとにタイムスタンプが異なる。
分離 HEAD の動作
分離時の HEAD は保存済みコミットを直接指す。コミットは可能で、HEAD の ID を親にして子を作り、HEAD を更新する。ブランチを選んでいないので、ブランチは動かない。特定コミットの調査や実験に使えるが、到達可能性に注意が必要だ。
分離後に参照を失うプリセットは、6f09690... で分離し、temp を作って main に戻る。一時コミットはオブジェクト庫に残るが、ブランチから到達できない。表示は未参照であり、削除済みではない。実際の Git は通常、最近の HEAD 位置を reflog に記録するため、しばらく復元できる場合がある。reflog の期限切れとオブジェクトの削除は別の仕組みだ。
分離作業の救出プリセットでは、離れる前に次を挿入する。
git branch rescue
git switch rescue
最初のコマンドが refs/heads/rescue を分離コミットに置き、次が HEAD をシンボリック参照へ戻す。コミットグラフは変わらないが、永続的な名前から到達できるようになる。
境界ケース
- 未誕生 HEAD: コミットがなくても
HEAD → mainは合法。最初のコミットが根オブジェクトと main の最初の ID を作る。本エンジンは有効な対象がない間、feature の作成を拒否する。 - 同名ブランチ: feature があれば作成は失敗し、参照を上書きせず、ラベルも増えない。
- 未知のブランチ:
git switch missingは失敗し、HEAD、参照、オブジェクトを変えない。 - 未知のコミット: 本エンジンの分離操作には保存済みコミットの完全な ID が必要で、なければ HEAD は変わらない。
- 一コミットに複数参照: 通常の状態で、マージではない。各名前が独立して同じ ID を持つ。
- 分離中のブランチ作成: 新ブランチは現在の HEAD の ID を得るが、switch するまで HEAD は分離したまま。
- 短い ID の衝突: 七文字から始め、必要なら接頭辞を長くする。識別に使うのは完全な ID。
- 表示制限: 四ブランチ、十コミット、二十コマンドまで。Git 自体の制限ではない。
よくある間違い
- 「ブランチはコミットを全て格納する。」 保持するのは一つの ID で、親リンクをたどって履歴を得る。
- 「ブランチ作成は履歴をコピーする。」 参照を増やすだけ。同じ先端からたどるオブジェクトは共有される。
- 「HEAD は最新コミット。」 現在の選択を記録し、古いブランチ先端や分離状態の古いコミットも指せる。
- 「切り替えるとブランチが動く。」 変わるのは HEAD。対象参照を動かすと、訪れたかった先端を失う。
- 「親上の全ブランチがコミットで進む。」 シンボリック HEAD が名前に持つブランチだけが進む。全てを動かすと独立した作業線を保てない。
- 「ブランチ参照を失うと即削除される。」 到達不能とオブジェクト削除は別の出来事で、reflog とガベージコレクションが復元可能な期間を決める。
モデルが省くもの
クリーンなローカルリポジトリをモデルにし、固定の空ツリーへ --allow-empty でコミットする。ファイルやインデックスの変化を除き、参照更新を見せるためだ。作者とコミッターは固定し、各コミットの時刻は 1700000000 に通し番号を足した値にして、ID を再現できるようにする。チェックアウト競合、リモート・上流参照、タグ、マージ、reflog UI、参照のパック、ブランチ削除・改名、フック、署名、置換参照、並行ファイル書き込み、ガベージコレクションは省く。
それでも、オブジェクトは不変、ブランチは移動可能、シンボリック HEAD はブランチを指し、分離 HEAD はコミットを直接指すという規則は変わらない。オブジェクトの仕組みは前のコミットのレッスン、参照の詳細は gitglossary、git branch、git switch を参照できる。