大きな画面での利用をおすすめします — アニメーションと操作がより快適になります。
入力

Git の四つの領域

add、commit、push は異なる境界を越える操作で、全てを「保存」と考えると混乱する。作業ツリー、インデックス、ローカルリポジトリ、リモートリポジトリは別の版を持ちうる。各領域のスナップショットを追うと、ステージ後の編集や未プッシュのコミットを理解できる。

Git の四領域の仕組み

  • 作業ツリーはチェックアウトされたファイル群で、エディターが直接読み書きする。
  • インデックスはステージング領域とも呼ばれ、次のコミットの候補ツリーを表す。
  • ローカルリポジトリはコミットオブジェクトと main などのローカルブランチ参照を持つ。
  • リモートリポジトリは独自のオブジェクトと refs/heads/main などの参照を持つ。本レッスンはサーバー側を origin: main と表示し、ローカルの main と区別する。

インデックスは完全な候補スナップショットで、コミット後に空になる待機箱ではない。git add app.py は現在の app.py をインデックスへ書く。その後で編集しても作業ツリーが変わるだけで、インデックスは自動更新されない。

コミットはインデックスが表すツリーを保存し、現在の HEAD を親(最初のコミットには親がない)とし、メッセージを付けてオブジェクトを作り、現在のブランチ参照を動かす。作業ツリー全体を直接読む操作でも、変更だけを袋に詰める操作でもない。同じパスに、古いステージ済み版と新しい未ステージ版が共存できる。

git push はリモートにないコミットなどのオブジェクトを転送し、リモートのブランチ参照を進めるよう要求する。現在の作業ファイルやインデックスを直接アップロードしない。

Git 四領域のステップ

既定では一ファイル、一ブランチだけを使う。

  1. edit app.py で作業ツリーに print("one") を書く。他の三領域はまだ空。
  2. git add app.py が print("one") をインデックスへ書く。作業ファイルはそのまま残り、移動ではなく内容のコピーになる。
  3. 再編集で作業ファイルを print("two") にする。インデックスは print("one") のままで、同じパスの版が分かれる。
  4. git commit -m "save staged one" はインデックスから C1 を作るので、内容は print("one")。ローカル main が C1 へ進み、作業ツリーの print("two") は未コミットのまま残る。
  5. git push origin main で C1 をリモートへ送り、その main を C1 へ進める。両リポジトリの先端は一致するが、作業ツリーはコミットと異なる。

四番目の操作はエディターの最新版を保存しない。コミットが読むのはステージされたツリーなので、新版を含めるには再び add する必要がある。

各境界で比較するもの

アニメーションは隣接する領域の間に比較状態を表示する。

境界状態意味
作業ツリーとインデックスsame追跡済みの作業内容がステージ内容と同じ
作業ツリーとインデックスunstaged作業内容が候補スナップショットと異なる
インデックスとローカル HEADnothing stagedツリーが同じで、通常のコミットに新しいツリーがない
インデックスとローカル HEADstagedインデックスが HEAD のツリーと異なる
ローカルとリモートの mainup to date同じコミットを指す
ローカルとリモートの mainahead 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、マージ、リベース、タグ、上流設定、追跡参照の遅れ、認証、フック、パック、通信障害は省く。実コマンドは複数の境界を越えることもあるが、四領域の区別は残る。