
Gitの内部構造入門 - blob・tree・commitとpackfileで理解するバージョン管理の実体
Gitのメンテナ本人によるオブジェクトモデルの解説書。
コマンドの背後にある仕組みを日本語で体系的に。
まず全体像をつかみたい人向けの入門書。
当サイトは Amazon.co.jp を宣伝しリンクすることで紹介料を得る手段を提供する、Amazonアソシエイト・プログラムの参加者です。価格・在庫はリンク先の最新情報をご確認ください。
git rebase で怖い思いをしたことがある人は多いと思います。git reset --hard で消したはずのコミットが git reflog で戻ってきて驚いた人もいるでしょう。こうした挙動は、コマンドを個別に暗記している限りずっと不思議なままですが、.git ディレクトリに何がどう保存されているかを一度見てしまうと、すべて同じ原理の操作として一列に並びます。
この記事では、Gitのオブジェクトモデル(blob・tree・commit)から、ブランチやHEADの正体、.git/index と reflog の実体、そして packfile の delta 圧縮までを、手元でコピペして確認できるコマンドとともに追いかけます。出典は Git 公式ドキュメントと Pro Git です。
NOTE
記事中のコマンド出力は Git 2.52.0(macOS)で実際に実行したものです。執筆時点の最新安定版は 2.55.0(2026年6月29日リリース)です。Linuxでは shasum を sha1sum に、shasum -a 256 を sha256sum に読み替えてください。
Gitは差分ではなくスナップショットを保存している
多くのバージョン管理システムは「ファイルごとの変更差分の集合」として履歴を持ちます。Gitは違います。Pro Git 第1章の "Snapshots, Not Differences" にこう書かれています。
Git thinks of its data more like a series of snapshots of a miniature filesystem. With Git, every time you commit, or save the state of your project, Git basically takes a picture of what all your files look like at that moment and stores a reference to that snapshot. To be efficient, if files have not changed, Git doesn't store the file again, just a link to the previous identical file it has already stored.
コミットのたびにその瞬間のファイル全体のスナップショットを撮り、変わっていないファイルは以前保存した同一ファイルへのリンクで済ませる、という設計です。同章はGitを「a mini filesystem with some incredibly powerful tools built on top of it」(強力なツール群を載せたミニファイルシステム)とまで表現しています。
ここで多くの人が疑問に思うはずです。毎回全ファイルを保存していたら、.git は際限なく肥大化するのではないか、と。答えは後半の packfile にあります。Gitはオブジェクトモデルとしてはスナップショット、ストレージ層としては差分圧縮という2層構造になっています。この2層を混同しないことが、内部構造を理解する最初の一歩です。
最小の実験 - 内容からIDが決まる
まずは空のリポジトリで、ファイル1つのIDを計算させてみます。
mkdir /tmp/gitdemo && cd /tmp/gitdemo && git init -q -b main .
git config user.name "Taro" && git config user.email "taro@example.com"
printf 'Hello, Git\n' > hello.txt
git hash-object hello.txtb7aec520dec0a7516c18eb4c68b64ae1eb9b5a5eこの40桁の16進数がオブジェクトIDです。重要なのは、これがファイル名とは無関係に、中身だけから決まることです。同じ内容のファイルは名前が違っても同じIDになります。いわゆる content-addressable storage(内容アドレス指定ストレージ)で、ハッシュテーブルと同じ発想がファイルシステムの層に持ち込まれていると考えると腑に落ちます。
では、このIDは正確には何のハッシュなのでしょうか。Pro Git 第10章によれば、Gitはファイルの中身そのままではなく、ヘッダを前置したバイト列をハッシュします。ヘッダの形式は "<type> <size>\0" です。実際に手で計算して一致するか確かめられます。
git hash-object hello.txt
printf 'blob 11\0Hello, Git\n' | shasum | awk '{print $1}'b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e
b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e一致しました。Hello, Git に改行を含めた11バイトがサイズなので、ハッシュ対象は blob 11 + ヌルバイト + 本文、というバイト列です。Gitのオブジェクトはすべてこの形式で、blob の部分が tree や commit に変わるだけです。
保存場所と、zlib圧縮された生データ
git hash-object は計算するだけで保存しません。-w を付けるか、git add すると .git/objects/ へ書き込まれます。
git add hello.txt && git commit -q -m "first commit"
ls .git/objects/b7/aec520dec0a7516c18eb4c68b64ae1eb9b5a5eパスは .git/objects/ + IDの先頭2文字 + / + 残り38文字です。ディレクトリを分けているのは、1つのディレクトリにファイルが数十万個並ぶのを避けるためです。ファイルの中身はzlibで圧縮されているので cat しても読めませんが、展開すれば先ほどのバイト列そのものが出てきます。
python3 -c "import zlib;print(repr(zlib.decompress(open('.git/objects/b7/aec520dec0a7516c18eb4c68b64ae1eb9b5a5e','rb').read())))"b'blob 11\x00Hello, Git\n'blob 11、ヌルバイト(\x00)、本文。ヘッダ形式の説明どおりです。実務では毎回zlibを展開する必要はなく、git cat-file が同じことをしてくれます。
git cat-file -t b7aec520 # 型を見る
git cat-file -s b7aec520 # サイズを見る
git cat-file -p b7aec520 # 中身を見るblob
11
Hello, Gitなお圧縮方式は、データ圧縮の仕組みで扱ったLZ77とハフマン符号の組み合わせ(Deflate)がそのまま使われています。
treeオブジェクト - ファイル名とモードはここにある
blobは中身しか持ちません。ファイル名も、実行権限も、ディレクトリ構造も入っていません。それらを持つのが tree オブジェクトです。実行可能ファイルとシンボリックリンク、サブディレクトリを足して確認してみます。
mkdir -p src && printf 'print("hi")\n' > src/app.py && chmod +x src/app.py
ln -sf hello.txt link.txt
git add -A && git commit -q -m "add src and symlink"
git cat-file -p HEAD^{tree}100644 blob b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e hello.txt
120000 blob a5162f80d4a6782b7cb2a0a197f834e683cb9eb1 link.txt
040000 tree a2f361361f94e1881196e64b073849bdbd8b707b srcモード・型・オブジェクトID・ファイル名の4列です。treeが別のtreeを参照することでディレクトリ階層が表現されます。モードは4種類しかありません。
| モード | 意味 |
|---|---|
| 100644 | 通常ファイル |
| 100755 | 実行可能ファイル |
| 120000 | シンボリックリンク |
| 040000 | サブディレクトリ(tree) |
UNIXのパーミッションに似ていますが、Gitが記録するのは実行ビットの有無だけです。所有者もグループも、644以外の細かい権限も保存されません。git ls-tree -r で再帰的に展開すると、サブディレクトリの中身までまとめて見えます。
git ls-tree -r HEAD100644 blob b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e hello.txt
120000 blob a5162f80d4a6782b7cb2a0a197f834e683cb9eb1 link.txt
100755 blob b80e3222ab264bd7cafb376749bd18814fd66776 src/app.pyシンボリックリンクのblob(a5162f8)を git cat-file -p で見ると、中身は hello.txt という9文字のテキストです。Gitはシンボリックリンクを「リンク先のパスを内容として持つファイル」として保存しています。
commitオブジェクト - 親へのポインタがグラフを作る
treeは「ある瞬間のディレクトリ構造」を表しますが、誰がいつ作ったのか、どのコミットの続きなのかは持ちません。それがcommitオブジェクトの役割です。
git cat-file commit HEAD | cat -vtree 5a475f365171b929c9e77c3151d09b124fa3fce2
parent 5c89e29b8746b41a4d7e241b8f941e6663de8279
author Demo User <demo@example.com> 1787106600 +0900
committer Demo User <demo@example.com> 1787106600 +0900
second commitPro Git 第10章の説明どおり、commitオブジェクトは「トップレベルのtree」「親コミット(あれば)」「author/committer情報とタイムスタンプ」「空行」「コミットメッセージ」という素朴な構造です。区切りはすべてLF1個、ヘッダ部とメッセージの間だけ空行が入ります。
タイムスタンプは 1787106600 +0900 のように、Unix epoch秒 + 半角スペース + UTCオフセットという書式です。日時そのものではなく秒数で持つため、タイムゾーンの扱いで悩む余地がありません(この考え方はタイムゾーンと日時処理でも触れたとおりです)。
最初のコミット(root commit)には parent 行がありません。逆にマージコミットには parent 行が2行以上並びます。この parent 行こそがGitの履歴グラフの実体です。コミットは親を指しますが、親は子を知りません。だから履歴は常に新しい方から古い方へ一方向にたどるDAG(有向非巡回グラフ)になります。
[commit C] --parent--> [commit B] --parent--> [commit A]
| | |
tree tree tree
| | |
blob/tree ... blob/tree ... blob/tree ...リポジトリ内の全オブジェクトを型とサイズ付きで一覧すると、blob・tree・commitがどれだけ積み上がっているかが一目で分かります。
git cat-file --batch-check --batch-all-objects2ff847c2032a9ff1693f1e2ee92eeeb4b13a1ac0 commit 214
996f638e1bc5fe472fc23bc5eeb5b232ab566185 tree 37
a2f361361f94e1881196e64b073849bdbd8b707b tree 34
a5162f80d4a6782b7cb2a0a197f834e683cb9eb1 blob 9
b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e blob 11
b80e3222ab264bd7cafb376749bd18814fd66776 blob 12
d41823b50effefc9fa36df9f32a9a252cf5bb532 tree 103
d75ff2d2fd72a43f50774f5f3b6c3eb20e88ff45 commit 159--batch-check のデフォルト出力形式は %(objectname) %(objecttype) %(objectsize) です。
なお、タグにも2種類あります。軽量タグは単なるrefですが、注釈付きタグ(git tag -a)は4つ目のオブジェクト型を作ります。
object a11934ed73c4e4dfb2dee2824ceded7dc87df996
type commit
tag v1.0
tagger Demo User <demo@example.com> 1787108400 +0900
release 1.0refとHEAD - ブランチはただのテキストファイル
ここまでで、オブジェクト同士は参照でつながりました。では「main ブランチの先頭」はどこに記録されているのでしょうか。
cat .git/refs/heads/main
cat .git/HEAD2ff847c2032a9ff1693f1e2ee92eeeb4b13a1ac0
ref: refs/heads/mainブランチの実体は、コミットIDを1行書いただけのテキストファイルです。HEADはそのブランチを指すシンボリックな参照にすぎません。ここが分かると、いくつもの挙動が一気に説明できます。
- ブランチ作成が一瞬で終わるのは、41バイトのファイルを1つ作るだけだから
git switchでブランチを切り替えても履歴は書き換わらない。HEADの中身が別のrefを指すだけ- detached HEAD は、HEADに
ref: ...ではなくコミットIDが直接書かれた状態 git reset --hardは、refの中身を書き換えて作業ツリーを合わせる操作。オブジェクトは消えない
最後の点が重要です。reset で「消えた」コミットオブジェクトは .git/objects に残ったままで、ただどのrefからもたどれなくなっただけです。この状態を到達不能(unreachable)オブジェクトと呼びます。
.git/index - ステージングエリアの実体
git add した内容が入る「ステージングエリア」も、実体は1つのバイナリファイルです。仕様は公式の gitformat-index に定義されています。12バイトのヘッダは、シグネチャ DIRC(dircache の意)、4バイトのバージョン、32ビットのエントリ数という構成です。
head -c 12 .git/index | od -A d -t x1
git ls-files --stage0000000 44 49 52 43 00 00 00 02 00 00 00 03
100644 b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e 0 hello.txt
120000 a5162f80d4a6782b7cb2a0a197f834e683cb9eb1 0 link.txt
100755 b80e3222ab264bd7cafb376749bd18814fd66776 0 src/app.py44 49 52 43 はASCIIで DIRC、続く 00 00 00 02 がバージョン2、00 00 00 03 がエントリ数3です。サポートされているバージョンは2・3・4で、すべてネットワークバイトオーダーです。
git ls-files --stage の出力を見ると、indexが持っているのはモード・オブジェクトID・ステージ番号・パスだとわかります。つまり git add の時点でblobはすでに .git/objects に書き込まれており、indexはそれを指すだけです。git commit は「indexの内容からtreeを作り、それを指すcommitを作り、refを進める」という3ステップに分解できます。
reflog - 失ったコミットを取り戻せる理由
refが書き換わるたび、Gitはその履歴を別途テキストで記録しています。
cat .git/logs/HEAD0000000000000000000000000000000000000000 d75ff2d2fd72a43f50774f5f3b6c3eb20e88ff45 Taro <taro@example.com> 1787101200 +0900 commit (initial): first commit
d75ff2d2fd72a43f50774f5f3b6c3eb20e88ff45 2ff847c2032a9ff1693f1e2ee92eeeb4b13a1ac0 Taro <taro@example.com> 1787104800 +0900 commit: add src and symlinkgit-update-ref のドキュメントによれば、書式は oldsha1 SP newsha1 SP committer LF、メッセージ付きの場合は committer とメッセージの間がタブになります。初回はoldが40桁のゼロです。
これが git reflog の正体です。git reset --hard や git rebase でrefを巻き戻しても、巻き戻す前のコミットIDがこのファイルに残っているため、git reset --hard HEAD@{1} で復帰できます。merge と rebase の使い分けで「やり直せる」と言えるのは、この仕組みが後ろに控えているからです。
ただし永久には残りません。gc.reflogExpire のデフォルトは90日、到達不能なコミットに関するエントリの gc.reflogExpireUnreachable は30日です。後者がより短いのは、git commit --amend などで日常的に大量発生するためです。
packfileとdelta圧縮 - スナップショットなのに小さい理由
冒頭の疑問に戻ります。コミットのたびにスナップショットを保存するなら、.git はどうして小さいままなのでしょうか。答えは、ある程度オブジェクトが溜まると1つのpackfileにまとめて差分圧縮するからです。
git count-objects -v
git gc -q --prune=now
git count-objects -v
ls .git/objects/pack/count: 8
size: 32
in-pack: 0
packs: 0
count: 0
size: 0
in-pack: 8
packs: 1
size-pack: 1
pack-772ae35eb3f2092987713b08a4a7819d5ba1f86d.idx
pack-772ae35eb3f2092987713b08a4a7819d5ba1f86d.pack
pack-772ae35eb3f2092987713b08a4a7819d5ba1f86d.revバラバラだった8個のloose objectが、packfile 1つに収まりました。count がloose objectの数、in-pack がpack内のオブジェクト数です。packファイルの先頭12バイトを見ると、シグネチャとバージョンが確認できます。
head -c 12 .git/objects/pack/*.pack | od -A d -t x10000000 50 41 43 4b 00 00 00 02 00 00 00 0850 41 43 4b はASCIIで PACK、続く4バイトがバージョン2、最後の4バイトがオブジェクト数8です。gitformat-pack によれば、Gitはバージョン2と3を読めますが、生成するのはバージョン2のみです。
pack内のオブジェクトには型番号が振られています。
| 型番号 | 名前 | 内容 |
|---|---|---|
| 1 | OBJ_COMMIT | commit |
| 2 | OBJ_TREE | tree |
| 3 | OBJ_BLOB | blob |
| 4 | OBJ_TAG | 注釈付きタグ |
| 6 | OBJ_OFS_DELTA | 差分(ベースをpack内オフセットで指す) |
| 7 | OBJ_REF_DELTA | 差分(ベースをオブジェクトIDで指す) |
型5は予約、型0は不正です。ポイントは6と7で、gitformat-pack はこう説明しています。
Both ofs-delta and ref-delta store the 'delta' to be applied to another object (called 'base object') to reconstruct the object. ... ref-delta directly encodes base object name. If the base object is in the same pack, ofs-delta encodes the offset of the base object in the pack instead.
つまりオブジェクトモデルとしてはフルスナップショットでも、pack内では「別のオブジェクトへの差分」として保存されるわけです。ベースオブジェクト自体がさらに差分であることも許されており、これを delta chain と呼びます。
実際に同じファイルを十数回書き換えたリポジトリで chain を見てみます。
git verify-pack -v .git/objects/pack/*.idx | awk '$2=="blob"'8daef85f274bdc53cfbb2710e9eaa420e2dbe8e5 blob 18437 2694 1665
443b591f087770b59ae91c8208a742aab9a85dc6 blob 318 166 4359 1 8daef85f274bdc53cfbb2710e9eaa420e2dbe8e5
33ed75cdf6548e411049d24029e6c3eea272aa53 blob 302 186 4525 2 443b591f087770b59ae91c8208a742aab9a85dc6
1b225d35240bdda7a943f61f21c2191b85136672 blob 216 188 6261 12 81d85289a19ff7dd8773a487b21a24a78c4ce470列は左から object-name / type / size / size-in-packfile / offset-in-packfile / depth / base-object-name です。1行目はベース(depth列なし)で18,437バイトを2,694バイトに圧縮していますが、2行目以降は150〜200バイト程度しか消費していません。最終行の depth 12 は、12段の差分を重ねた先にあることを意味します。
差分探索の範囲と深さは設定で決まります。git pack-objects のドキュメントによれば、--window のデフォルトは10、--depth のデフォルトは50、深さの上限は4095です。ウィンドウは「差分の相手を何個先まで探すか」、深さは「何段まで差分を重ねてよいか」です。深くするほど小さくなりますが、復元時に何段もたどる必要があるので読み出しは遅くなります。
NOTE
git pack-objects を単体で呼ぶ場合、OFS_DELTA を使うには --delta-base-offset が必要ですが、repack.useDeltaBaseOffset により git repack や git gc 経由では OFS_DELTA が既定です。ドキュメントは --delta-base-offset について「typically shrinks the resulting packfile by 3-5 per-cent」と述べています。
packが増えたときの索引 - .idx と multi-pack-index
packfileは中身が連続したバイト列なので、そのままでは「あるIDのオブジェクトがどこにあるか」を引けません。そこで同名の .idx ファイルが索引を持ちます。その先頭にあるのが fan-out テーブルで、256個の4バイト整数という単純な構造です。N番目のエントリには「オブジェクトIDの先頭バイトがN以下であるオブジェクトの個数」が入ります。
これにより、探したいIDの先頭1バイトを見るだけで、.idx 内の探索範囲を1/256に絞り込めます。あとはソート済みの範囲を二分探索するだけです。
同じディレクトリに .rev ファイルが生成されていることにも気づいたかもしれません。これは逆引き(pack内オフセットからIDを引く)用の索引で、pack.writeReverseIndex のデフォルトが true なので無設定でも作られます。中身は RIDX というマジックで始まります。
packが何個も増えてくると、今度は「どのpackにあるか」を毎回すべての .idx に問い合わせることになります。それを1つにまとめるのが multi-pack-index です。
git multi-pack-index write
ls .git/objects/pack/
git multi-pack-index verifymulti-pack-index というファイルが .git/objects/pack/ に作られ、verify は問題なければ何も出力しません。
自動でpackにまとめられる条件も設定で決まっています。gc.auto のデフォルトは6700で、「loose objectがおよそこの数を超えると git gc --auto がpackする」という意味です。gc.autoPackLimit のデフォルトは50で、.keep の付いていないpackがこれを超えると1つに統合されます。gc.pruneExpire のデフォルトは 2.weeks.ago なので、到達不能オブジェクトも即座には消えません。
SHA-1からSHA-256へ - 現在地
Gitのオブジェクト名は長らくSHA-1でした。しかし2017年2月23日、実用的なSHA-1衝突(SHAttered)が公表され、移行の必要性が明確になりました。Gitプロジェクトは後継としてSHA-256を選定し、現在は --object-format で選べます。
git init --object-format=sha256 sha256demo
cd sha256demo && git commit -q --allow-empty -m init
git rev-parse HEAD
cat .git/config11d3c4b8a8341a303f1e825a948b264701947b49bbc3e7adb22a70e11c877a63
[extensions]
objectformat = sha256
[core]
repositoryformatversion = 1IDが64桁になり、.git/objects/ のパスも先頭2文字 + 残り62文字になります。ヘッダ形式は変わらないので、printf 'blob 3\0hi\n' | shasum -a 256 が git hash-object と一致することも同様に確認できます。
ただし、ここには重要な但し書きがあります。git-init のドキュメントは今もこう明記しています。
Note: At present, there is no interoperability between SHA-256 repositories and SHA-1 repositories.
SHA-256リポジトリとSHA-1リポジトリの間に相互運用性はまだありません。SHA-1のリモートへpush/fetchすることもできません。相互運用は extensions.compatObjectFormat として開発中で(Git 2.45で着手)、git-config には「the functionality enabled by this extension is incomplete and subject to change ... not designed to be enabled by end users」と書かれています。
一方で「実験的」という表現も今は正確ではありません。Git 2.42のリリースノートは、SHA-256リポジトリへの強い警告文をトーンダウンした理由をこう説明しています。
We do not have support for them to interoperate with traditional SHA-1 repositories, but at this point, we do not plan to make breaking changes to SHA-256 repositories and there is no longer need for such a strongly phrased warning.
まとめると、現在のSHA-256は破壊的変更は予定されていないが、SHA-1との相互運用はまだないという位置づけです。SHA-256リポジトリは古いバージョンのGitでは読めない点にも注意が必要です。なお主要ホスティングサービスの対応状況は各社のドキュメントで確認してください(本記事執筆時点で、公式ドキュメントによる明確な対応表明は確認できませんでした)。
真正性という観点では、公開鍵暗号とデジタル署名で扱ったとおり、ハッシュだけでは「誰が作ったか」は保証できません。コミットの作成者を検証したいなら署名(git commit -S)を併用するのが本筋です。
内部構造がわかると腑に落ちること
ここまでの知識で、日常のコマンドがすべて「オブジェクトを作る」「refを動かす」の組み合わせとして読み直せます。
| コマンド | 内部で起きていること |
|---|---|
git add | 作業ツリーのファイルからblobを作り、indexにモードとIDを登録する |
git commit | indexからtreeを作り、それを指すcommitを作り、現在のブランチrefを進める |
git switch | HEADが指すrefを変え、作業ツリーとindexをそのcommitのtreeに合わせる |
git reset --hard | refの中身を書き換え、indexと作業ツリーを合わせる。オブジェクトは消えない |
git rebase | 元のcommitは残したまま、内容が同じで親が違う新しいcommitを作り直し、refを付け替える |
git cherry-pick | 指定commitの変更を適用した新しいcommitを、現在のブランチの先に作る |
git tag -a | tagオブジェクトを作り、refs/tags/ 配下のrefから指す |
git gc | loose objectをpackfileにまとめ、期限切れの到達不能オブジェクトを削除する |
rebaseが「履歴の書き換え」と呼ばれ、共有ブランチで避けるべきとされる理由も明確です。rebaseは既存のcommitを改変しているのではなく、別IDの新しいcommitを作っているため、他人が元のIDを参照していると履歴が二重化します。この点はmerge と rebase の使い分けで扱った黄金律の根拠そのものです。
逆に、rebase直後に元へ戻せるのも同じ理由です。元のcommitオブジェクトは削除されず、reflogにIDが残っているので、git reset --hard HEAD@{1} でrefを戻せば元通りになります。
まとめ
- Gitのオブジェクトはすべて「型 + サイズ + ヌルバイト + 中身」をハッシュしたIDで識別され、zlib圧縮されて
.git/objects/先頭2文字/残りに置かれる - blobは中身だけ、treeがファイル名とモードと階層、commitがtreeと親とauthor情報を持つ。
parent行が履歴のDAGを形作る - ブランチはコミットIDを1行書いたテキストファイル、HEADはそれを指すref。だからブランチ作成は一瞬で終わり、
resetはオブジェクトを消さない .git/indexはDIRCで始まるバイナリで、モード・ID・パスを持つ。git commitは「index からtree、treeからcommit、そしてref更新」の3ステップ- reflogは
.git/logs/配下のテキストで、refの変遷を記録する。デフォルト保持は90日(到達不能分は30日) - スナップショットモデルでも
.gitが肥大化しないのは、packfileが OFS_DELTA / REF_DELTA として差分圧縮するため。既定は window 10 / depth 50 - SHA-256は選択可能で破壊的変更も予定されていないが、SHA-1との相互運用は現時点で存在しない
Gitのコマンドを個別に暗記するのは大変ですが、オブジェクトモデルとrefという2つの概念に還元してしまえば、あとは組み合わせでしかありません。この記事のコマンドは使い捨てのディレクトリですべて試せるので、git cat-file -p で自分のリポジトリを掘ってみるところから始めてみてください。
参考リンク
- Pro Git: What is Git?(Snapshots, Not Differences)
- Pro Git: Git Internals - Git Objects
- Git Documentation: gitformat-pack(packfile / .idx / .rev の形式)
- Git Documentation: gitformat-index(.git/index の形式)
- Git Documentation: git-init(--object-format と SHA-256 の注記)
- Git Documentation: git-config(gc.auto / pack.window / extensions.compatObjectFormat)
- Git Documentation: git-pack-objects(--window / --depth のデフォルト)
- Git Documentation: git-verify-pack(出力フォーマット)
- Git Documentation: git-count-objects
- Git Documentation: git-update-ref(reflog のレコード書式)
- Git Documentation: git-multi-pack-index
- Git: hash-function-transition(SHA-256移行の設計文書)
- SHAttered(SHA-1の実用的衝突)
- Git の merge と rebase の使い分け(当ブログ)
- ハッシュテーブルの仕組み(当ブログ)
- データ圧縮の仕組み 入門(当ブログ)
- 二分探索アルゴリズム入門(当ブログ)
- 公開鍵暗号とデジタル署名 入門(当ブログ)
- タイムゾーンと日時処理(当ブログ)


