
# Stash

Git stash sparar ändringar i arbetskatalogen och indexet så att du kan växla till en annan uppgift och återställa dem senare. Pitmaster implementerar stash med `refs/stash` och reflog, enligt gits lagringslayout. Det finns ingen stash-metod på `Repository`; använd klassen `Stash` nedan.

Två kända problem: med fler än en post lämnar `drop()` (och därmed `pop()`) posten kvar i listan ([#10](https://github.com/inline0/pitmaster/issues/10)), och `apply()` skriver över oincheckade ändringar i filer som stash inte berör ([#11](https://github.com/inline0/pitmaster/issues/11)). Tillämpa stashar på ett rent arbetsträd.

## Så fungerar stash

Varje stash-post är en commit med två föräldrar:
- **Förälder 0**: HEAD-committen vid stash-tillfället (bastillståndet)
- **Förälder 1**: en commit som fångar indexets tillstånd (stegade ändringar)

Stash-commitens eget träd fångar arbetsträdets tillstånd (inklusive ostegade ändringar).

Stash-"stacken" är refloggen för `refs/stash`. Den senaste stashen är själva `refs/stash`. Äldre poster är 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
```

## Skapa Stash-objektet

```php
use Pitmaster\Stash\Stash;

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

## Skicka (spara ändringar)

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

Skicka gör följande:
1. Läser den aktuella HEAD-committen
2. Bygger ett träd från det aktuella indexet (stegat tillstånd)
3. Skapar en "index-commit" med HEAD som förälder
4. Bygger ett träd från den aktuella arbetskatalogen (inklusive ostegade ändringar)
5. Skapar stash-committen (arbetsträdets träd, föräldrar = HEAD + index-commit)
6. Uppdaterar `refs/stash` så att den pekar på stash-committen
7. Lägger till en reflogpost för stacken
8. Återställer arbetsträdet och indexet till HEAD

Efter push matchar spårade filer HEAD. Ospårade filer finns kvar om `includeUntracked` inte är angivet; annars tas de bort och lagras i stash-commitens eget träd (git lagrar dem i en tredje förälder i stället, så git ser dem som spårade ändringar). `push()` skapar en post även när inget har ändrats, då rapporterar git ”Inga lokala ändringar att spara”.

### Med standardmeddelande

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

Med frikopplad HEAD är `<branch>` `HEAD`.

## Pop (tillämpa och ta bort)

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

Tillämpa den översta stash-posten på arbetskatalogen och ta bort den från stacken. Motsvarar `apply()` följt av `drop()`.

### Poppa en specifik post

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

## Tillämpa (återställ utan att ta bort)

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

Apply sammanfogar stashen med den aktuella HEAD-filen fil för fil (stashens bas, aktuell HEAD, stash-trädet). Stash-posten ligger kvar i stacken och indexet påverkas inte. Konflikter lämnar markörer märkta `Updated upstream` och `Stashed changes` i filerna och utlöser `Pitmaster\Exceptions\MergeConflictException`; `pop()` behåller då posten. Apply kontrollerar inte först om det finns lokala ändringar (se anmärkningen högst upp).

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

Varje post innehåller:
- `index`: stashens position (0 = senaste)
- `message`: stash-meddelandet
- `hash`: stash-commitens hash

## Ta bort (utan att tillämpa)

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

Om den enda posten tas bort försvinner `refs/stash` och dess reflogg. Med fler poster flyttar `drop()` för närvarande bara `refs/stash` och lämnar refloggen oförändrad, så posten ligger kvar i `listEntries()` ([#10](https://github.com/inline0/pitmaster/issues/10)).

## Lagringsdetaljer

### Stash-commitens struktur

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

<stash message>
```

### Index-commitens struktur

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

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

Identiteten hämtas från `GIT_COMMITTER_NAME`, därefter `GIT_AUTHOR_NAME`, sedan `user.name` i arkivkonfigurationen och slutligen `Pitmaster` (samma ordning gäller för e-postadressen och avslutas med `pitmaster@example.invalid`). Datumet hämtas från `GIT_COMMITTER_DATE`, därefter `GIT_AUTHOR_DATE` och sedan aktuell tid. Till skillnad från commits läser stash inte konstanterna `PITMASTER_*`.

### Reflog-lagring

Stash-poster lagras 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 ordning. Stash-listan vänder på ordningen (nyaste först). Stash-refloggen skrivs oavsett `core.logallrefupdates`.

## Användning av 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
```

Meddelandet är argumentet efter `push` (eller `save`); `-m` stöds inte. `pop`, `apply` och `drop` fungerar alltid på `stash@{0}`; CLI har ingen `--include-untracked`.

## Begränsningar

- Endast stash av hela arbetskatalogen (ingen `--keep-index`); ospårade filer endast via `push($message, includeUntracked: true)`
- Inget läge `--patch` (interaktiv val av delar)
- Pop/apply återställer arbetsträdets tillstånd; indexet återställs inte. Stegade ändringar kommer tillbaka som ostegade ändringar, och nyligen stegade filer kommer tillbaka som ospårade filer
- `stash@{n}` accepteras inte av `Repository::resolve()`
- Stash i ett länkat arbetsträd använder fel konfiguration och reflogplats ([#14](https://github.com/inline0/pitmaster/issues/14))
