
# Stash

Git stash tallentaa työhakemiston ja indeksin muutokset, jotta voit siirtyä toiseen tehtävään ja palauttaa muutokset myöhemmin. Pitmaster toteuttaa stashin `refs/stash`- ja reflog-rakenteilla gitin tallennusmuodossa. `Repository`-luokassa ei ole stash-metodia; käytä alla olevaa `Stash`-luokkaa.

Tunnettuja ongelmia on kaksi: jos merkintöjä on useampi kuin yksi, `drop()` (ja siksi myös `pop()`) jättää merkinnän luetteloon ([#10](https://github.com/inline0/pitmaster/issues/10)), ja `apply()` korvaa keskeneräiset muutokset tiedostoissa, joita stash ei koske ([#11](https://github.com/inline0/pitmaster/issues/11)). Käytä stasheja vain puhtaassa työhakemistossa.

## Stashin toiminta

Jokainen stash-merkintä on commit, jolla on kaksi vanhempaa:
- **Vanhempi 0**: HEAD-commit stashin luontihetkellä (pohjatila)
- **Vanhempi 1**: indeksin tilan tallentava commit (vaiheistetut muutokset)

Stash-commitin oma puu tallentaa työhakemiston tilan (myös vaiheistamattomat muutokset).

Stash-"pino" on kohteen `refs/stash` reflog. Uusin stash on itse `refs/stash`. Vanhemmat merkinnät ovat reflog-merkintöjä: `stash@{0}`, `stash@{1}` jne.

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

```php
use Pitmaster\Stash\Stash;

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

## Push (tallenna muutokset)

```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 tekee seuraavaa:
1. Lukee nykyisen HEAD-commitin
2. Luo puun nykyisestä indeksistä (vaiheistettu tila)
3. Luo "index commitin", jonka vanhempi on HEAD
4. Luo puun nykyisestä työhakemistosta (myös vaiheistamattomat muutokset)
5. Luo stash-commitin (työpuu, vanhempina HEAD ja index commit)
6. Päivittää kohteen `refs/stash` osoittamaan stash-commitiin
7. Lisää pinolle reflog-merkinnän
8. Palauttaa työhakemiston ja indeksin HEAD-tilaan

Pushin jälkeen seuratut tiedostot vastaavat HEAD-tilaa. Seuraamattomat tiedostot säilyvät, ellei `includeUntracked` ole asetettu; silloin ne poistetaan ja tallennetaan stash-commitin omaan puuhun (git tallentaa ne sen sijaan kolmantena vanhempana, jolloin git näkee ne seurattuina muutoksina). `push()` luo merkinnän myös silloin, kun mitään ei ole muuttunut; git ilmoittaa tällöin "No local changes to save".

### Oletusviestillä

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

Kun HEAD on irrotettu, `<branch>` on `HEAD`.

## Pop (käytä ja poista)

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

Käyttää pinon ylimmän stash-merkinnän työhakemistossa ja poistaa sen pinosta. Vastaa toimintoja `apply()` ja sitten `drop()`.

### Tietyn merkinnän pop

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

## Apply (palauta poistamatta)

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

Apply yhdistää stashin nykyiseen HEAD-tiedostoon tiedosto kerrallaan (stashin pohja, nykyinen HEAD, stash-puu). Stash-merkintä säilyy pinossa, eikä indeksiin kosketa. Ristiriitatapauksissa tiedostoihin jää merkinnät `Updated upstream` ja `Stashed changes`, ja toiminto aiheuttaa `Pitmaster\Exceptions\MergeConflictException`-poikkeuksen; `pop()` säilyttää tällöin merkinnän. Apply ei tarkista ensin paikallisia muutoksia (katso alussa oleva huomautus).

## Merkintöjen luettelo

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

Jokainen merkintä sisältää:
- `index`: stash-sijainti (0 = uusin)
- `message`: stash-viesti
- `hash`: stash-commitin tiiviste

## Drop (poista käyttämättä)

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

Jos poistetaan ainoa merkintä, `refs/stash` ja sen reflog poistetaan. Jos merkintöjä on useita, `drop()` siirtää tällä hetkellä vain kohdetta `refs/stash` eikä muuta reflogia, joten merkintä säilyy kohteessa `listEntries()` ([#10](https://github.com/inline0/pitmaster/issues/10)).

## Tallennuksen yksityiskohdat

### Stash-commitin rakenne

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

<stash message>
```

### Index commitin rakenne

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

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

Identiteetti tulee ensin kohteesta `GIT_COMMITTER_NAME`, sitten kohteesta `GIT_AUTHOR_NAME`, sitten arkiston asetuksesta `user.name` ja lopuksi kohteesta `Pitmaster` (sähköpostiosoitteessa sama järjestys, ja viimeisenä `pitmaster@example.invalid`). Päivämäärä tulee kohteesta `GIT_COMMITTER_DATE`, sitten kohteesta `GIT_AUTHOR_DATE` ja lopuksi nykyhetkestä. Toisin kuin commitit, stash ei lue `PITMASTER_*`-vakioita.

### Reflog-tallennus

Stash-merkinnät tallennetaan kohteeseen `.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
```

Reflog luetaan aikajärjestyksessä. Stash-luettelo kääntää järjestyksen (uusin ensin). Stash-reflog kirjoitetaan `core.logallrefupdates`-asetuksesta riippumatta.

## CLI-käyttö

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

Viesti on argumentti kohteen `push` (tai `save`) jälkeen; `-m` ei ole tuettu. `pop`, `apply` ja `drop` toimivat aina kohteessa `stash@{0}`; kohteella CLI ei ole `--include-untracked`-toimintoa.

## Rajoitukset

- Vain koko työhakemiston stash (ei `--keep-index`); seuraamattomat tiedostot vain kohteen `push($message, includeUntracked: true)` kautta
- Ei `--patch`-tilaa (vuorovaikutteista muutosten osittaista valintaa)
- Pop/apply palauttaa työhakemiston tilan, mutta ei indeksiä. Vaiheistetut muokkaukset palaavat vaiheistamattomina, ja vasta lisätyt tiedostot palaavat seuraamattomina
- `stash@{n}` ei ole hyväksytty kohteessa `Repository::resolve()`
- Linkitetyssä työhakemistossa tehty stash käyttää vääriä asetuksia ja reflog-sijaintia ([#14](https://github.com/inline0/pitmaster/issues/14))
