Git の四つの領域
add、commit、push は異なる境界を越える操作で、全てを「保存」と考えると混乱する。作業ツリー、インデックス、ローカルリポジトリ、リモートリポジトリは別の版を持ちうる。各領域のスナップショットを追うと、ステージ後の編集や未プッシュのコミットを理解できる。
Git の四領域の仕組み
- 作業ツリーはチェックアウトされたファイル群で、エディターが直接読み書きする。
- インデックスはステージング領域とも呼ばれ、次のコミットの候補ツリーを表す。
- ローカルリポジトリはコミットオブジェクトと
mainなどのローカルブランチ参照を持つ。 - リモートリポジトリは独自のオブジェクトと
refs/heads/mainなどの参照を持つ。本レッスンはサーバー側をorigin: mainと表示し、ローカルのmainと区別する。
インデックスは完全な候補スナップショットで、コミット後に空になる待機箱ではない。git add app.py は現在の app.py をインデックスへ書く。その後で編集しても作業ツリーが変わるだけで、インデックスは自動更新されない。
コミットはインデックスが表すツリーを保存し、現在の HEAD を親(最初のコミットには親がない)とし、メッセージを付けてオブジェクトを作り、現在のブランチ参照を動かす。作業ツリー全体を直接読む操作でも、変更だけを袋に詰める操作でもない。同じパスに、古いステージ済み版と新しい未ステージ版が共存できる。
git push はリモートにないコミットなどのオブジェクトを転送し、リモートのブランチ参照を進めるよう要求する。現在の作業ファイルやインデックスを直接アップロードしない。
Git 四領域のステップ
既定では一ファイル、一ブランチだけを使う。
edit app.pyで作業ツリーにprint("one")を書く。他の三領域はまだ空。git add app.pyがprint("one")をインデックスへ書く。作業ファイルはそのまま残り、移動ではなく内容のコピーになる。- 再編集で作業ファイルを
print("two")にする。インデックスはprint("one")のままで、同じパスの版が分かれる。 git commit -m "save staged one"はインデックスから C1 を作るので、内容はprint("one")。ローカルmainが C1 へ進み、作業ツリーのprint("two")は未コミットのまま残る。git push origin mainで C1 をリモートへ送り、そのmainを C1 へ進める。両リポジトリの先端は一致するが、作業ツリーはコミットと異なる。
四番目の操作はエディターの最新版を保存しない。コミットが読むのはステージされたツリーなので、新版を含めるには再び add する必要がある。
各境界で比較するもの
アニメーションは隣接する領域の間に比較状態を表示する。
| 境界 | 状態 | 意味 |
|---|---|---|
| 作業ツリーとインデックス | same | 追跡済みの作業内容がステージ内容と同じ |
| 作業ツリーとインデックス | unstaged | 作業内容が候補スナップショットと異なる |
インデックスとローカル HEAD | nothing staged | ツリーが同じで、通常のコミットに新しいツリーがない |
インデックスとローカル HEAD | staged | インデックスが HEAD のツリーと異なる |
ローカルとリモートの main | up to date | 同じコミットを指す |
ローカルとリモートの main | ahead by N | ローカルから到達でき、リモートのブランチからはまだ到達できないコミットが N 個ある |
これらはスナップショットと参照の比較で、データが常に左から右へ流れるという意味ではない。実際の Git にはインデックスからの復元、参照のリセット、リモートオブジェクトの取得、履歴のマージもある。本レッスンは編集、ステージ、コミット、プッシュの通常経路を扱う。
境界ケース
状態を変えないコマンドも実行できる。それもモデルの動作だ。
app.pyがない状態の add はインデックスを変えない。本レッスンは実際の Git の存在しないパスのエラーと、削除のステージの違いまでは扱わない。- 作業ファイルがインデックスと同じなら、再度 add しても同じ内容で状態は変わらない。
- インデックスが HEAD と同じなら、通常の commit はステージ変更なしと報告する。未ステージの編集があっても変わらない。
- ローカルコミットがなければ、push で転送するものはない。
- 両 main が一致している push は成功するが変更はなく、最新状態を報告する。
分かれたスナップショットのプリセットで即座に commit すると、古いステージ版を保存する。未ステージ変更のプリセットで add せず commit すると、新しいコミットは作られない。二つを比べるとインデックスの役割が分かる。
よくある間違い
- 「add は変更ファイルを知らせるだけ。」 Git は差分を比較できる。add は選んだ内容をインデックスへ書く操作で、再編集した新版を含めるなら再ステージする。
- 「commit はプロジェクト内の全内容を保存する。」 保存するのはインデックスで、未追跡・未ステージ内容は含まれないことがある。
- 「コミットするとステージ領域が空になる。」 通常は新しい HEAD と同じになる。「ステージ変更なし」は差分がないという意味だ。
- 「push は最新ファイルを送る。」 コミットを交渉、転送し、リモート参照を更新する。ローカルコミットに入っていない内容は送れない。
- 「origin/main はリモートそのもの。」 これは fetch などで更新するローカルのリモート追跡参照。サーバーは別の
refs/heads/mainを持ち、本レッスンはorigin: mainと表す。同一参照ではない。
モデルの簡略化
一ファイル、一本の線形ブランチ、C1 などの仮のコミット ID、常に利用できるリモートを使う。blob/tree の詳細、分離 HEAD、マージ、リベース、タグ、上流設定、追跡参照の遅れ、認証、フック、パック、通信障害は省く。実コマンドは複数の境界を越えることもあるが、四領域の区別は残る。
続けて学ぶ
同じ手法につながるレッスンや、次の概念への入口です。
鍵マークのあるレッスンの操作には有料アクセスが必要です。解説はすべて無料で読めます。
誤りや気になる点を見つけましたか? フィードバックを送る。