
# Zásobník

Git zásobník ukládá změny pracovního adresáře a indexu, abyste mohli přejít k jinému úkolu a později je obnovit. Pitmaster implementuje zásobník pomocí `refs/stash` a reflogu podle způsobu ukládání dat v gitu. Třída `Repository` nemá metodu pro práci se zásobníkem; použijte níže uvedenou třídu `Stash`.

Jsou známy dva problémy: pokud je v seznamu více než jedna položka, `drop()` (a tedy i `pop()`) položku ze seznamu neodstraní ([#10](https://github.com/inline0/pitmaster/issues/10)); `apply()` přepíše nepotvrzené změny v souborech, kterých se zásobník netýká ([#11](https://github.com/inline0/pitmaster/issues/11)). Zásobníky aplikujte na čistý pracovní strom.

## Jak funguje zásobník

Každá položka zásobníku je commit se dvěma rodiči:
- **Rodič 0**: commit HEAD v okamžiku uložení do zásobníku (základní stav)
- **Rodič 1**: commit zachycující stav indexu (připravené změny)

Vlastní strom commitu zásobníku zachycuje stav pracovního stromu (včetně nepřipravených změn).

Zásobník „stack“ je reflogem `refs/stash`. Nejnovější stash je samotné `refs/stash`. Starší položky jsou záznamy reflogu: `stash@{0}`, `stash@{1}` atd.

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

## Vytvoření objektu Stash

```php
use Pitmaster\Stash\Stash;

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

## Push (uložení změn)

```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 provede následující:
1. Načte aktuální commit HEAD
2. Sestaví strom z aktuálního indexu (připraveného stavu)
3. Vytvoří „index commit“ s HEAD jako rodičem
4. Sestaví strom z aktuálního pracovního adresáře (včetně nepřipravených změn)
5. Vytvoří commit stash (strom pracovního adresáře, rodiče = HEAD + index commit)
6. Nastaví `refs/stash` tak, aby ukazoval na commit stash
7. Přidá záznam reflogu zásobníku
8. Vrátí pracovní strom a index do stavu HEAD

Po push odpovídají sledované soubory HEAD. Nesledované soubory zůstanou, pokud není nastaveno `includeUntracked`; v takovém případě se odstraní a uloží do vlastního stromu commitu stash (git je místo toho ukládá ve třetím rodiči, takže je git považuje za sledované změny). `push()` vytvoří položku i tehdy, když se nic nezměnilo; git pak hlásí „No local changes to save“.

### S výchozí zprávou

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

Při odpojeném HEAD je `<branch>` rovno `HEAD`.

## Pop (použití a odebrání)

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

Použije nejvyšší položku stash v pracovním adresáři a odebere ji ze zásobníku. Ekvivalent příkazů `apply()` následovaného `drop()`.

### Odebrání konkrétní položky

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

## Apply (obnovení bez odebrání)

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

Apply sloučí stash se soubory aktuálního HEAD, soubor po souboru (základ stash, aktuální HEAD, strom stash). Položka stash zůstane v zásobníku a index se nezmění. Při konfliktech se v souborech ponechají značky `Updated upstream` a `Stashed changes` a vyvolá se `Pitmaster\Exceptions\MergeConflictException`; `pop()` pak položku zachová. Apply nejprve nekontroluje místní změny (viz poznámka nahoře).

## Výpis položek

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

Každá položka obsahuje:
- `index`: pozici stash (0 = nejnovější)
- `message`: zprávu stash
- `hash`: hash commitu stash

## Drop (odebrání bez použití)

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

Při odebrání jediné položky se odstraní `refs/stash` i jeho reflog. Pokud je položek více, `drop()` aktuálně pouze posune `refs/stash` a reflog ponechá beze změny, takže položka zůstane v `listEntries()` ([#10](https://github.com/inline0/pitmaster/issues/10)).

## Podrobnosti o úložišti

### Struktura commitu stash

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

<stash message>
```

### Struktura index commitu

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

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

Identita se načítá z `GIT_COMMITTER_NAME`, poté z `GIT_AUTHOR_NAME`, následně z `user.name` v konfiguraci repozitáře a nakonec z `Pitmaster` (stejné pořadí platí i pro e-mail, jehož poslední možností je `pitmaster@example.invalid`). Datum se načítá z `GIT_COMMITTER_DATE`, poté z `GIT_AUTHOR_DATE` a nakonec z aktuálního času. Na rozdíl od commitů stash nenačítá konstanty `PITMASTER_*`.

### Úložiště Reflog

Položky stash se ukládají do `.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 se načítá v chronologickém pořadí. Seznam stash jej obrací (nejnovější položka je první). Reflog stash se zapisuje bez ohledu na `core.logallrefupdates`.

## Použití 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
```

Zpráva je argument za `push` (nebo `save`); `-m` není podporováno. `pop`, `apply` a `drop` vždy pracují s `stash@{0}`; CLI nemá žádné `--include-untracked`.

## Omezení

- Pouze stash celého pracovního adresáře (bez `--keep-index`); nesledované soubory lze přidat jen přes `push($message, includeUntracked: true)`
- Režim `--patch` (interaktivní výběr částí změn) není k dispozici
- Pop/apply obnoví stav pracovního stromu, ale neobnoví index. Připravené úpravy se vrátí jako nepřipravené a nově připravené soubory jako nesledované
- `stash@{n}` nepřijímá `Repository::resolve()`
- Stash v propojeném pracovním stromu používá nesprávné umístění konfigurace a reflogu ([#14](https://github.com/inline0/pitmaster/issues/14))
