Zum Inhalt springen

Git-Objekte

Alles in einem git-Repository wird als Objekt gespeichert. Es gibt vier Typen: Blobs (Dateiinhalte), Trees (Verzeichnislisten), Commits (Snapshots mit Metadaten) und Tags (annotierte Referenzen). Jedes Objekt wird durch den SHA-1-Hash seines Inhalts identifiziert.

Objekttypen

Blob

Ein Blob enthält rohe Dateiinhalte ohne Metadaten (keinen Dateinamen und keine Berechtigungen).

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

Ein Tree ist eine Verzeichnisliste. Jeder Eintrag hat einen Modus, einen Namen und einen Hash, der auf einen Blob oder Unter-Tree verweist.

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() schreibt die Einträge in der angegebenen Reihenfolge und die Moduszeichenfolgen genau wie angegeben. Sortiere sie daher zuerst und gib Verzeichnismodi als 40000 an. Ein mit 040000 oder in anderer Reihenfolge geschriebener Tree erhält einen anderen Hash als bei git, und git fsck --strict gibt den Modus mit führenden Nullen aus.

TreeEntry

TreeEntry ist ein schreibgeschütztes Wertobjekt:

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)

Moduswerte:

Modus Bedeutung
100644 Reguläre Datei
100755 Ausführbare Datei
40000 Verzeichnis (Tree); von git ls-tree als 040000 angezeigt
120000 Symbolischer Link
160000 Gitlink (Submodul)

Tree-Einträge werden nach Namen sortiert; bei Verzeichnissen wird für den Vergleich so getan, als endeten ihre Namen mit /.

Commit

Ein Commit verknüpft einen Tree mit seiner Historie und seinen Metadaten.

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-Inhalt erstellen

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

Das rohe Commit-Format:

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

Annotierte Tags sind Objekte mit Metadaten.

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

Rohes Tag-Format:

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

ObjectId

ObjectId ist ein schreibgeschütztes Wertobjekt, das einen Hash im Format SHA-1 (oder SHA-256) darstellt.

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

Der Hash wird über die unkomprimierten Daten berechnet: sha1("{type} {size}{content}") oder SHA-256 derselben Bytes. fromHex() und fromBinary() wählen den Algorithmus anhand der Länge aus.

ObjectType-Enum

PHP
use Pitmaster\Object\ObjectType;

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

Format loser Objekte

Lose Objekte werden als einzelne Dateien in .git/objects/ gespeichert und anhand der ersten beiden Hexadezimalzeichen ihres Hashs organisiert.

Code
.git/objects/a1/b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

Der Dateiinhalt ist zlib-komprimiert. Der unkomprimierte Inhalt sieht so aus:

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

Beispiel: Ein Blob mit dem Inhalt „Hello“ wird so gespeichert:

Code
zlib_encode("blob 5\0Hello")

Lose Objekte lesen

PHP
use Pitmaster\Storage\LooseObjectStore;

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

LooseObjectStore:

  1. Berechnet den Dateipfad anhand des Hashs
  2. Liest die Datei und dekomprimiert sie mit zlib
  3. Liest Typ und Größe aus dem Header aus
  4. Übergibt an den entsprechenden Parser (Blob, Tree, Commit, Tag)

Lose Objekte schreiben

PHP
$store->write($object);

Der Schreibvorgang:

  1. Serialisieren: "{type} {size}\0{content}"
  2. Den id des Objekts verwenden, der beim Erstellen berechnet wurde (beim Lesen wird der Hash geprüft)
  3. Komprimieren: zlib_encode(serialized)
  4. In .git/objects/{hash[0..1]}/{rest of hash} schreiben
  5. Atomar schreiben (in eine temporäre Datei schreiben und dann umbenennen)

ObjectSerializer

ObjectSerializer übernimmt das Kodieren und Dekodieren des Objekt-Wire-Formats.

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 ist ein zusammengesetzter Speicher, der zuerst lose Objekte und dann Pack-Dateien durchsucht.

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

Das Schreiben in den losen Speicher ist beabsichtigt. Überlass es git gc, Objekte in Pack-Dateien neu zu packen.

Unterstützung für SHA-256

Pitmaster abstrahiert den Hashalgorithmus über ObjectId. SHA-1 ist der Standard. SHA-256 wird bei der Erstellung für jedes Repository ausgewählt:

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

Dadurch wird extensions.objectformat = sha256 in die Konfiguration geschrieben. Später geöffnete Repositories lesen das Format aus diesem Eintrag. Anschließend verwenden alle Objekt-, Tree-, Index- und Ref-Operationen 64 Zeichen lange Hexadezimalstrings und 32 Byte lange Binär-Hashes. Es gibt weder eine globale Umschaltmöglichkeit noch eine Konstante für den Algorithmus.

Wenn du Objekte selbst erstellst, übergibst du den Algorithmus als Argument an die Objektklassen: Blob::fromContent($content, 'sha256'), Tree::fromEntries($entries, 'sha256'), ObjectId::compute($type, $content, 'sha256'). Pitmaster::clone() erstellt SHA-1-Repositories.

Binärformat von Tree-Einträgen

Im Inhalt eines Tree-Objekts ist jeder Eintrag wie folgt kodiert:

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

Hinweis: Der Hash liegt als rohe Binärdaten vor, nicht als Hexadezimalwert. Bei Commit- und Tag-Objekten sind die Hashes dagegen hexadezimal.

Einträge werden verglichen, als hätten Verzeichnisse ein abschließendes /. Diese Sortierreihenfolge muss exakt eingehalten werden, damit der Hash des Trees mit der Berechnung von git übereinstimmt.