Git は少ない数のシンプルだが強力なアイデアで成り立っています。 それらを理解しなくても git を利用することはできますが、 理解することで git をより直感的に理解できます。
最も重要なコンセプトである オブジェクトデータベース と 索引(index) の説明から開始しましょう、
既に the section called “履歴の理解:コミット” で見てきたように、全てのコミットは 40桁の "オブジェクト名" で格納されています。実際、プロジェクトの履歴を 表現するのに必要な全ての情報は、そのような名前のオブジェクトとして格納されています。 それぞれの名前はオブジェクト内容の SHA-1 ハッシュによって 計算されています。SHA-1ハッシュは暗号学的ハッシュ関数です。 それはつまり、同じ名前を持つ2つの異なるオブジェクトを見つけるのが 不可能であることを意味します。このことは多くの利点を持っています。 とりわけ:
(オブジェクトの形式と SHA1 計算の詳細は the section called “オブジェクトの保管形式” を参照してください)
Gitが扱う オブジェクトには4種類あります:"blob", "tree", "commit" そして "tag" です。
オブジェクトタイプの詳細:
"commit" オブジェクトはツリーの物理的な状態にリンクし、 また、どのようにしてその記述に至ったかの情報も一緒に含んでいます。 —pretty=raw オプション付きで git-show(1) または git-log(1) を 実行すると、特定のコミットの内容を確認できます:
$ git show -s --pretty=raw 2be7fcb476
commit 2be7fcb4764f2dbcee52635b91fedb1b3dcf7ab4
tree fb3a8bdd0ceddd019615af4d57a53f43d8cee2bf
parent 257a84d9d02e90447b149af58b271c19405edb6a
author Dave Watson <dwatson@mimvista.com> 1187576872 -0400
committer Junio C Hamano <gitster@pobox.com> 1187591163 -0700
Fix misspelling of 'suppress' in docs
Signed-off-by: Junio C Hamano <gitster@pobox.com>
このように、コミットは次のように定義されています:
注意:コミット自身は実際にどのような変更がされたかの情報を持っていません; 全ての変更はコミットが参照しているツリーとparents から連想されるツリーとの比較 によって計算されます。特に、git はファイル名の変更を明示的には記録しようと しません。しかし、同じデータをもつファイルが存在する場合に名前変更であると 認識する方法があります。(例えば、git-diff(1) の -M オプションを参照)
コミットは通常 git-commit(1) によって作成されます。 デフォルトではその親を現在のHEADとしたコミットが作成され、 そのツリーは現在の索引に格納されている内容が使用されます。
git-show(1) コマンドは tree オブジェクトに対しても 使用することができますが、git-ls-tree(1) の方が より詳細な情報を表示します:
$ git ls-tree fb3a8bdd0ce
100644 blob 63c918c667fa005ff12ad89437f2fdc80926e21c .gitignore
100644 blob 5529b198e8d14decbe4ad99db3f7fb632de0439d .mailmap
100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3 COPYING
040000 tree 2fb783e477100ce076f6bf57e4a6f026013dc745 Documentation
100755 blob 3c0032cec592a765692234f1cba47dfdcc3a9200 GIT-VERSION-GEN
100644 blob 289b046a443c0647624607d471289b2c7dcd470b INSTALL
100644 blob 4eb463797adc693dc168b926b6932ff53f17d0b1 Makefile
100644 blob 548142c327a6790ff8821d67c2ee1eff7a656b52 README
...
このように、tree オブジェクトは名前順でソートされたエントリの一覧を含んでおり、 各エントリは mode, オブジェクトタイプ、SHA-1名、名前を持っています。 tree は 1つのディレクトリツリーの中身を表現します。
オブジェクトタイプが blob の場合はファイルデータであることを表し、 tree である場合は、サブディレクトリであることを表しています。 tree と blob は他のオブジェクトと同じようにその中身の SHA-1値によって 名前が付けられていて、その中身が(全てのサブディレクトリの内容も含めて)同じ場合 にのみ同じ SHA-1 名となります。この仕組みにより git は2つの関連する tree オブジェクト間の差分を高速に調べることができます。
(注意:サブモジュールが存在する場合、tree は commit をエントリに 持つことがあります。Chapter 8, サブモジュール のドキュメントを参照。)
注意:ファイルは全て 644 または 755 のモードとなります:実際、git は 実行パーミッションだけを管理しています。
git-show(1) を使用すると blob の内容を参照できます; 例として、上記ツリーの blob エントリ "COPYING" を確認します:
$ git show 6ff87c4664
Note that the only valid version of the GPL as far as this project
is concerned is _this_ particular version of the license (ie v2, not
v2.2 or v3.x or whatever), unless explicitly otherwise stated.
...
"blob" オブジェクトはバイナリの blob データであるにすぎません。 参照や属性といったものは持っていません。
blob はそれ自身のデータによって完全に定義されるため、 ディレクトリツリー内(またはリポジトリ内の異なるバージョン)に 2つのファイルがあり、それらが同じ内容であるなら、同じ blob オブジェクトを 共有します。オブジェクトはディレクトリツリーの位置に完全に独立しており、 ファイル名を変更してもそれに対応するオブジェクトは変更されません。
注意:全ての tree と blob オブジェクトは git-show(1) に <revision>:<path> の引数を付けて実行することができます。 これにより、現在チェックアウトしていないツリーの中身を ブラウズすることができます。
あるソースから blob の SHA-1値と(信頼できないかもしれない)ソースの中身 を受け取ったとします。この場合でも SHA-1値が一致する限りはその内容が 正しいと信頼することができます。何故なら SHA-1値は同じハッシュ値を生成 する異なるファイルを見つけることが困難なように設計されているからです。
同様に、トップレベルのツリーオブジェクトの SHA-1値を信頼するだけで それが参照する全ディレクトリの内容を信頼することができます。 また、信頼するソースからコミットのSHA-1値を受け取ったなら、 そのコミットの親から到達可能なコミットの全履歴とコミットが参照する ツリーの内容全てを容易に信頼することができます。
従ってトップレベルのコミット名を含む 一つの 特定のノートに デジタル署名するだけで、システムに信頼性を持たせることができます。 デジタル署名はあなたがそのコミットを信頼していることを示し、 コミット履歴の不変性は全ての履歴が信頼できることを示しています。
言い換えると、トップレベルのコミットのSHA-1値を伝えるメールを作成し、 GPG/PGPでデジタル署名するだけで、容易に全ての履歴の妥当性を示す ことができるということです。
この仕組みを支援するため、gitはtagオブジェクトも用意しています。
tag オブジェクトはオブジェクトとオブジェクトタイプ、タグ名、 タグの作成者、メッセージ(これには署名が付けられることがあります)を 含んでいます。このことは git-cat-file(1) で確認できます:
$ git cat-file tag v1.5.0
object 437b1b20df4b356c9342dac8d38849f24ef44f27
type commit
tag v1.5.0
tagger Junio C Hamano <junkio@cox.net> 1171411200 +0000
GIT 1.5.0
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
iD8DBQBF0lGqwMbZpPMRm5oRAuRiAJ9ohBLd7s2kqjkKlq1qqC57SbnmzQCdG4ui
nLE/L9aUXdWeTFPron96DLA=
=2E+0
-----END PGP SIGNATURE-----
タグの作成方法と検証方法は git-tag(1) コマンドを参照してください。 (注意: git-tag(1) は'軽量'タグを作成することもできます。 これは、tagオブジェクトとは全く異なるもので、'refs/tags/'で始まる 単なる参照です)
新規作成されたオブジェクトは最初はオブジェクトの SHA-1ハッシュ値の 名前でファイルとして保存されます(.git/objects 内に保管されます)。
残念なことに、プロジェクトに大量のオブジェクトが作成されると、 この仕組みでは不十分になります。古いプロジェクトで以下を実行 してみてください:
$ git count-objects
6930 objects, 47620 kilobytes
最初の数字はファイルとして保管されているオブジェクト数です。 二つ目の数字はこれらの "遊離した" オブジェクトによって消費される 容量の合計値です。
これら遊離したオブジェクトを "packファイル" に移動することで ディスク容量を節約し、またgitを高速化することができます。 pack ファイルはオブジェクトのグループを十分に圧縮した形式で保管します: packファイル形式の詳細は technical/pack-format.txt を参照してください。
遊離したオブジェクトを pack に移動するには、git repack を実行するだけです:
$ git repack
Generating pack...
Done counting 6020 objects.
Deltifying 6020 objects.
100% (6020/6020) done
Writing 6020 objects.
100% (6020/6020) done
Total 6020, written 6020 (delta 4070), reused 0 (delta 0)
Pack pack-3e54ad29d5b2e05838c75df582c65257b8d08e1c created.
その後、
$ git prune
を実行すると pack に含まれる全ての "遊離した" オブジェクトは削除されます。 この操作は参照されていないオブジェクト(例えば、"git reset" によって コミットを削除した場合などに作成される)も全て削除します 遊離したオブジェクトが削除されたことは .git/objects ディレクトリを 見るか、次のコマンドを実行することで確認できます。
$ git count-objects
0 objects, 0 kilobytes
オブジェクトファイルが削除されても、そのオブジェクトを参照する 全てのコマンドは以前と同じように動作します。
The git-gc(1) コマンドは repack、prune などを実行してくれるので 通常は高レベルであるこのコマンドのみ使用します。
git-fsck(1) コマンドは dangling オブジェクトに関する メッセージを表示することがありますが、これは問題ではありません。
dangling オブジェクトが作成される主な原因は ブランチをリベースした場合や、 他のユーザがリベースしたブランチを pull した場合です。 — Chapter 5, 履歴を再編集し、一連のパッチを管理する 参照。この場合、ブランチの古い head は まだ存在していて、head が参照していたオブジェクトも全て残っています。 ブランチのポインタは、他の場所に移しかえられているので、存在しませんが。
dangling オブジェクトが作成される他の例もあります。 例えば、ファイルを "git add" したが、そのファイルに別の変更を加えて コミットしたような場合です。 — この場合、もともと add していた内容は、どのコミットとツリーにも 参照されず、dangling blob オブジェクトとなります。
同様に、"再帰的に" マージを実行した際に、マージ内容が複雑で 複数のマージベースが存在するような場合(あまり発生することはありませんが)には、 中間状態のツリーが一時的に作成されます。これら中間状態の作成時に オブジェクトが作成されますが、それらは最終的なマージ結果では 参照されることがありません。従ってこれらも "dangling" となります。
一般に、dangling オブジェクトが存在しても心配する必要はありません。 どちらかといえば、それらは役に立つものです:何か操作間違いをした時に、 dangling オブジェクトを使用すると元の状態に戻すことができます。 (リベースした後に誤りに気が付いた時、 ある古い dangling の状態に head をリセットすることができます)
commit の場合は、次のようにします:
$ gitk <dangling-commit-sha-goes-here> --not --all
これは、指定したコミットから到達可能だが他のブランチやタグ、その他の参照からは 到達できない履歴すべてを表示します。そのうちのどれかが望むものであるなら、 新しい参照を作成します、例えば次のように。
$ git branch recovered-branch <dangling-commit-sha-goes-here>
blob と tree の場合は、同じようにはできませんが、 次のようにして確認することができます。
$ git show <dangling-blob/tree-sha-goes-here>
これは、blob の中身が何であるか(ツリーの場合、ディレクトリの "ls" の内容) を表示するので、その dangling オブジェクトを残すのにどのような操作が 必要となるかを教えてくれるでしょう。
通常、dangling blob と tree はあまり必要になることはありません。 大抵はコンフリクトマーカのついたマージの途中状態であるか、 "git fetch" を Ctrl+C や何かで中断した際に作成されたもので、 宙ぶらりんで役に立ちません。
いずれにしろ、dangling 状態に興味がないことを確認したのなら、 到達できないオブジェクト全てを破棄することができます:
$ git prune
"git prune" は必ず休止状態にあるリポジトリでだけ実行してください。 — これはファイルシステムの fsck によるリカバリのようなものです: ファイルシステムがマウントされている時には行なうべきではありません。
("git fsck" についてもこれと同じことが言えます。ところで、
git fsck
は実際に決してリポジトリを変更することはなく、
検出されたものを報告するだけで、実行しても危険なものではありません。
誰かがリポジトリを変更している最中に実行した場合、
紛らわしく恐ろしいメッセージが表示されますが、悪いことは行なわれません。
それとは反対に "git prune" を他のユーザがリポジトリを変更している最中に
実行するのは 悪い アイデアです。)
git は意図的にデータを慎重に扱います。しかし、git 自身にバグがなかったとしても ハードウェアやオペレーティングシステムのエラーによりデータが壊れることがあります。
そのような問題に対する第1の防御はバックアップです。 clone コマンドを使用するか cp, tar または他のバックアップメカニズムを 使用して git ディレクトリをバックアップすることができます。
最後の手段は、破損したオブジェクトを探し出して手作業で置き換えることです。 破損しかかっている最中であっても、作業をする前にリポジトリを バックアップしてください。
1つの blob が紛失または破損している場合を考えます。 そのような場合は時に解決できることがあります。 (ツリー、そして特にコミットの紛失から復旧する場合はより困難です)
開始する前に、破損している箇所を git-fsck(1) を使用して 確認します;これには多大な時間を必要とするかもしれません。
次のように出力されたとします:
$ git fsck --full
broken link from tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
to blob 4b9458b3786228369c63936db65827de3cc06200
missing blob 4b9458b3786228369c63936db65827de3cc06200
(通常、これらと一緒に "dangling オブジェクト" のメッセージも表示 されますが、これらは興味深いものではありません)
4b9458b3 の blob が紛失しており、2d9263c6 のツリーが それを参照していることがわかります。その紛失したblobのコピーを 他のリポジトリなどから見つけることができるなら、それを .git/objects/4b/9458b3… に移動すれば修復は完了です。もしそれが できない場合でも、git-ls-tree(1) を用いて、それが何を指し示して いるかを確認することができます:
$ git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
100644 blob 8d14531846b95bfa3564b58ccfb7913a034323b8 .gitignore
100644 blob ebf9bf84da0aab5ed944264a5db2a65fe3a3e883 .mailmap
100644 blob ca442d313d86dc67e0a2e5d584b465bd382cbf5c COPYING
...
100644 blob 4b9458b3786228369c63936db65827de3cc06200 myfile
...
これにより、紛失した blob が "myfile" という名前のファイルデータであることが わかります。そして、それがあるディレクトリも特定できたとします— "somedirectory" とします。運よくチェックアウトした作業ツリー内の "somedirectory/myfile" にそれと同じコピーがあるのなら、 git-hash-object(1) を用いてそれが正しいかどうかをテストできます。
$ git hash-object -w somedirectory/myfile
これにより、somedirectory/myfile の内容をもった blob オブジェクトを 作成して格納し、そのオブジェクトの SHA-1 を表示します。 その値が 4b9458b3786228369c63936db65827de3cc06200 であったなら あなたは非常に幸運です。この場合、あなたの推測は正しく、破損は 修復されました!
一致しなかった場合、より詳しい情報が必要になります。 そのファイルのどのバージョンを無くしたかを確認します。
最も簡単な方法は、次のとおりです:
$ git log --raw --all --full-history -- somedirectory/myfile
生データを出力しているため、次のような出力が得られます
commit abc
Author:
Date:
...
:100644 100644 4b9458b... newsha... M somedirectory/myfile
commit xyz
Author:
Date:
...
:100644 100644 oldsha... 4b9458b... M somedirectory/myfile
これはすぐ前のファイルのバージョンが "newsha" であり、すぐ後のバージョンが "oldsha1" であることを示しています。 また、oldsha から 4b9458b の変更点と 4b9458b から newsha での変更点 のコミットメッセージについても知ることができます。
変更内容が十分小さい場合、4b9458b の状態の内容を再作成することが できるかもしれません。
もしそれができたのなら、紛失したオブジェクトを次のようにして 再作成することができます。
$ git hash-object -w <recreated-file>
これにより、リポジトリは正常に戻ります!
(ところで、fsck を無視することもできます。次のようにして、
$ git log --raw --all
紛失したオブジェクト(4b9458b..)の sha を探し出します。 - git はたくさんの情報を持っていますが、紛失したのは1つの特定の blob バージョンであるにすぎません)