Gitオブジェクト
gitリポジトリ内のすべてのデータはオブジェクトとして保存されます。オブジェクトは4種類あります。ファイル内容を格納するblob、ディレクトリ一覧を表すtree、メタデータ付きのスナップショットであるcommit、注釈付き参照であるtagです。各オブジェクトは、内容のSHA-1ハッシュで識別されます。
オブジェクトの種類
Blob
blobには、メタデータ(ファイル名や権限)を含まないファイルの生の内容が格納されます。
use Pitmaster\Object\Blob;
// Create from content
$blob = Blob::fromContent('Hello, world!');
echo $blob->content; // 'Hello, world!'
echo $blob->size(); // 13
echo $blob->id->hex; // SHA-1 hash
echo $blob->type->value; // 'blob' (the property is ObjectType::Blob)
ツリー
treeはディレクトリ一覧です。各エントリにはモード、名前、blobまたはサブツリーを指すハッシュがあります。
use Pitmaster\Object\Tree;
use Pitmaster\Object\TreeEntry;
// Read a tree
$tree = $repo->readObject($treeHash);
foreach ($tree->entries as $entry) {
echo "{$entry->mode} {$entry->name} {$entry->hash->hex}\n";
}
// Look up a specific entry
$entry = $tree->entry('README.md');
// Create a tree (entries already in git's sort order)
$entries = [
new TreeEntry('100644', 'README.md', $blobId),
new TreeEntry('100755', 'deploy.sh', $execBlobId),
new TreeEntry('40000', 'src', $subtreeId),
];
$tree = Tree::fromEntries($entries);
Tree::fromEntries()は指定された順序でエントリを書き込み、モード文字列も指定どおりに扱います。そのため、あらかじめ並べ替え、ディレクトリモードには40000を指定してください。040000を使うか、別の順序で書き込むと、gitのものとは異なるハッシュになります。また、git fsck --strictはゼロ埋めされたモードを報告します。
TreeEntry
TreeEntryは読み取り専用の値オブジェクトです:
use Pitmaster\Object\TreeEntry;
$entry->mode; // '100644', '100755', '40000', '120000', '160000'
$entry->name; // Filename
$entry->hash; // ObjectId
// Type checks
$entry->isBlob(); // regular or executable file (mode 10xxxx)
$entry->isExecutable(); // mode 100755
$entry->isTree(); // mode 40000 (also accepts 040000)
$entry->isSymlink(); // mode 120000
$entry->isGitlink(); // mode 160000 (submodule)
モード値:
| モード | 意味 |
|---|---|
100644 |
通常ファイル |
100755 |
実行可能ファイル |
40000 |
ディレクトリ(tree)。git ls-treeでは040000として表示 |
120000 |
シンボリックリンク |
160000 |
Gitlink(サブモジュール) |
treeのエントリは名前順に並べられます。比較の際、ディレクトリ名は末尾に/があるものとして扱われます。
コミット
commitはtreeを履歴とメタデータに結び付けます。
use Pitmaster\Object\Commit;
$commit = $repo->head();
$commit->id; // ObjectId
$commit->tree; // ObjectId of the tree
$commit->parents; // array of ObjectId (zero for root, one for normal, two+ for merge)
$commit->author; // 'Name <email> timestamp timezone'
$commit->committer; // 'Name <email> timestamp timezone'
$commit->message; // Full commit message
$commit->isMerge(); // true if two or more parents
commit内容の構築
$content = Commit::buildContent(
tree: $treeId,
parents: [$parentId],
author: 'Jane <jane@example.com> 1700000000 +0000',
committer: 'Jane <jane@example.com> 1700000000 +0000',
message: "Add feature\n",
);
commitの生の形式:
tree <hex hash>\n
parent <hex hash>\n (zero or more)
author <name> <<email>> <timestamp> <tz>\n
committer <name> <<email>> <timestamp> <tz>\n
\n
<message>
タグ
注釈付きtagはメタデータを持つオブジェクトです。
use Pitmaster\Object\Tag;
$tag = $repo->readObject($tagHash);
$tag->object; // ObjectId of the target
$tag->objectType; // ObjectType enum (ObjectType::Commit, ...)
$tag->name; // Tag name
$tag->tagger; // 'Name <email> timestamp timezone'
$tag->message; // Tag message
tagの生の形式:
object <hex hash>\n
type commit\n
tag <name>\n
tagger <name> <<email>> <timestamp> <tz>\n
\n
<message>
ObjectId
ObjectIdはSHA-1(またはSHA-256)ハッシュを表す読み取り専用の値オブジェクトです。
use Pitmaster\Object\ObjectId;
use Pitmaster\Object\ObjectType;
// From hex string
$id = ObjectId::fromHex('a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2');
// From binary (20 bytes for SHA-1, 32 for SHA-256)
$id = ObjectId::fromBinary($rawBytes);
// Compute from content
$id = ObjectId::compute(ObjectType::Blob, 'Hello, world!');
$id = ObjectId::compute(ObjectType::Blob, 'Hello, world!', 'sha256');
// Zero hash
$id = ObjectId::zero();
$id = ObjectId::zero('sha256');
// Properties
$id->hex; // 40-character hex string (64 for SHA-256)
$id->binary; // 20-byte raw binary (32 for SHA-256)
$id->algo; // 'sha1' or 'sha256'
$id->isSha256();
$id->isZero();
// Comparison
$id->equals($otherId);
ハッシュは圧縮前のデータに対して計算されます:同じバイト列のsha1("{type} {size}、またはSHA-256。fromHex()とfromBinary()は長さに応じてアルゴリズムを選択します。
オブジェクトタイプ列挙型
use Pitmaster\Object\ObjectType;
ObjectType::Blob; // 'blob'
ObjectType::Tree; // 'tree'
ObjectType::Commit; // 'commit'
ObjectType::Tag; // 'tag'
looseオブジェクト形式
looseオブジェクトは.git/objects/内に個別のファイルとして保存され、ハッシュの先頭2文字の16進数で整理されます。
.git/objects/a1/b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
ファイルの内容はzlibで圧縮されます。圧縮前の内容:
<type> <decimal size>\0<raw content>
たとえば、"Hello"を含むblobは次のように保存されます:
zlib_encode("blob 5\0Hello")
looseオブジェクトの読み込み
use Pitmaster\Storage\LooseObjectStore;
$store = new LooseObjectStore('/path/to/.git/objects');
$object = $store->read($objectId);
LooseObjectStoreは次の処理を行います:
- ハッシュからファイルパスを計算
- ファイルを読み込み、zlibで展開
- ヘッダーを解析して種類とサイズを抽出
- 適切なパーサー(
Blob、Tree、Commit、Tag)に処理を委譲
looseオブジェクトの書き込み
$store->write($object);
書き込みの手順:
- シリアライズ:
"{type} {size}\0{content}" - オブジェクト構築時に計算された
idを使用(読み込み時にはハッシュを検証) - 圧縮:
zlib_encode(serialized) .git/objects/{hash[0..1]}/{rest of hash}に書き込み- アトミックに書き込み(一時ファイルに書き込んでから名前を変更)
ObjectSerializer
ObjectSerializerはオブジェクトのワイヤ形式のエンコードとデコードを処理します。
use Pitmaster\Storage\ObjectSerializer;
// Encode
$raw = ObjectSerializer::encodeRaw($object);
// "blob 5\0Hello"
$compressed = ObjectSerializer::encode($object);
// zlib-compressed form, as stored in a loose object file
// Decode
$object = ObjectSerializer::decodeRaw($raw);
$object = ObjectSerializer::decode($compressed, $expectedHex); // throws CorruptObjectException on a hash mismatch
ObjectDatabase
ObjectDatabaseは複合ストアです。まずlooseオブジェクトを検索し、次にpackファイルを検索します。
use Pitmaster\Storage\ObjectDatabase;
$db = new ObjectDatabase('/path/to/.git/objects');
$object = $db->read($objectId); // Search loose, then packs
$db->write($object); // Always writes to loose
$exists = $db->exists($objectId); // Check loose, then packs
$all = $db->listAll(); // All hashes from loose + packs
looseストレージへの書き込みは意図的なものです。git gcにpackファイルへの再パックを任せてください。
SHA-256のサポート
PitmasterはObjectIdを通じてハッシュアルゴリズムを抽象化します。デフォルトはSHA-1です。SHA-256はリポジトリ作成時に選択されます:
$repo = Pitmaster::init('/path/to/project', 'sha256');
./vendor/bin/pitmaster init --object-format=sha256
これにより、extensions.objectformat = sha256が設定に書き込まれます。後で開いたリポジトリはそのエントリから形式を読み込み、以降のすべてのオブジェクト、tree、インデックス、refの操作で64文字の16進文字列と32バイトのバイナリハッシュを使います。このアルゴリズムを切り替えるグローバルな設定や定数はありません。
オブジェクトを自分で構築する場合、オブジェクトクラスには引数としてアルゴリズムを渡します:Blob::fromContent($content, 'sha256')、Tree::fromEntries($entries, 'sha256')、ObjectId::compute($type, $content, 'sha256')。Pitmaster::clone()はSHA-1リポジトリを作成します。
Treeエントリのバイナリ形式
treeオブジェクトの内容では、各エントリは次の形式でエンコードされます:
<octal mode as ASCII> <space> <filename> \0 <binary hash: 20 bytes SHA-1, 32 bytes SHA-256>
注:ハッシュは16進数ではなく、生のバイナリです。commitおよびtagオブジェクト内の16進ハッシュとは異なります。
エントリは、ディレクトリ名の末尾に/があるものとして比較し、並べ替えられます。treeのハッシュをgitの計算結果と一致させるには、この順序を正確に守る必要があります。