Git バージョン管理の可視化
編集、ステージング、コミット、プッシュは別の領域に作用します。編集は作業ツリーを変えます。git add は選んだ内容をインデックスとオブジェクトデータベースへ書きます。git commit はステージ済みツリーと親を記録します。git push が扱うのはコミットと参照であり、エディター内の未コミットの内容ではありません。
このコース群は四つの領域から始め、オブジェクトデータベースを開き、コミットグラフに動くブランチ名を加えます。マージでは参照の移動だけで済む条件を確かめ、次に分岐したスナップショットを統合します。リベースはパッチを新しい親の列へコピーします。リモートでは二つ目のデータベースを加え、リリースでは古いコミットに名前を付けます。最後に、ファイルのコピー、参照の移動、逆変更のコミットを区別します。
領域の境界
Git の四つの領域では、一つのファイルを作業ツリー、インデックス、ローカル、リモートで追います。重要なのは git add 後の編集です。作業ファイルが two でも、インデックスが one なら次のコミットは one を記録します。ステージングは選択済みの内容を後の編集から分けます。
ローカルコミットとプッシュも別です。リモートが遅れたままローカルにコミットを作れます。作業内容が通常のプッシュで渡るには、到達可能なコミットに含まれている必要があります。
オブジェクトのモデル
スナップショットとしてのコミットはローカルリポジトリを開きます。ファイルのバイトが blob になり、インデックスから tree を作り、commit に tree と parent を記録します。app.py だけを変えると、新しい tree は古い README.md の blob を再利用します。空コミットなら tree 全体を再利用しても、parent が変わるため commit ID は新しくなります。
Git は内容で識別する不変オブジェクトにデータを保存し、移動できる名前で指します。HEAD が動いてもオブジェクトは移動しません。変わるのは参照です。
参照のモデル
ブランチと HEADは参照の変化を示します。最初は main と feature が同じコミットを指します。feature でコミットすると feature だけが進みます。main へ切り替えると HEAD が変わり、両ブランチ参照は動きません。その後のコミットは main だけを進めます。分岐は元のオブジェクトを共有し、履歴を二組コピーしません。
detached HEAD のシナリオでは、ブランチ名を経由しません。新しいコミットで HEAD が直接進み、ブランチは変わりません。保存用ブランチを作らずに切り替えると、そのコミットはブランチから参照されなくなります。ただしオブジェクトは残る場合があり、実際のリポジトリでは reflog から回復できることもあります。
最初のマージ
Fast-Forward マージは、同じ親の列で main が feature より遅れている状態から始めます。Git は取り込み元の先端から親をたどり、現在の先端を探します。祖先関係が証明できれば、main を feature の指す既存コミットへ移すだけです。元の参照は動かず、マージコミットも増えません。
先端が同じ場合や、取り込み元が現在の履歴に含まれる場合は更新済みです。共通の基点から双方に別の子があると、--ff-only は参照を動かさず拒否します。
分岐したスナップショットの統合
三方向マージはこの分岐を扱います。現在の先端から到達できるコミットを記録し、取り込み元の祖先を調べ、このモデルで一意の最良共通祖先を選びます。それが base、両先端が ours と theirs です。
既定例では main が app.py、feature が README.md を変えます。パスごとの規則で変更側の値を採用します。全パスの解決後に結果 tree を書き、二つの旧先端を順序付きの親に持つ commit を作り、main を進めます。取り込み元の参照は固定です。
競合例は同じパスを双方で別の値に変えます。tree、commit、参照への書き込みより前で止まり、統合案の作成と結果の記録を分けます。
競合の解決
マージ競合では停止後の状態を開きます。HEAD と現在のブランチは固定され、MERGE_HEAD が取り込み元の先端を覚えます。インデックスの stage 0 を stage 1/base、stage 2/ours、stage 3/theirs に置き換えます。作業ファイルの競合マーカーは編集可能なバイトにすぎません。
既定の手順はファイルを編集し、インデックスが未解決のままと確認してから、git add app.txt を実行します。三つのエントリーが一つの stage-0 blob にまとまり、git merge --continue が tree と二親コミットを作って main だけを進められます。
編集だけで add しなければ未解決です。一方、マーカー付き内容を add しても Git は受け入れます。ステージするのは解決の意図ではなくバイトだからです。
リベースでブランチを書き直す
リベースは現在のブランチ固有のコミットを探し、古い順にパッチを対象の先端へ適用します。パッチとメッセージは残り、親が変わるため新しいオブジェクトと ID ができます。成功するとブランチ参照が新しい列へ移り、古い列のオブジェクトは残ります。
競合例では回復を支える参照の規則を見ます。一時的な detached HEAD は成功した再適用ごとに進み、名前付きブランチは元の先端に残ります。REBASE_HEAD は失敗したパッチの元コミットを指します。git rebase --abort は元の接続されたブランチへ戻します。途中でブランチ参照を何度も動かしていないため、その連続した移動を逆転する必要はありません。
リモートとの協働
リモートとの協働はローカルの main、ローカルキャッシュの origin/main、リモート自身の main を分けます。共同作業者が先にプッシュすると、ローカルの二参照は古いままです。Fetch は不足オブジェクトをコピーし、origin/main だけを進めます。Fast-forward の pull がローカル main とチェックアウトを更新します。新しいローカルコミットの後、push がオブジェクトを送り、リモートブランチを進め、追跡参照も更新します。
分岐時の pull --ff-only は fetch 結果を保持し、ローカル main の移動を拒否します。Push は古い追跡参照ではなくサーバーの実際の先端を調べます。非 fast-forward の提案は拒否され、リモートの永続状態を変えません。
リリースに名前を付ける
タグとリリースは三つを区別します。軽量タグはオブジェクトを直接指す参照です。注釈付きタグは tag オブジェクトを指し、そのオブジェクトがコミットを指します。ホスティングサービスの release は Git データベース外の記録で、このモデルでは公開時にタグをたどって得たソースコミットを保存します。
既定例では v1.0.0 を公開し、main を進め、古い版をチェックアウトします。ブランチが進んでも、タグとリリースのソースは公開時のコミットに残ります。HEAD はタグの最終対象で detached になります。タグを強制移動する例では、現在のタグ対象と公開記録のソースが食い違います。タグ参照は明示的に書き換えられるためです。
変更を取り消す
変更の取り消しは作業ツリー、インデックス、HEAD の tree、選択コミット、main、ORIG_HEAD を同時に示します。Restore はスナップショットをファイルの層へコピーします。Reset は main を動かし、モードに応じてインデックスと作業ツリーも変えます。Revert は逆変更を求め、新しいコミットを追加します。元の誤ったコミットは祖先に残ります。
既定例はどちらも mode=safe に戻りますが、同じ文字列でも同じ履歴ではありません。Reset は参照を移し、オブジェクトを追加しません。Revert は新規コミットを一つ書き、旧先端への到達可能性を保ちます。Hard reset の例では、ORIG_HEAD が古いコミットを覚えていても、Git に一度も保存していない作業バイトは保護できないことを確認します。
学習の順序
- Git の四つの領域:各コマンドがどの領域を読み書きするかを説明します。
- スナップショットとしてのコミット:実際の Git 形式の ID を見て、tree と commit を区別します。
- ブランチと HEAD:不変オブジェクトの周囲で参照と HEAD が動く様子を追います。
- Fast-Forward マージ:祖先関係を証明してから、現在のブランチだけを動かします。
- 三方向マージ:基点を探し、パスを解決し、tree と二親コミットを作ります。
- マージ競合:インデックスと作業ツリーで解決を確認し、add のコピー境界を理解します。
- リベース:基点と競合の規則を使い、古い順に新しい一親コミットを作ります。全パッチ成功まで名前付きブランチは固定です。
- リモートとの協働:二つのリポジトリでオブジェクト、参照、祖先の規則を使い、ローカルの知識とリモートの事実を分けます。
- タグとリリース:不変オブジェクト、可動参照、detached HEAD、サービスのメタデータを合わせ、公開されたコミットを確かめます。
- 変更の取り消し:スナップショットのコピー、名前の移動、修正の追加を判別します。
Git バージョン管理の全レッスン
各既定トレースの後、サンドボックスで一つずつ前提を試してください。未ステージの編集からコミットする、通常と空コミットを比べる、HEAD を分離して切り替える、線形履歴と包含済み履歴を比べる、競合の有無を変える、マーカー付きファイルをステージする、一回の再適用後にリベースを中止する、ローカルとリモートを分岐させる、公開済みタグを強制移動する、同じファイル内容に戻る hard reset と revert の履歴を比べる、という順です。