
# Stash

Git stash slaat wijzigingen in de werkdirectory en index op, zodat u naar een andere taak kunt overschakelen en ze later kunt herstellen. Pitmaster implementeert stash met `refs/stash` en de reflog, volgens de opslagindeling van git. `Repository` heeft geen stash-methode; gebruik de onderstaande class `Stash`.

Twee bekende problemen: bij meer dan één item laat `drop()` (en dus ook `pop()`) het item in de lijst staan ([#10](https://github.com/inline0/pitmaster/issues/10)), en `apply()` overschrijft niet-vastgelegde wijzigingen in bestanden die de stash niet aanraakt ([#11](https://github.com/inline0/pitmaster/issues/11)). Pas stashes toe op een schone werkboom.

## Hoe stash werkt

Elke stashvermelding is een commit met twee ouders:
- **Ouder 0**: de HEAD-commit ten tijde van de stash (de basistoestand)
- **Ouder 1**: een commit die de indexstatus vastlegt (klaargezette wijzigingen)

De eigen boom van de stash-commit legt de status van de werkboom vast (inclusief niet-klaargezette wijzigingen).

De stash-'stack' is de reflog van `refs/stash`. De meest recente stash is `refs/stash` zelf. Oudere items zijn reflog-vermeldingen: `stash@{0}`, `stash@{1}`, enzovoort.

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

## Het Stash-object maken

```php
use Pitmaster\Stash\Stash;

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

## Push (wijzigingen opslaan)

```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 doet het volgende:
1. Leest de huidige HEAD-commit
2. Bouwt een tree op basis van de huidige index (de gestagede status)
3. Maakt een "index-commit" met HEAD als parent
4. Bouwt een tree op basis van de huidige werkdirectory (inclusief niet-gestagede wijzigingen)
5. Maakt de stash-commit (worktree-tree, parents = HEAD + index-commit)
6. Werkt `refs/stash` bij zodat deze naar de stash-commit verwijst
7. Voegt een reflog-vermelding toe voor de stack
8. Zet de werkdirectory en index terug naar HEAD

Na push komen bijgehouden bestanden overeen met HEAD. Niet-bijgehouden bestanden blijven staan, tenzij `includeUntracked` is ingesteld; dan worden ze verwijderd en opgeslagen in de eigen tree van de stash-commit (git slaat ze in plaats daarvan op in een derde parent, zodat git ze als bijgehouden wijzigingen ziet). `push()` maakt ook een vermelding aan als er niets is gewijzigd; git meldt dan "No local changes to save".

### Met standaardbericht

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

Bij een detached HEAD is `<branch>` gelijk aan `HEAD`.

## Pop (toepassen en verwijderen)

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

Past de bovenste stash-vermelding toe op de werkdirectory en verwijdert deze uit de stack. Dit is gelijk aan `apply()` gevolgd door `drop()`.

### Een specifieke vermelding poppen

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

## Apply (herstellen zonder verwijderen)

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

Apply voegt de stash bestand voor bestand samen met het huidige HEAD-bestand (stash-basis, huidig HEAD, stash-tree). De stash-vermelding blijft in de stack staan en de index wordt niet aangepast. Bij conflicten blijven markeringen met de labels `Updated upstream` en `Stashed changes` in de bestanden staan en wordt `Pitmaster\Exceptions\MergeConflictException` gegenereerd; `pop()` behoudt de vermelding vervolgens. Apply controleert vooraf niet op lokale wijzigingen (zie de opmerking bovenaan).

## Vermeldingen weergeven

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

Elke vermelding bevat:
- `index`: de stash-positie (0 = meest recent)
- `message`: het stash-bericht
- `hash`: de hash van de stash-commit

## Drop (verwijderen zonder toe te passen)

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

Als je de enige vermelding verwijdert, worden `refs/stash` en de bijbehorende reflog verwijderd. Zijn er meer vermeldingen, dan verplaatst `drop()` momenteel alleen `refs/stash` en blijft de reflog ongewijzigd, waardoor de vermelding in `listEntries()` blijft staan ([#10](https://github.com/inline0/pitmaster/issues/10)).

## Opslagdetails

### Structuur van de stash-commit

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

<stash message>
```

### Structuur van de index-commit

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

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

De identiteit komt uit `GIT_COMMITTER_NAME`, vervolgens `GIT_AUTHOR_NAME`, daarna `user.name` in de repositoryconfiguratie en vervolgens `Pitmaster` (voor het e-mailadres geldt dezelfde volgorde, eindigend met `pitmaster@example.invalid`). De datum komt uit `GIT_COMMITTER_DATE`, vervolgens `GIT_AUTHOR_DATE` en daarna de huidige tijd. In tegenstelling tot commits leest stash de constanten `PITMASTER_*` niet.

### Reflog-opslag

Stash-vermeldingen worden opgeslagen in `.git/logs/refs/stash`:

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

De reflog wordt in chronologische volgorde gelezen. De stash-lijst keert die volgorde om (nieuwste eerst). De stash-reflog wordt geschreven ongeacht `core.logallrefupdates`.

## Gebruik van 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
```

Het bericht is het argument na `push` (of `save`); `-m` wordt niet ondersteund. `pop`, `apply` en `drop` werken altijd op `stash@{0}`; de CLI heeft geen `--include-untracked`.

## Beperkingen

- Alleen een volledige stash van de werkdirectory (geen `--keep-index`); niet-bijgehouden bestanden alleen via `push($message, includeUntracked: true)`
- Geen modus `--patch` (interactieve selectie van hunk-wijzigingen)
- Pop/apply herstelt de status van de werkdirectory; de index wordt niet hersteld. Gestagede bewerkingen komen terug als niet-gestagede bewerkingen en nieuw gestagede bestanden komen terug als niet-bijgehouden bestanden
- `stash@{n}` wordt niet geaccepteerd door `Repository::resolve()`
- Stash in een gekoppelde worktree gebruikt de verkeerde configuratie en reflog-locatie ([#14](https://github.com/inline0/pitmaster/issues/14))
