
# 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:

```
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:

```
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}\0{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.

```
.git/objects/a1/b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
```

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

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

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

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

```bash
./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:

```
<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.
