[Inside Git #1] Gitはどのようにバージョン管理をしているのか?
会社の同期の方と一緒に勉強会をしていて、せっかくなので発表内容を記事としてまとめることにしました。
ある時、ふと、Gitはどのように過去のファイルの状態を記憶しているんだろう?と疑問に感じました。今回はその疑問について調べた内容、Gitオブジェクトについてまとめていこうと思います!!
環境
- MacOS Monterey 12.3.1
- git version 2.35.1
対象読者
- Gitを多少触ったことがあれば理解できると思います。
.git
の誕生
まず、Git管理下に置きたいディレクトリの中でgit init
を実行すると.git
という隠しディレクトリが作成されます。Gitはこの.git
ディレクトリの中のファイルを操作することで、Git管理下にあるファイル群のバージョン管理をしています。
試しにrepoというディレクトリを作成して、git init
してみましょう!
% mkdir repo; cd repo
% git init
% ls -a # .gitディレクトリが作成されている
. .. .git
GitにはGitオブジェクトという超重要概念があります。Gitでのバージョン管理とは、このGitオブジェクトの管理と言っても過言ではありません。
このGitオブジェクトは.git
ディレクトリの中のobjects
というディレクトリの中に保存されていきます。今回は、このGitオブジェクトがどんなものなのかを観察していき、Gitがどのようにバージョン管理をしているのかを簡単に説明したいと思います!
.git
ディレクトリの中には他にも、HEADが現在何を参照しているかが記載されているHEAD
ファイルや、各ブランチがどのコミットを参照しているかなどの様々な参照情報が記載されているrefs
ディレクトリなどがこの.git
の中に保存されています。.git
の中身について深追いすることはしませんが、これらHEAD
やrefs
についても必要最低限、軽く解説しようと思っています!
% ls -F1 .git
HEAD
config
description
hooks/
info/
objects/
refs/
コミットの正体
まずはいくつか適当なコミットを積んで、その過程で出来上がったコミットを観察していくことにしましょう。
% touch README.md
% git add -A && git commit -m 'README.mdの作成'
% touch curry-ingredients.md
% git add -A && git commit -m 'curry-ingredients.mdの作成'
% echo '# カレーのレシピ' > README.md
% git commit -am 'README.mdの更新'
% echo '- にんじん' >> curry-ingredients.md
% echo '- じゃがいも' >> curry-ingredients.md
% echo '- カレールー' >> curry-ingredients.md
% git commit -am 'curry-ingredients.mdの更新'
% tree
.
├── README.md
└── curry-ingredients.md
0 directories, 2 files
まずは最新のコミットの情報をgit log
で調べてみます。
% git log -1
commit bf734d3ec625afdec625acfd010f084501ba6305 (HEAD -> main)
Author: Sayama <sayama@example.co.jp>
Date: Sun Jan 15 15:10:39 2023 +0900
curry-ingredients.mdの更新
このbf734d3ec625afdec625acfd010f084501ba6305
という意味の分からない文字列[1]は、あるGitオブジェクトのハッシュ値[2]になっています。実際のGitオブジェクトは
.git/objects/bf/734d3ec625afdec625acfd010f084501ba6305
というファイルに記されています[3]。しかしこのファイルはバイナリ形式になっているため、直接覗くことは難しいです。しかしgit cat-file
というコマンドが用意されていて、これを使うことで中身を詳しく見ることができるので、早速覗いてみましょう!
% git cat-file -t bf734d3 # -tでGitオブジェクトのタイプが見れます
commit
% git cat-file -p bf734d3 # -pでGitオブジェクトの中身が見れます
tree 433babe388be6759d8e6d51e2b8c388854e51324
parent bb25ab1539bfcd5f80ff7d37f60b43a643306300
author Sayama <sayama@example.co.jp> 1673763039 +0900
committer Sayama <sayama@example.co.jp> 1673763039 +0900
curry-ingredients.mdの更新
このGitオブジェクトはcommit
というタイプで、author
やcommitter
の情報、コミットメッセージなどが記載されていることがわかります。他に、2つの重要な情報が記載されています。
parentという項目にあるGitオブジェクト:bb25ab1539bfcd5f80ff7d37f60b43a643306300
treeという項目にあるGitオブジェクト:433babe388be6759d8e6d51e2b8c388854e51324
一旦、tree項目に書かれた謎のGitオブジェクトは無視しましょう。parentという項目にあるGitオブジェクトを再度調べてみます。
% git cat-file -t bb25ab1
commit
% git cat-file -p bb25ab1
tree d13fd5f50504dfa30597a297bb65d1581c4623dd
parent a4c662bed612919fac1f6171ffbb65f783f85fc6
author Sayama <sayama@example.co.jp> 1673763016 +0900
committer Sayama <sayama@example.co.jp> 1673763016 +0900
README.mdの更新
またしてもcommit
というタイプのGitオブジェクトでした。このタイプのGitオブジェクトのことを我々はコミットと呼んでいます。コミットはコミットメッセージなどの情報の他に一つ前のコミットである親コミットのハッシュ値を持っています。
このように最新のコミットは、ひとつ前の親コミットを参照し、そのコミットはまたひとつ前の親コミットを参照し、と言った具合に参照の列を作っています。
この参照の列によりバージョンの時間的経過を表すことができます。コミット、すなわちcommit
タイプのGitオブジェクト全体のことを、ヒストリーまたはタイムラインと言います。
一旦、分かったことを簡単にまとめると、bf734d3
というコミットはbb25ab1
という親コミットを参照していて、bb25ab1
というコミットはa4c662b
という親コミットを参照していて、というような参照の列があるという事がわかりました。
% git log --oneline
bf734d3 (HEAD -> main) curry-ingredients.mdの更新
bb25ab1 README.mdの更新
a4c662b curry-ingredients.mdの作成
38180f9 README.mdの作成
この節のまとめ
- コミットの正体は
commit
というタイプのGitオブジェクトのことだった。 - コミットはひとつ前の親コミットを参照していて、その親コミットはそのまた親コミットを参照していて、と参照の列が作られている。
- 出来上がった参照の列がバージョンの時間的経過を表す。
ブランチの正体
前節では、コミットとは何かということをみました。
続いてはブランチとは何かについて観察していこうと思います。
後で比較するために、まずは.git/refs/heads
ディレクトリの中身を見ておこうと思います。
% ls .git/refs/heads
main
% cat .git/refs/heads/main
bf734d3ec625afdec625acfd010f084501ba6305
カレーの材料に鶏肉を入れ忘れていたので、meat
ブランチを作成してコミットを積んでおきましょう。
% git switch -c meat
% echo '- 鶏肉' >> curry-ingredients.md
% git commit -am '材料に鶏肉を追加'
% git log --oneline
f7986e8 (HEAD -> meat) 材料に鶏肉を追加
bf734d3 curry-ingredients.mdの更新
bb25ab1 README.mdの更新
a4c662b curry-ingredients.mdの作成
38180f9 README.mdの作成
% git switch main
% echo '美味しいカレーを作ろう!' >> README.md
% git commit -am 'README.mdの更新'
% git log --oneline
8add7f0 (HEAD -> meat) README.mdの更新
bf734d3 curry-ingredients.mdの更新
bb25ab1 README.mdの更新
a4c662b curry-ingredients.mdの作成
38180f9 README.mdの作成
ブランチの実態を見るために.git/refs/heads
の中を覗いてみると、新たにmeat
というファイルが作成されていることがわかります。
% ls .git/refs/heads
main meat
% cat .git/refs/heads/main
96dcb8ee86c2c6f10c913f4d4b64e442cb2b3d33
% cat .git/refs/heads/meat
f7986e8e9bd2011d4433cd81ff383b137c538523
これらのmain
とmeat
ファイルの中身からも分かるように、ブランチとはあるコミットへの参照 (リファレンス) のことです。ブランチというと、根本から枝先までの一連のコミット群をイメージしてしまいますが、そうではなくただの参照値のことで、もっと雑な言い方をすればただの文字列です。従って、1000個くらいブランチを作ったとしても、データのサイズは大して増えません。
もうひとつ重要なリファレンスがあります。それがHEADです。HEADは現在のバージョンを指し示すリファレンスのことです。HEADが指す参照値は.git/HEAD
というファイルに記載されています。
% cat .git/HEAD
ref: refs/heads/main
どうやらHEADはmainブランチを参照しているようです。
色々とスイッチしていくとHEAD
ファイルの中身も変化していきます。
% git switch meat # meatブランチへスイッチ
% cat .git/HEAD
ref: refs/heads/meat
% git switch bb25ab1 --detach # bb25ab1コミットへスイッチ
% cat .git/HEAD
bb25ab1539bfcd5f80ff7d37f60b43a643306300
このようにHEADはブランチを参照することもあれば、コミットを直接参照することもあります。
HEADが直接コミットを参照している状態のことをdetached HEADというふうに言います。
detached HEADの状態でコミットを積むこともできますが、特に何もせずに既存のブランチにスイッチしてしまうと、誰からも参照されないコミットが出来上がってしまいます。そして、参照の向きから考えれば、あなたが数日後にこのコミットを見つけようと思っても、かなり厳しいんじゃないでしょうか。このようなごみコミットは時間が経てばGitのガベージコレクションが掃除してくれますが、あまり気持ちのいい状態とは言えません。detached HEADの状態にする時は、このようなことを分かった上で行うべきですね。
この節のまとめ
- ブランチはあるコミットを参照するだけのリファレンス。
- HEADはブランチやコミットを参照するだけのリファレンスで、現在の状態を表す。
Gitオブジェクトの正体
treeオブジェクト / blobオブジェクト
ところで、少し前にcommitオブジェクトの中身を調べた時に、treeという項目に謎のGitオブジェクトのハッシュ値が書かれていました。ここではこの謎のGitオブジェクトを調べていこうと思います!
調べる前に、説明のために使いたいコミットをひとつだけ積もうと思います。
% git switch main
% mkdir dir
% echo '# カレーのレシピ\n美味しいカレーを作ろう!' > dir/sample.txt
% git add -A && git commit -m 'README.mdと同じ内容のファイルを作成'
% tree
.
├── README.md
├── curry-ingredients.md
└── dir
└── sample.txt
1 directory, 3 files
では早速調べていきましょう!
% git log --oneline -1
845a32f (HEAD -> main) README.mdと同じ内容のファイルを作成
% git cat-file -p 845a32f
tree 0cdbafebf15332c0788686f2457a87d8ea3ddbf5 # こいつを調べます
parent 8add7f0af64c4166aa39f3b2430c07c2f3bd3d4f
author Sayama <sayama@example.co.jp> 1673766412 +0900
committer Sayama <sayama@example.co.jp> 1673766412 +0900
README.mdと同じ内容のファイルを作成
% git cat-file -t 0cdbafe
tree
% git cat-file -p 0cdbafe
100644 blob 944b8ef2e83aea596fd2a662d629042f3e92edc3 README.md
100644 blob 87f3f8afa28796b2eeda4094bee471acbde78dcc curry-ingredients.md
040000 tree 6fc8f11b5d479640d1c79f9f8697c35f66d08f67 dir
Gitオブジェクト0cdbafe
はtree
というタイプだということがわかりました。今後はこのタイプのGitオブジェクトのことを簡単にtreeオブジェクトということにします。
treeオブジェクト0cdbafe
の中身を見てみると3つのGitオブジェクトについての情報が記載されていそうですね。多分こんな感じの内容なんじゃないでしょうか。
-
README.md
に対応していそうなGitオブジェクト944b8ef
。おそらくタイプはblob
。 -
curry-ingredients.md
に対応していそうなGitオブジェクト87f3f8a
。おそらくタイプはblob
。 -
dir
に対応していそうなGitオブジェクト6fc8f11
。おそらくタイプはtree
。
blob
という新たなタイプと思われるGitオブジェクトも登場したので、本当にそうなのか、こちらもgit cat-file
で詳しく調べておきましょう!
% git cat-file -t 944b8ef
blob
README.md
に対応していそうなGitオブジェクト944b8ef
は、確かにblob
というタイプだということがわかりました!今後はこのタイプのGitオブジェクトのことを簡単にblobオブジェクトということにします。
さて、これでtreeオブジェクトとblobオブジェクトの存在を確認することができましたが、これらは一体何者なのでしょう?もう一度、treeオブジェクト0cdbafe
の中身をよく観察してみましょう。
% git cat-file -p 0cdbafe
100644 blob 944b8ef2e83aea596fd2a662d629042f3e92edc3 README.md
100644 blob 87f3f8afa28796b2eeda4094bee471acbde78dcc curry-ingredients.md
040000 tree 6fc8f11b5d479640d1c79f9f8697c35f66d08f67 dir
% tree
.
├── README.md
├── curry-ingredients.md
└── dir
└── sample.txt
1 directory, 3 files
すると、treeオブジェクト0cdbafe
に書かれている3つのファイル/ディレクトリ名は、Git管理下 (repo/) の直下にある3つのファイル/ディレクトリ名と一致していることが分かります!
さらに、ファイルはblobオブジェクトに、ディレクトリはtreeオブジェクトに対応していることも分かります!
blobオブジェクト944b8ef
の中身についてもよく観察してみましょう!
% git cat-file -p 944b8ef
# カレーのレシピ
美味しいカレーを作ろう!
% cat README.md
# カレーのレシピ
美味しいカレーを作ろう!
すると、README.md
に対応していそうなblobオブジェクト944b8ef
の中身はまさしく、README.md
の中身そのものだという事が分かります!
以上の観察を簡単にまとめてみます。
- treeオブジェクトはディレクトリに対応していそうで、その中身には対応するディレクトリ直下にあるファイル/ディレクトリに対応するGitオブジェクトが書かれている。
- blobオブジェクトはファイルに対応していそうで、その中身には対応するファイルの中身が書かれている。
実はこれらの予想は正しく、treeオブジェクトはディレクトリを表すGitオブジェクトのことです。中にあるファイルやディレクトリの種類/権限を表す6桁の数字 (100644, 040000)、それらファイルやディレクトリに対応するGitオブジェクトのタイプ (blob, tree)、それぞれのGitオブジェクトのハッシュ値 (944b8ef..., 87f3f8a...)、ファイル名/ディレクトリ名 (README.md, curry-ingredients.md) が記載されていることがわかります。ここでひとつポイントになっている点は、treeオブジェクトは自分自身のディレクトリ名の情報を持っていないことです。
そしてもちろん、blobオブジェクトとはファイルを表すGitオブジェクトのことです。blobオブジェクトの中にはファイルの中身そのものが記載されています。また、ここでもポイントになるのが、このblobオブジェクトは自分自身のファイル名の情報を持っていないということです。
最後に他のtreeオブジェクト/blobオブジェクトの中身も観察して、この節で分かったポイントをまとめておきましょう。
# dirに対応するtreeオブジェクト6fc8f11について
% git cat-file -p 6fc8f11
100644 blob 944b8ef2e83aea596fd2a662d629042f3e92edc3 sample.txt
% tree dir
dir
└── sample.txt
0 directories, 1 file
# curry-ingredients.mdに対応するblobオブジェクト87f3f8aについて
% git cat-file -p 87f3f8a
- にんじん
- じゃがいも
- カレールー
% cat curry-ingredients.md
- にんじん
- じゃがいも
- カレールー
この節のまとめ
- commitタイプのGitオブジェクト(コミット)には、親コミットのハッシュ値以外に、ルートディレクトリに対応したtreeオブジェクトのハッシュ値も記載されている。
- treeタイプのGitオブジェクトは、ディレクトリに対応していて、自身に含まれるファイルやディレクトリに対応するGitオブジェクトのハッシュ値と、それらの名前が記載されている。ただし自分の名前は知らない。
- blobタイプのGitオブジェクトは、ファイルに対応していて、自身の中身が記載されている。ただし自分の名前は知らない。
treeやblobオブジェクトは自分の名前を知らなくてもいい
treeオブジェクトやblobオブジェクトがディレクトリやファイルに対応しているが、自分自身の名前の情報を持っていないことを上で観察しました。自分の名前を知らない、これらtreeオブジェクトやblobオブジェクトから元のディレクトリ・ファイル群を再現できないのでは?と一瞬思うかもしれません。しかし、無問題です!
あるblobオブジェクトは、対応するファイルの中身を知っています。そしてそのファイル名は、このblobオブジェクトを含むtreeオブジェクトに記載されています。このtreeオブジェクトに対応するディレクトリの名前は、その親のtreeオブジェクトに記載されていて、その親の名前のそのまた親に記載されて、という具合に、ルートディレクトリに対応するtreeオブジェクトまで遡れば、全てのファイルの名前と中身、全てのディレクトリの名前と中身がきちんと再現できることがわかります。そしてルートディレクトリに名前は必要ありません。
この節のまとめ
- コミットはルートディレクトリに対応するtreeオブジェクトを参照している。
- treeオブジェクトはディレクトリに対応している。
- blobオブジェクトはファイルに対応している。
- これらtreeオブジェクトとblobオブジェクトがあれば、ディレクトリ・ファイル群の状態を再構築できる。
【余談 ☕️ 】参照の向きと、親子
commitオブジェクトの時は、参照先を親コミットと呼びましたが、tree/blobオブジェクトの時は参照元のtreeオブジェクトのことを親と呼びました。
親コミットという言い方は一般的ですが、tree/blobオブジェクトに関しては僕のオリジナルです。ディレクトリ構造を考えたときに、サブディレクトリを子と呼びたい気持ちから、参照元を親と呼んでいます。
ハッシュ値の計算
先ほど、treeオブジェクトを調べた際に、README.md
とdir/sample.txt
に対応するblobオブジェクトのハッシュ値が全く同じものだったことに気づいてましたか?
% git cat-file -p 0cdbafe
100644 blob 944b8ef2e83aea596fd2a662d629042f3e92edc3 README.md # ハッシュ値が全く同じ!
100644 blob 87f3f8afa28796b2eeda4094bee471acbde78dcc curry-ingredients.md
040000 tree 6fc8f11b5d479640d1c79f9f8697c35f66d08f67 dir
% git cat-file -p 6fc8f11
100644 blob 944b8ef2e83aea596fd2a662d629042f3e92edc3 sample.txt # ハッシュ値が全く同じ!
実はblobオブジェクトのハッシュ値は、そのファイルの中身の情報だけから計算されます。自分自身のファイル名などの情報は一切使いません!
あなたのPCで、これらREADME.md
と同じ内容のファイルを作成したなら、僕と全く同じハッシュ値をもつblobオブジェクトが作成されているはずです!
もうひとつ例を見ておきましょう。例えば、初めの方のコミットで2つの空ファイルを追加しています。対応する空ファイルのハッシュ値はやはりどちらもe69de29
になっています[4]。
% git cat-file -p a4c662b
tree eda14422e5a939043e04ea00fc403e0ee1798a44 # 空のファイルが2つあるはず
..
% git cat-file -p eda1442
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 README.md # ハッシュ値が全く同じ!
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 curry-ingredients.md # ハッシュ値が全く同じ!
このように、ファイルの中身だけを考えていれば、「同じ中身なのにファイル名が違うため異なるblobオブジェクトが必要」というような無駄が無くなります。Gitオブジェクトが節約できるんですね!
treeオブジェクトのハッシュ値も全く同じ考え方で計算されています。treeオブジェクトの中に書かれている、ファイル/ディレクトリ名やそれらに対応するGitオブジェクトのハッシュ値から、自身のハッシュ値が計算されています。つまり自分自身のディレクトリ名の情報はハッシュ値の計算に全く使用されません!
もしあなたが一緒に手を動かしながら、この記事の内容を辿ってきたなら、あなたのPCで計算されたblobオブジェクト、treeオブジェクトのハッシュ値は全く同じものになっているはずです!
一方で、commitオブジェクトのハッシュ値はおそらく僕のそれと別の値になっていると思います。
これはcommitオブジェクトのハッシュ値が、author
やcommitter
などの情報も含めて、計算されているためです。僕とあなたとでは、名前もメールアドレスも、コミットした時間も違うため異なるハッシュ値になります。
この節のまとめ
- Gitオブジェクトはその中身の情報だけからハッシュ値が計算されている。
変化の伝播とファイル群の再現
最後に、Gitがどのようにコミット単位でファイル群の状態を記憶しているのかを説明して終わりにしたいと思います。
あるファイルの中身が変更されると、その変更されたファイルのblobオブジェクトの中身も変更されるわけですから、そのハッシュ値が変化します。すると、親のtreeオブジェクトに記載されているblobオブジェクトのハッシュ値も変更されるため、もちろんそのtreeオブジェクトのハッシュ値も再計算され、別のハッシュ値が与えられます。すると、ハッシュ値が変化したこのtreeオブジェクトの親treeオブジェクトも、同様の理由により新たなハッシュ値が与えられます。この変化の伝播が繰り返されると必ずルートディレクトリに対応するtreeオブジェクトのハッシュ値が変化します。
これはもちろん、あるディレクトリの名前を変更した時も同様です。treeオブジェクトのハッシュ値の変更が木の根本まで伝播していき、最終的には必ずルートディレクトリに対応するtreeオブジェクトのハッシュ値が変化します。
事実1
「blobオブジェクトやtreeオブジェクトのハッシュ値が、その中身から計算されていること」
「treeオブジェクトには子であるblob/treeオブジェクトのハッシュ値が記載されていること」
この2つの事実から、「木構造の末端であるblobオブジェクト(ファイル)や枝の途中であるtreeオブジェクト(ディレクトリ)が更新されると、最終的にルートディレクトリに対応するtreeオブジェクトのハッシュ値が変化する」 ということがわかりました。もう少し別の言い方をすると、「その時のファイル群の状態に応じて、ルートディレクトリに対応するtreeオブジェクトのハッシュ値が一意[5]に決められる」 ということがわかりました。
そして、commitオブジェクト (コミット) にはルートディレクトリに対応するtreeオブジェクトのハッシュ値が記載されているので、そのコミットから、その時のファイル群の状態を再構築することができます。
事実2
「commitオブジェクトは、ルートディレクトリに対応するtreeオブジェクトを参照している」
「ルートディレクトリに対応するtreeオブジェクトから、その時のファイル群の状態を再構築できる」
この2つの事実から、「Gitはコミット単位でファイル群の状態を覚えておくことができる」 ということがわかりました。
例えば、meat
ブランチにスイッチすると、HEAD
がmeat
ブランチを参照します。そしてmeat
ブランチはあるコミットを参照しています。そしてそのコミットは、ルートディレクトリに対応するtreeオブジェクトを参照しています。このtreeオブジェクトからその時のファイル群の状態を再構築できます。
事実3
「HEAD
は現在の状態を表す」
「適当なブランチにスイッチするとHEAD
の参照先が変化する」
「ブランチはあるコミットを参照している」
「コミットからその時の状態を再現することができる」
この4つの事実から、「適当なブランチにスイッチすると、その時のファイル群の状態に戻すことができる」 というよく知られた事実が再確認できました!
紹介しなかった部分
- Gitオブジェクトには
commit
、tree
、blob
の他にもtag
というタイプのGitオブジェクトがあります。 - indexについては全く触れませんでした。
- Gitオブジェクトの圧縮や中身のフォーマットについても触れませんでした。
その他諸々、紹介しきれなかった部分、もしくはただ僕が知らないだけの部分、などなどたくさんあるかもしれませんが、今回はここで終わろうと思います。ありがとうございました!
参考にしたもの
今回の記事を書くのに、以下のサイトを参考にしました。
こせきの技術日記 Gitの仕組み(1)
非常にわかりやすい記事で、僕の記事はただの縮小再生産のようなものです。一応、僕の言葉でわかりやすい説明内容を目指しましたが、「よく分からんかった」「もっと詳しく知りたい」という方は、この種記事の方も参考にしてみてください。超面白かったです。
-
あなたのPCで同じ作業をしたとしても、このコミットのハッシュ値は異なる値になっているはずです。しかし、この記事の中に出てくる一部のGitオブジェクトのハッシュ値はあなたのPCで作成したものと同じになるものがあります。Gitオブジェクトの理解につながると思うので、是非あなたのPCでも試してみてください! ↩︎
-
ハッシュ値とはざっくり言うと、ある値から計算される整数値のことです。今回はGitオブジェクトから16進数表示された整数値である
bf734d3ec625afdec625acfd010f084501ba6305
のような値を計算して、これをこのGitオブジェクトのハッシュ値と呼んでいます。ハッシュ値はできるだけ被らないことが大切です。例えば全く別のGitオブジェクトAとBについて、同じハッシュ値になってしまってはGitシステムくんはどっちがどっちのGitオブジェクトなのか区別をつけることができなくなってしまいます。しかし今回は40桁の16進数ということですから、うまくハッシュ値を計算することができているなら、Gitオブジェクトのハッシュ値が被ってしまう確率は、あなたが毎年連続で年末ジャンボ宝くじに当たる確率より遥かに低いです。どうせ一回も当たったことないと思うので安心してください! ↩︎ -
bf
が分かれてディレクトリ名になっているのは、おそらく検索性を高めるためだと思っています。しかし、実際の理由は知りません。 ↩︎ -
空ファイルのようなあるあるファイルのハッシュ値は、世界中の様々なPCで全く同じものが作成されています。このハッシュ値を検索してみると、いくつかヒットするので、確かにblobオブジェクトのハッシュ値はその中身にしか依存していないんだなということが実感できます。 ↩︎
-
被りなくという意味です。 ↩︎
Discussion