Ga naar de inhoud

Git-objecten

Alles in een git-repository wordt als object opgeslagen. Er zijn vier typen: blobs (bestandsinhoud), trees (mapvermeldingen), commits (momentopnamen met metadata) en tags (geannoteerde verwijzingen). Elk object wordt geïdentificeerd aan de hand van de SHA-1-hash van de inhoud.

Objecttypen

Blob

Een blob bevat ruwe bestandsinhoud zonder metadata (geen bestandsnaam of rechten).

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

Een tree is een mapvermelding. Elke vermelding heeft een modus, naam en hash die naar een blob of sub-tree verwijst.

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() schrijft de vermeldingen in de opgegeven volgorde en de modustekenreeksen precies zoals opgegeven. Sorteer ze daarom eerst en schrijf mappen als 40000. Een tree die met 040000 of in een andere volgorde wordt geschreven, krijgt een andere hash dan die van git, en git fsck --strict geeft de modus met voorloopnullen weer.

TreeEntry

TreeEntry is een alleen-lezen waardeobject:

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)

Moduswaarden:

Modus Betekenis
100644 Gewoon bestand
100755 Uitvoerbaar bestand
40000 Map (tree); weergegeven als 040000 door git ls-tree
120000 Symbolische koppeling
160000 Gitlink (submodule)

Tree-vermeldingen worden op naam gesorteerd. Voor de vergelijking worden mapnamen behandeld alsof ze eindigen op /.

Commit

Een commit koppelt een tree aan de bijbehorende geschiedenis en metadata.

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

Commitinhoud opbouwen

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",
);

De onbewerkte commitindeling:

Code
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

Geannoteerde tags zijn objecten met metadata.

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

Onbewerkte tagindeling:

Code
object <hex hash>\n
type commit\n
tag <name>\n
tagger <name> <<email>> <timestamp> <tz>\n
\n
<message>

ObjectId

ObjectId is een alleen-lezen waardeobject dat een hash van SHA-1 (of SHA-256) vertegenwoordigt.

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);

De hash wordt berekend over de niet-gecomprimeerde gegevens: sha1("{type} {size}{content}") of SHA-256 van dezelfde bytes. fromHex() en fromBinary() kiezen het algoritme op basis van de lengte.

ObjectType-enum

PHP
use Pitmaster\Object\ObjectType;

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

Indeling van losse objecten

Losse objecten worden opgeslagen als afzonderlijke bestanden in .git/objects/, geordend op de eerste twee hexadecimale tekens van hun hash.

Code
.git/objects/a1/b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

De bestandsinhoud is gecomprimeerd met zlib. De niet-gecomprimeerde inhoud is:

Code
<type> <decimal size>\0<raw content>

Een blob met "Hello" wordt bijvoorbeeld als volgt opgeslagen:

Code
zlib_encode("blob 5\0Hello")

Losse objecten lezen

PHP
use Pitmaster\Storage\LooseObjectStore;

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

De LooseObjectStore:

  1. Berekent het bestandspad op basis van de hash
  2. Leest het bestand en decomprimeert het met zlib
  3. Parseert de koptekst om het type en de grootte uit te lezen
  4. Delegeert aan de bijbehorende parser (Blob, Tree, Commit, Tag)

Losse objecten schrijven

PHP
$store->write($object);

Het schrijfproces:

  1. Serialiseren: "{type} {size}\0{content}"
  2. De id van het object gebruiken, berekend toen het object werd opgebouwd (bij het lezen wordt de hash gecontroleerd)
  3. Comprimeren: zlib_encode(serialized)
  4. Schrijven naar .git/objects/{hash[0..1]}/{rest of hash}
  5. Atomair schrijven (naar een tijdelijk bestand schrijven en het daarna hernoemen)

ObjectSerializer

De ObjectSerializer verzorgt de codering en decodering van het object-wire-formaat.

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

De ObjectDatabase is een samengestelde opslag die eerst naar losse objecten zoekt en daarna naar packbestanden.

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

Wegschrijven naar losse opslag is bewust zo ontworpen. Laat git gc de objecten herpakken in packbestanden.

Ondersteuning voor SHA-256

Pitmaster abstraheert het hash-algoritme via ObjectId. SHA-1 is de standaard. SHA-256 wordt gekozen per repository wanneer deze wordt aangemaakt:

PHP
$repo = Pitmaster::init('/path/to/project', 'sha256');
Shell
./vendor/bin/pitmaster init --object-format=sha256

Hiermee wordt extensions.objectformat = sha256 naar de configuratie geschreven. Later geopende repositories lezen de indeling uit die instelling; daarna gebruiken alle bewerkingen met objecten, trees, indexen en refs hextekenreeksen van 64 tekens en binaire hashes van 32 bytes. Er is geen algemene schakelaar of constante voor het algoritme.

De objectklassen krijgen het algoritme als argument wanneer je zelf objecten aanmaakt: Blob::fromContent($content, 'sha256'), Tree::fromEntries($entries, 'sha256'), ObjectId::compute($type, $content, 'sha256'). Pitmaster::clone() maakt SHA-1-repositories aan.

Binair formaat van tree-vermeldingen

Binnen de inhoud van een tree-object wordt elke vermelding als volgt gecodeerd:

Code
<octal mode as ASCII> <space> <filename> \0 <binary hash: 20 bytes SHA-1, 32 bytes SHA-256>

Opmerking: de hash bestaat uit ruwe binaire gegevens, geen hexadecimale tekens zoals de hashes in commit- en tag-objecten.

Vermeldingen worden gesorteerd door namen te vergelijken alsof mappen eindigen op /. Deze sorteervolgorde moet exact kloppen om de hash van de tree overeen te laten komen met de berekening van git.