Git は不変オブジェクトを保存し、その間で小さな参照を移動します。日常作業では porcelain コマンドを使いますが、 ストレージモデルを知ると、書き換えで新しい履歴が作られる理由と、一定期間リカバリーできる理由を理解できます。
リポジトリデータベースの検査 #
.git ディレクトリにはリポジトリローカルの状態が含まれます。
| パス | 用途 |
|---|---|
HEAD | チェックアウトしたブランチのシンボリック名、または detached object ID |
config | リポジトリ設定 |
index | 次のコミット向けの候補スナップショット |
objects/ | ルーズオブジェクトとパックファイル |
refs/ と packed-refs | ブランチ、タグ、リモート追跡名 |
logs/ | 参照のローカル reflog |
hooks/ | クライアント側フックのサンプルとインストール済みフック |
modules/ | 初期化済みサブモジュールの Git ディレクトリ |
ワークツリーはチェックアウト済みファイルを保持します。index は候補ツリーを記録します。 コミットは親 ID とメタデータとともにツリーを指します。ブランチはコミットへの移動可能な参照であり、変更可能なコミット容器ではありません。
Inspect names and object types without decoding files manually:
git rev-parse --git-dir
git show-ref --head
git cat-file -t HEAD
git cat-file -p HEAD
git count-objects -vHオブジェクトモデルとコンテンツアドレス #
Git は正規バイト列をハッシュしてオブジェクトを識別します。
<type><space><decimal-size><NUL><content>SHA-1 リポジトリは 160 ビットのオブジェクト ID を使います。新しいリポジトリでは SHA-256 を使えます。 オブジェクト ID はコンテンツと構造を識別し、時刻やシーケンス番号をエンコードしません。
Git には 4 つの主要なオブジェクト型があります。
- blob はファイル名なしでファイルのバイト列を保存します。
- tree はパス名とモードを blob およびサブツリーへ対応付けます。
- commit はルートツリー、0 個以上の親、作者とコミッターの ID・時刻、メッセージを指定します。
- annotated tag はタグメタデータを保存し、別のオブジェクトを指します。
tree エントリーモード 100644 は通常の非実行ファイル、100755 は実行ファイル、120000 はシンボリックリンク、
160000 はサブモジュール gitlink です。gitlink はリポジトリのファイルを埋め込むのではなく、別リポジトリのコミットを指します。
Create and inspect objects in a disposable repository:
printf 'hello\n' | git hash-object -w --stdin
git cat-file -p <object-id>
git ls-tree HEAD
git ls-tree -r HEADLoose objects live under .git/objects/aa/bb..., where aa is the first byte
of the hexadecimal ID. Git zlib-compresses their canonical representation.
git cat-file is safer than manually adding compression headers or using
ad-hoc decompression pipelines.
パック、デルタ、到達可能性の理解 #
多数のルーズオブジェクトはファイルシステムのメタデータ操作コストを増やします。
git gc は到達可能なオブジェクトを .git/objects/pack/ のパックファイルへ集約できます。
.pack はオブジェクト表現を保持し、.idx はオブジェクト ID をパックオフセットへ対応付け、
.rev はパック順序を必要とする操作を高速化できます。
パックファイルは他のオブジェクトとの差分でオブジェクトを圧縮できます。デルタはストレージ最適化であり Git の履歴モデルではありません。 各コミットは完全なルートツリーを指定し、Git はオブジェクトグラフを通じて参照された blob を再構築できます。 コミットが保存するのは親との差分テキストだけではありません。
Reachability starts at references such as branches, tags, remote-tracking
branches, and reflogs. Garbage collection retains reachable objects and can
prune unreachable objects after configured expiration. git fsck reports
connectivity and can locate unreachable objects:
git fsck --full --no-reflogs --unreachable
git cat-file -p <unreachable-object-id>参照と index の読み取り #
HEAD には通常 ref: refs/heads/main が含まれます。detached checkout にはオブジェクト ID が直接含まれます。
ルーズなブランチ名は refs/heads/ 下のファイルであり、多数の参照は packed-refs に集約できます。
index は変更ファイルの一覧ではありません。次のツリー向けにパス、モード、オブジェクト ID、stat キャッシュを保存します。 マージ競合中、index は 1 つのパスに複数のステージを持てます。
| ステージ | 意味 |
|---|---|
| 1 | マージベース |
| 2 | 現在のブランチのバージョン |
| 3 | もう一方のブランチのバージョン |
Inspect index entries with:
git ls-files --stage
git diff --cachedResolving a conflict and running git add replaces stages 1–3 with one
stage-0 entry.
書き換えで ID が変わる理由 #
コミットには親オブジェクト ID が含まれます。メッセージ、ツリー、作者、コミッター、タイムスタンプ、親を変更すると別のコミット ID になります。 記録された親が変わるため、すべての子孫を再作成する必要があります。
履歴が改善されるなら、ローカルで未公開の作業は自由に書き換えます。共有ブランチを書き換える前には調整します。
強制更新は共同作業者の新しいリモートコミットを破棄する可能性があるため、force push が必要なら --force-with-lease を使います。
git push --force-with-lease origin feature/topic最新コミットの修正 #
git commit --amend は HEAD の置換コミットを作り、現在のブランチを移動します。
git add README.md
git commit --amend --no-editメッセージを置き換えるには --no-edit なしで git commit --amend を実行します。
元のコミットは reflog の期限切れと prune まで回復可能です。
対話的な古いコミット編集 #
Interactive rebase rewrites the commits after the selected endpoint:
git rebase -i HEAD~3このコマンドは直近 3 件のコミットを提示します。todo コマンドには次が含まれます。
| コマンド | 効果 |
|---|---|
pick | コミットを保持する |
reword | メッセージを置き換える |
edit | 適用後に停止する |
squash | 直前のコミットと結合し、メッセージを編集する |
fixup | 結合してメッセージを破棄する |
drop | 省略する |
To change an older commit’s contents, mark it edit, amend when the rebase
stops, and continue:
git add path/to/file
git commit --amend --no-edit
git rebase --continueResolve conflicts, stage the resolved paths, and continue. git rebase --abort
returns the branch to its pre-rebase state. git rebase --edit-todo changes
remaining instructions while a rebase is paused.
reflog による回復 #
すべてのローカル参照の移動には reflog エントリーを作成できます。危険な書き換えの前に明示的なバックアップ名を作ります。
git branch backup/before-rewriteThen locate and name an earlier state:
git reflog show --date=iso HEAD
git switch -c recovered-work HEAD@{1}reflog は分散バックアップではなく、ローカル実装履歴です。重要な作業は公開し、リモートバックアップを保持し、 回復が完了する前に破壊的なクリーンアップを実行しないでください。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)