
# Stash

Git stash speichert Änderungen am Arbeitsverzeichnis und Index, damit du zu einer anderen Aufgabe wechseln und sie später wiederherstellen kannst. Pitmaster implementiert den Stash mit `refs/stash` und dem Reflog im Speicherlayout von git. `Repository` bietet keine Stash-Methode; verwende die folgende Klasse `Stash`.

Es gibt zwei bekannte Probleme: Bei mehr als einem Eintrag lässt `drop()` (und damit auch `pop()`) den Eintrag in der Liste ([#10](https://github.com/inline0/pitmaster/issues/10)) zurück, und `apply()` überschreibt nicht committete Änderungen an Dateien, die der Stash nicht berührt ([#11](https://github.com/inline0/pitmaster/issues/11)). Wende Stashes nur in einem sauberen Arbeitsverzeichnis an.

## So funktioniert Stash

Jeder Stash-Eintrag ist ein Commit mit zwei Eltern:
- **Parent 0**: der HEAD-Commit zum Zeitpunkt des Stashens (der Ausgangszustand)
- **Parent 1**: ein Commit, der den Indexzustand erfasst (bereitgestellte Änderungen)

Der eigene Baum des Stash-Commits erfasst den Zustand des Arbeitsverzeichnisses (einschließlich nicht bereitgestellter Änderungen).

Der „Stack“ des Stash ist das Reflog von `refs/stash`. Der neueste Stash ist `refs/stash` selbst. Ältere Einträge sind Reflog-Einträge: `stash@{0}`, `stash@{1}` usw.

```
refs/stash -> stash commit (worktree state)
                parent[0] -> HEAD at time of stash
                parent[1] -> index state commit
                               parent[0] -> HEAD at time of stash
```

## Stash-Objekt erstellen

```php
use Pitmaster\Stash\Stash;

$stash = new Stash(
    $repo->objectDatabase(),
    $repo->refDatabase(),
    $repo->gitDir(),
    $repo->workDir(),
);
```

## Push (Änderungen speichern)

```php
// public function push(string $message = '', bool $includeUntracked = false): ObjectId
$stashId = $stash->push('Work in progress on login');
echo "Saved: {$stashId->hex}\n";

// Also stash untracked files
$stashId = $stash->push('With new files', includeUntracked: true);
```

Push führt Folgendes aus:
1. Liest den aktuellen HEAD-Commit
2. Erstellt einen Baum aus dem aktuellen Index (bereitgestellter Zustand)
3. Erstellt einen „Index-Commit“ mit HEAD als Parent
4. Erstellt einen Baum aus dem aktuellen Arbeitsverzeichnis (einschließlich nicht bereitgestellter Änderungen)
5. Erstellt den Stash-Commit (Arbeitsverzeichnis-Baum, Parents = HEAD + Index-Commit)
6. Aktualisiert `refs/stash`, sodass es auf den Stash-Commit zeigt
7. Hängt einen Reflog-Eintrag für den Stack an
8. Setzt Arbeitsverzeichnis und Index auf HEAD zurück

Nach dem Push stimmen getrackte Dateien mit HEAD überein. Nicht getrackte Dateien bleiben erhalten, außer `includeUntracked` ist gesetzt; dann werden sie entfernt und im eigenen Baum des Stash-Commits gespeichert (git speichert sie stattdessen in einem dritten Parent, sodass git sie als getrackte Änderungen erkennt). `push()` erstellt auch dann einen Eintrag, wenn es keine Änderungen gab; git meldet dabei „No local changes to save“.

### Mit Standardnachricht

```php
$stashId = $stash->push();
// Message: "WIP on <branch>: <short hash> <HEAD subject>"
// With a message: "On <branch>: <message>"
```

Bei einem abgetrennten HEAD ist `<branch>` gleich `HEAD`.

## Pop (anwenden und entfernen)

```php
$stash->pop();
```

Wendet den obersten Stash-Eintrag auf das Arbeitsverzeichnis an und entfernt ihn aus dem Stack. Entspricht `apply()` gefolgt von `drop()`.

### Bestimmten Eintrag abrufen

```php
$stash->pop(1);  // Pop stash@{1}
```

## Apply (wiederherstellen, ohne zu entfernen)

```php
$stash->apply();      // Apply stash@{0}
$stash->apply(2);     // Apply stash@{2}
```

Apply führt den Stash Datei für Datei mit dem aktuellen HEAD zusammen (Stash-Basis, aktueller HEAD, Stash-Baum). Der Stash-Eintrag bleibt im Stack, und der Index bleibt unverändert. Bei Konflikten hinterlässt es in den Dateien Markierungen mit den Bezeichnungen `Updated upstream` und `Stashed changes` und löst `Pitmaster\Exceptions\MergeConflictException` aus; `pop()` behält den Eintrag dann bei. Apply prüft nicht zuerst, ob lokale Änderungen vorhanden sind (siehe den Hinweis oben).

## Einträge auflisten

```php
$entries = $stash->listEntries();

foreach ($entries as $entry) {
    echo "stash@{{$entry['index']}}: {$entry['message']}\n";
    // stash@{0}: On main: Work in progress on login
    // stash@{1}: WIP on main: f707601 Add login form
}
```

Jeder Eintrag enthält:
- `index`: die Position im Stash (0 = neuester)
- `message`: die Stash-Nachricht
- `hash`: den Hash des Stash-Commits

## Drop (entfernen, ohne anzuwenden)

```php
$stash->drop();       // Drop stash@{0}
$stash->drop(1);      // Drop stash@{1}
```

Wird der einzige Eintrag entfernt, werden `refs/stash` und sein Reflog gelöscht. Gibt es mehrere Einträge, verschiebt `drop()` derzeit nur `refs/stash` und lässt das Reflog unverändert, sodass der Eintrag in `listEntries()` verbleibt ([#10](https://github.com/inline0/pitmaster/issues/10)).

## Speicherdetails

### Aufbau des Stash-Commits

```
commit <hash>
tree <worktree-tree-hash>
parent <HEAD-hash>
parent <index-commit-hash>
author <current identity>
committer <current identity>

<stash message>
```

### Aufbau des Index-Commits

```
commit <hash>
tree <index-tree-hash>
parent <HEAD-hash>
author <current identity>
committer <current identity>

index on <branch>: <short hash> <HEAD subject>
```

Die Identität stammt der Reihe nach aus `GIT_COMMITTER_NAME`, dann `GIT_AUTHOR_NAME`, dann `user.name` in der Repository-Konfiguration und schließlich `Pitmaster` (für die E-Mail gilt dieselbe Reihenfolge; sie endet mit `pitmaster@example.invalid`). Das Datum stammt aus `GIT_COMMITTER_DATE`, dann `GIT_AUTHOR_DATE` und schließlich der aktuellen Uhrzeit. Anders als bei Commits liest Stash die Konstanten `PITMASTER_*` nicht.

### Reflog-Speicherung

Stash-Einträge werden in `.git/logs/refs/stash` gespeichert:

```
0000000000000000000000000000000000000000 abc123... Name <email> 1700000000 +0000	WIP on main: f707601 Add login form
abc123... def456... Name <email> 1700000001 +0000	On main: Work in progress on login
```

Das Reflog wird in chronologischer Reihenfolge gelesen. Die Stash-Liste kehrt diese Reihenfolge um (neuester Eintrag zuerst). Das Stash-Reflog wird unabhängig von `core.logallrefupdates` geschrieben.

## Verwendung von CLI

```bash
# Save current changes
./vendor/bin/pitmaster stash
./vendor/bin/pitmaster stash push
./vendor/bin/pitmaster stash push "Work in progress on login"

# Apply and remove top entry
./vendor/bin/pitmaster stash pop

# Apply without removing
./vendor/bin/pitmaster stash apply

# List all entries
./vendor/bin/pitmaster stash list

# Remove top entry without applying
./vendor/bin/pitmaster stash drop
```

Die Nachricht ist das Argument nach `push` (oder `save`); `-m` wird nicht unterstützt. `pop`, `apply` und `drop` arbeiten immer mit `stash@{0}`; CLI hat kein `--include-untracked`.

## Einschränkungen

- Nur vollständiger Stash des Arbeitsverzeichnisses (kein `--keep-index`); nicht getrackte Dateien nur über `push($message, includeUntracked: true)`
- Kein Modus `--patch` (interaktive Auswahl einzelner Änderungen)
- Pop/Apply stellt den Zustand des Arbeitsverzeichnisses wieder her, nicht den Index. Bereitgestellte Änderungen kommen als nicht bereitgestellte zurück, neu bereitgestellte Dateien als nicht getrackte Dateien
- `stash@{n}` wird von `Repository::resolve()` nicht akzeptiert
- Stash in einem verknüpften Worktree verwendet den falschen Speicherort für Konfiguration und Reflog ([#14](https://github.com/inline0/pitmaster/issues/14))
