Перейти до вмісту

Об'єкти Git

У репозиторії git усе зберігається як об'єкт. Є чотири типи: blob (вміст файлу), tree (списки каталогів), commit (знімки з метаданими) і tag (анотовані посилання). Кожен об'єкт ідентифікується хешем SHA-1 свого вмісту.

Типи об'єктів

Blob

Blob містить необроблений вміст файлу без метаданих (назви файлу чи дозволів).

PHP
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 або піддерево.

PHP
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 — це незмінний об'єкт-значення:

PHP
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); показується як 040000 у git ls-tree
120000 Символічне посилання
160000 Gitlink (підмодуль)

Записи Tree сортуються за назвою; під час порівняння вважається, що назви каталогів закінчуються на /.

Фіксація

Commit пов'язує дерево з його історією та метаданими.

PHP
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

PHP
$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>

Тег

Анотовані теги — це об'єкти з метаданими.

PHP
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).

PHP
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}{content}") або SHA-256 тих самих байтів. fromHex() і fromBinary() вибирають алгоритм за довжиною.

Перелік ObjectType

PHP
use Pitmaster\Object\ObjectType;

ObjectType::Blob;    // 'blob'
ObjectType::Tree;    // 'tree'
ObjectType::Commit;  // 'commit'
ObjectType::Tag;     // 'tag'

Формат окремих об'єктів

Окремі об'єкти зберігаються як самостійні файли в .git/objects/, упорядковані за першими двома шістнадцятковими символами їхнього хешу.

Код
.git/objects/a1/b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

Вміст файлу стиснуто за допомогою zlib. Нестиснений вміст має вигляд:

Код
<type> <decimal size>\0<raw content>

Наприклад, blob із вмістом «Hello» зберігається так:

Код
zlib_encode("blob 5\0Hello")

Читання окремих об'єктів

PHP
use Pitmaster\Storage\LooseObjectStore;

$store = new LooseObjectStore('/path/to/.git/objects');
$object = $store->read($objectId);

LooseObjectStore:

  1. Обчислює шлях до файлу за хешем
  2. Читає файл і розпаковує його за допомогою zlib
  3. Розбирає заголовок, щоб отримати тип і розмір
  4. Передає обробку відповідному аналізатору (Blob, Tree, Commit, Tag)

Запис окремих об'єктів

PHP
$store->write($object);

Процес запису:

  1. Серіалізація: "{type} {size}\0{content}"
  2. Використання id об'єкта, обчисленого під час створення об'єкта (читання перевіряє хеш)
  3. Стиснення: zlib_encode(serialized)
  4. Запис у .git/objects/{hash[0..1]}/{rest of hash}
  5. Атомарний запис (запис у тимчасовий файл, потім перейменування)

ObjectSerializer

ObjectSerializer відповідає за кодування та декодування дротового формату об'єктів.

PHP
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 — це складене сховище, яке спершу шукає окремі об'єкти, а потім — у pack-файлах.

PHP
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

Запис в окреме сховище виконується навмисно. Дозвольте git gc перепакувати об'єкти у pack-файли.

SHA-256 підтримки

Pitmaster абстрагує алгоритм хешування через ObjectId. SHA-1 — типовий варіант. SHA-256 вибирається для кожного репозиторію під час його створення:

PHP
$repo = Pitmaster::init('/path/to/project', 'sha256');
Командна оболонка
./vendor/bin/pitmaster init --object-format=sha256

Це записує extensions.objectformat = sha256 у конфігурацію. Під час подальшого відкриття репозиторії читають формат із цього запису, а для кожної операції з об'єктами, деревами, індексом і refs використовуються 64-символьні шістнадцяткові рядки та 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>

Примітка: хеш — це необроблені двійкові дані, а не шістнадцяткове подання, на відміну від шістнадцяткових хешів в об'єктах commit і tag.

Записи сортуються порівнянням назв так, ніби після назв каталогів стоїть /. Цей порядок сортування має бути точним, щоб хеш дерева збігався з обчисленням git.