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).
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.
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:
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.
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
$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:
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.
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:
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.
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} of SHA-256 van dezelfde bytes. fromHex() en fromBinary() kiezen het algoritme op basis van de lengte.
ObjectType-enum
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.
.git/objects/a1/b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
De bestandsinhoud is gecomprimeerd met zlib. De niet-gecomprimeerde inhoud is:
<type> <decimal size>\0<raw content>
Een blob met "Hello" wordt bijvoorbeeld als volgt opgeslagen:
zlib_encode("blob 5\0Hello")
Losse objecten lezen
use Pitmaster\Storage\LooseObjectStore;
$store = new LooseObjectStore('/path/to/.git/objects');
$object = $store->read($objectId);
De LooseObjectStore:
- Berekent het bestandspad op basis van de hash
- Leest het bestand en decomprimeert het met zlib
- Parseert de koptekst om het type en de grootte uit te lezen
- Delegeert aan de bijbehorende parser (
Blob,Tree,Commit,Tag)
Losse objecten schrijven
$store->write($object);
Het schrijfproces:
- Serialiseren:
"{type} {size}\0{content}" - De
idvan het object gebruiken, berekend toen het object werd opgebouwd (bij het lezen wordt de hash gecontroleerd) - Comprimeren:
zlib_encode(serialized) - Schrijven naar
.git/objects/{hash[0..1]}/{rest of hash} - Atomair schrijven (naar een tijdelijk bestand schrijven en het daarna hernoemen)
ObjectSerializer
De ObjectSerializer verzorgt de codering en decodering van het object-wire-formaat.
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.
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:
$repo = Pitmaster::init('/path/to/project', 'sha256');
./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:
<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.