
# In Repositories schreiben

Pitmaster schreibt git-Repositories in denselben Binärformaten wie die maßgebliche `git`-Binärdatei. Objekte werden im Speicher für lose Objekte gespeichert (zlib-komprimiert), der Index im Format v2 (oder v3, wenn Einträge erweiterte Flags benötigen; ein vorhandener Index behält seine Version) und Refs als lose Dateien. Mit `git fsck --strict` lassen sich die geschriebenen Daten überprüfen.

## Repository initialisieren

```php
use Pitmaster\Pitmaster;

$repo = Pitmaster::init('/path/to/new-project');
```

Dabei werden erstellt:
- `.git/objects/` (leerer Objektspeicher)
- `.git/refs/heads/` und `.git/refs/tags/` (leere Ref-Verzeichnisse)
- `.git/HEAD` mit `ref: refs/heads/main`
- `.git/hooks/`, `.git/info/exclude` und `.git/description`
- `.git/config` mit Standardeinstellungen

Übergib `'sha256'` als zweites Argument, um ein SHA-256-Repository zu erstellen: `Pitmaster::init($path, 'sha256')`.

In der Standardkonfiguration wird `core.logallrefupdates` nicht festgelegt. Daher zeichnet Pitmaster in einem von ihm erstellten Repository keine Reflog-Einträge auf, bis du diesen Schlüssel auf `true` setzt ([#8](https://github.com/inline0/pitmaster/issues/8)). Bei mit `git init` erstellten Repositories ist der Schlüssel bereits gesetzt.

## Dateien vormerken

### add

```php
$repo->add('src/Feature.php');
$repo->add('src/Feature.php', 'tests/FeatureTest.php', 'README.md');
```

Jedes Argument ist ein Dateipfad relativ zum Stammverzeichnis des Arbeitsverzeichnisses. Verzeichnisse und Glob-Muster werden nicht erweitert; bei einer fehlenden Datei wird `RuntimeException` ausgelöst. Für jede Datei führt Pitmaster folgende Schritte aus:
1. Liest den Dateiinhalt aus dem Arbeitsverzeichnis
2. Erstellt ein `Blob`-Objekt und berechnet dessen Hash
3. Schreibt den Blob nach `.git/objects/` (zlib-komprimiert)
4. Aktualisiert den Indexeintrag für diesen Pfad mit dem neuen Hash und den Dateimetadaten

### remove

```php
$repo->remove('old-file.txt');
$repo->remove('-r', 'src');
$repo->remove('--cached', 'old-file.txt');
```

Entfernt versionierte Pfade aus dem Index und dem Arbeitsverzeichnis. Verwende `--cached`, um nur den Indexeintrag zu entfernen, oder `-r` / `--recursive` für versionierte Verzeichnisse.

### mv

```php
$repo->mv('old-name.php', 'new-name.php');
```

Benennt die Datei im Arbeitsverzeichnis um, entfernt den alten Indexeintrag und fügt den neuen hinzu.

## Commits erstellen

```php
$commitId = $repo->commit('Fix null pointer bug');
echo $commitId->hex;
```

Der Commit-Ablauf:

1. **Tree aus dem Index erstellen**: Die flache Liste der Indexeinträge wird zu einer Tree-Hierarchie geordnet (Verzeichnisse werden zu Tree-Objekten, Dateien zu Blob-Referenzen). Jeder Tree wird im Objektspeicher abgelegt.
2. **Parent ermitteln**: Der aktuelle HEAD-Commit wird zum Parent. Root-Commits (der erste Commit in einem Repository) haben keine Parents.
3. **Autor ermitteln**: Die Angaben stammen aus den Umgebungsvariablen `PITMASTER_AUTHOR_*` und `GIT_AUTHOR_*`, dann aus den Konstanten `PITMASTER_AUTHOR_NAME`/`PITMASTER_AUTHOR_EMAIL`, anschließend aus `.git/config` (`user.name`/`user.email`) und zuletzt aus `Pitmaster <pitmaster@localhost>`. Der Committer wird auf dieselbe Weise ermittelt.
4. **Commit-Objekt schreiben**: Der Commit-Inhalt (`tree`, `parent`, `author`, `committer`, Nachricht) wird serialisiert, gehasht und geschrieben.
5. **HEAD aktualisieren**: Die Branch-Ref, auf die HEAD verweist, wird auf den Hash des neuen Commits gesetzt.

Vor und nach diesen Schritten werden Hooks ausgeführt (`pre-commit`, `prepare-commit-msg`, `commit-msg`, `post-commit`), sofern das Handle nicht mit `['hooks' => false]` geöffnet wurde. `commit()` löst einen Fehler aus, wenn die Nachricht fehlt, der Tree gegenüber HEAD unverändert ist oder der Index nicht zusammengeführte Pfade enthält.

### Benutzerdefinierter Autor

```php
$author = 'Jane Smith <jane@example.com> 1700000000 +0000';
$commitId = $repo->commit('Add feature', $author);
```

## Mit git fsck überprüfen

Die Integrationstests von Pitmaster prüfen geschriebene Objekte anhand von `git fsck --strict`. Dieselbe Prüfung kannst du manuell ausführen:

```bash
cd /path/to/repo
git fsck --strict
```

Wenn Pitmaster ein ungültiges Objekt schreibt, meldet `git fsck` den Fehler sofort.

## Branches verwalten

### Branch erstellen

```php
// Branch from HEAD
$repo->createBranch('feature/new-thing');

// Branch from a specific commit
$repo->createBranch('hotfix', $repo->resolve('v1.0.0'));
```

Erstellt unter `.git/refs/heads/<name>` eine Ref-Datei mit dem Hash des Ziel-Commits.

### Branch löschen

```php
$repo->deleteBranch('feature/old');
```

### Branches wechseln

```php
$repo->checkout('feature/login');
```

Aktualisiert HEAD zu einer symbolischen Ref, die auf den Branch verweist, setzt den Index passend zum Tree der Branch-Spitze zurück und aktualisiert das Arbeitsverzeichnis.

Lokale Änderungen an Pfaden, die sich zwischen den beiden Trees unterscheiden, brechen den Checkout mit einem `RuntimeException` ab. Nicht committete Änderungen an allen anderen versionierten Dateien werden durch den Inhalt des Branches ersetzt, sofern git sie beibehalten würde ([#2](https://github.com/inline0/pitmaster/issues/2)). Committe oder sichere diese Änderungen vor dem Wechsel.

### Prüfen, ob zusammengeführt

```php
if ($repo->isBranchMerged('feature/done')) {
    $repo->deleteBranch('feature/done');
}
```

## Tags verwalten

### Lightweight-Tag

```php
$repo->createLightweightTag('v1.0.0');                          // At HEAD
$repo->createLightweightTag('v0.9.0', $repo->resolve('HEAD~5'));
```

Ein Lightweight-Tag ist lediglich eine Ref, die direkt auf einen Commit verweist.

### Annotiertes Tag

```php
$tagId = $repo->createTag('v2.0.0', 'Release version 2.0.0');
```

Ein annotiertes Tag erstellt ein Tag-Objekt (mit Tagger, Nachricht und Ziel) sowie eine darauf verweisende Ref. Als Tagger wird die Identität des aktuellen Committers verwendet, die wie der Commit-Autor ermittelt wird.

### Benutzerdefinierter Tagger

```php
$tagId = $repo->createTag(
    'v2.0.0',
    'Release version 2.0.0',
    $repo->resolve('HEAD'),
    'Jane Smith <jane@example.com> 1700000000 +0000',
);
```

## Refs direkt aktualisieren

```php
$repo->updateRef('refs/heads/main', $commitId);
$repo->updateRef('refs/tags/latest', $commitId);
```

## Objekte direkt schreiben

Für fortgeschrittene Anwendungsfälle kannst du Objekte schreiben, ohne den übergeordneten API zu verwenden.

### Blob schreiben

```php
use Pitmaster\Object\Blob;

$blob = Blob::fromContent('Hello, world!');
$id = $repo->writeObject($blob);
echo $id->hex;  // SHA-1 of "blob 13\0Hello, world!"
```

Übergib in einem SHA-256-Repository den Algorithmus: `Blob::fromContent('Hello, world!', 'sha256')`.

### Tree schreiben

```php
use Pitmaster\Object\Tree;
use Pitmaster\Object\TreeEntry;
use Pitmaster\Object\ObjectId;

$entries = [
    new TreeEntry('100644', 'README.md', $blobId),
    new TreeEntry('40000', 'src', $subtreeId),
];

$tree = Tree::fromEntries($entries);
$id = $repo->writeObject($tree);
```

`Tree::fromEntries()` schreibt die Einträge in der angegebenen Reihenfolge und verwendet die angegebenen Moduszeichenfolgen. Übergib sie in der Tree-Reihenfolge von git (nach Namen sortiert, wobei Verzeichnisse so verglichen werden, als endete ihr Name mit `/`) und gib Verzeichnismodi als `40000` an: git speichert Tree-Modi ohne die führende Null, und `git fsck --strict` lehnt `040000` ab.

### Commit schreiben

```php
use Pitmaster\Object\Commit;
use Pitmaster\Object\ObjectId;
use Pitmaster\Object\ObjectType;

$content = Commit::buildContent(
    tree: $treeId,
    parents: [$parentId],
    author: 'Name <email> 1700000000 +0000',
    committer: 'Name <email> 1700000000 +0000',
    message: "Commit message\n",
);

$commitId = ObjectId::compute(ObjectType::Commit, $content);
$commit = Commit::parse($content, $commitId);
$repo->writeObject($commit);
```

## Reset

```php
// Soft: move HEAD only
$repo->reset('HEAD~1', 'soft');

// Mixed: move HEAD + reset index (default)
$repo->reset('HEAD~1');

// Hard: move HEAD + reset index + reset working tree
$repo->reset('HEAD~3', 'hard');
```

## Restore

```php
// Restore a file from the index into the worktree
$repo->restore('src/File.php');

// Restore the index version from HEAD (the worktree is left alone)
$repo->restore('src/File.php', staged: true);

// Restore from a specific commit
$repo->restore('src/File.php', 'HEAD~1');

// Restore both index and worktree from a specific commit
$repo->restore('src/File.php', 'HEAD~1', staged: true, worktree: true);
```

## Cherry-pick und Revert

```php
// Apply a commit on the current branch
$newId = $repo->cherryPick('feature~1');

// Undo a commit
$revertId = $repo->revert('HEAD~2');
```

Beide akzeptieren jede Revision, die `resolve()` akzeptiert. Vollständige Hashes funktionieren; abgekürzte Hashes nicht.
