
# Stash

Git stash gemmer ændringer i arbejdsmappe og indeks, så du kan skifte til en anden opgave og gendanne dem senere. Pitmaster implementerer stash med `refs/stash` og reflog i gits lagerlayout. Der findes ingen stash-metode på `Repository`; brug klassen `Stash` nedenfor.

To kendte problemer: når der er mere end én post, lader `drop()` (og dermed `pop()`) posten blive stående på listen ([#10](https://github.com/inline0/pitmaster/issues/10)), og `apply()` overskriver ikke-committede ændringer i filer, som stashen ikke berører ([#11](https://github.com/inline0/pitmaster/issues/11)). Anvend stash-poster på et rent working tree.

## Sådan fungerer stash

Hver stash-post er en commit med to forældre:
- **Forælder 0**: HEAD-committen på stash-tidspunktet (grundtilstanden)
- **Forælder 1**: en commit, der indfanger indeksets tilstand (staged ændringer)

Stash-commitens eget træ indfanger working tree-tilstanden (herunder ikke-staged ændringer).

Stash-"stakken" er refloggen for `refs/stash`. Den nyeste stash er selve `refs/stash`. Ældre poster er reflogposter: `stash@{0}`, `stash@{1}` osv.

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

## Oprettelse af Stash-objektet

```php
use Pitmaster\Stash\Stash;

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

## Push (gem ændringer)

```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 gør følgende:
1. Læser den aktuelle HEAD-commit
2. Bygger et træ ud fra det aktuelle indeks (staged-tilstand)
3. Opretter en "index commit" med HEAD som parent
4. Bygger et træ ud fra den aktuelle arbejdsmappe (inklusive unstaged-ændringer)
5. Opretter stash-committet (worktree-træ, parents = HEAD + index commit)
6. Opdaterer `refs/stash`, så den peger på stash-committet
7. Tilføjer en reflogpost til stakken
8. Nulstiller arbejdstræet og indekset til HEAD

Efter push matcher sporede filer HEAD. Usporede filer forbliver, medmindre `includeUntracked` er angivet; så fjernes de og gemmes i stash-committets eget træ (git gemmer dem i stedet i en tredje parent, så git ser dem som sporede ændringer). `push()` opretter en post, selv når intet er ændret, hvor git rapporterer "No local changes to save".

### Med standardbesked

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

Med en detached HEAD er `<branch>` lig med `HEAD`.

## Pop (anvend og fjern)

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

Anvender den øverste stash-post på arbejdsmappen og fjerner den fra stakken. Svarer til `apply()` efterfulgt af `drop()`.

### Pop en bestemt post

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

## Apply (gendan uden at fjerne)

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

Apply fletter stashen ind i den aktuelle HEAD-fil for fil (stash-base, aktuel HEAD, stash-træ). Stash-posten forbliver i stakken, og indekset berøres ikke. Konflikter efterlader markører mærket `Updated upstream` og `Stashed changes` i filerne og kaster `Pitmaster\Exceptions\MergeConflictException`; `pop()` beholder derefter posten. Apply kontrollerer ikke først for lokale ændringer (se bemærkningen øverst).

## Vis poster

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

Hver post indeholder:
- `index`: stash-positionen (0 = nyeste)
- `message`: stash-beskeden
- `hash`: stash-commit-hashen

## Drop (fjern uden at anvende)

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

Hvis den eneste post fjernes, fjernes `refs/stash` og dens reflog. Hvis der er flere poster, flytter `drop()` i øjeblikket kun `refs/stash` og lader refloggen være uændret, så posten forbliver i `listEntries()` ([#10](https://github.com/inline0/pitmaster/issues/10)).

## Lagringsdetaljer

### Stash-commitstruktur

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

<stash message>
```

### Index-commitstruktur

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

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

Identiteten hentes fra `GIT_COMMITTER_NAME`, derefter `GIT_AUTHOR_NAME`, derefter `user.name` i repository-konfigurationen og til sidst `Pitmaster` (samme rækkefølge gælder for e-mailen, der slutter med `pitmaster@example.invalid`). Datoen hentes fra `GIT_COMMITTER_DATE`, derefter `GIT_AUTHOR_DATE` og til sidst det aktuelle tidspunkt. I modsætning til commits læser stash ikke konstanterne `PITMASTER_*`.

### Reflog-lagring

Stash-poster gemmes i `.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
```

Refloggen læses i kronologisk rækkefølge. Stash-listen vender rækkefølgen om (nyeste først). Stash-refloggen skrives uanset `core.logallrefupdates`.

## Brug af 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
```

Beskeden er argumentet efter `push` (eller `save`); `-m` understøttes ikke. `pop`, `apply` og `drop` fungerer altid på `stash@{0}`; CLI har ingen `--include-untracked`.

## Begrænsninger

- Kun stash af hele arbejdsbiblioteket (ingen `--keep-index`); usporede filer kun via `push($message, includeUntracked: true)`
- Ingen tilstand for `--patch` (interaktivt valg af hunk)
- Pop/apply gendanner arbejdstræets tilstand; indekset gendannes ikke. Staged-ændringer kommer tilbage som unstaged-ændringer, og nyligt staged filer kommer tilbage som usporede filer
- `stash@{n}` accepteres ikke af `Repository::resolve()`
- Stash i et tilknyttet worktree bruger forkert konfiguration og reflogplacering ([#14](https://github.com/inline0/pitmaster/issues/14))
