
# Stash

Git stash zapisuje zmiany w katalogu roboczym i indeksie, aby można było przejść do innego zadania, a później je przywrócić. Pitmaster implementuje stash za pomocą `refs/stash` i refloga, zgodnie z układem przechowywania git. Klasa `Repository` nie ma metody stash; użyj poniższej klasy `Stash`.

Znane są dwa problemy: gdy istnieje więcej niż jeden wpis, `drop()` (a zatem i `pop()`) pozostawia wpis na liście ([#10](https://github.com/inline0/pitmaster/issues/10)), a `apply()` nadpisuje niezatwierdzone zmiany w plikach, których stash nie obejmuje ([#11](https://github.com/inline0/pitmaster/issues/11)). Stosuj stash na czystym drzewie roboczym.

## Jak działa stash

Każdy wpis stash to commit z dwoma rodzicami:
- **Rodzic 0**: commit HEAD w chwili utworzenia stash (stan bazowy)
- **Rodzic 1**: commit zawierający stan indeksu (zmiany przygotowane)

Własne drzewo commitu stash zapisuje stan drzewa roboczego (w tym zmiany nieprzygotowane).

Stos stash to reflog `refs/stash`. Najnowszy stash to samo `refs/stash`. Starsze wpisy znajdują się w reflogu: `stash@{0}`, `stash@{1}` itd.

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

## Tworzenie obiektu Stash

```php
use Pitmaster\Stash\Stash;

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

## Push (zapis zmian)

```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 wykonuje następujące czynności:
1. Odczytuje bieżący commit HEAD
2. Buduje drzewo z bieżącego indeksu (stan przygotowanych zmian)
3. Tworzy „commit indeksu”, którego rodzicem jest HEAD
4. Buduje drzewo z bieżącego katalogu roboczego (w tym zmian nieprzygotowanych)
5. Tworzy commit stash (drzewo katalogu roboczego, rodzice = HEAD + commit indeksu)
6. Ustawia `refs/stash`, aby wskazywał na commit stash
7. Dodaje wpis refloga dla stosu
8. Resetuje katalog roboczy i indeks do HEAD

Po pushu śledzone pliki odpowiadają HEAD. Pliki nieśledzone pozostają, chyba że ustawiono `includeUntracked`; wtedy są usuwane i zapisywane we własnym drzewie commitu stash (git przechowuje je zamiast tego w trzecim rodzicu, więc git widzi je jako śledzone zmiany). `push()` tworzy wpis nawet wtedy, gdy nic się nie zmieniło; git zgłasza wówczas „No local changes to save”.

### Z domyślnym komunikatem

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

Przy odłączonym HEAD wartość `<branch>` to `HEAD`.

## Pop (zastosuj i usuń)

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

Stosuje najnowszy wpis stash w katalogu roboczym i usuwa go ze stosu. Odpowiada kolejno `apply()` i `drop()`.

### Pop określonego wpisu

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

## Apply (przywróć bez usuwania)

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

Apply scala stash z plikami bieżącego HEAD, plik po pliku (baza stash, bieżący HEAD, drzewo stash). Wpis stash pozostaje na stosie, a indeks nie jest zmieniany. W przypadku konfliktów w plikach pozostają znaczniki `Updated upstream` i `Stashed changes`, a operacja zgłasza `Pitmaster\Exceptions\MergeConflictException`; `pop()` zachowuje wtedy wpis. Apply nie sprawdza wcześniej, czy istnieją zmiany lokalne (zobacz uwagę na początku).

## Lista wpisów

```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żdy wpis zawiera:
- `index`: pozycję stash (0 = najnowszy)
- `message`: komunikat stash
- `hash`: hash commitu stash

## Drop (usuń bez stosowania)

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

Jeśli usuwany jest jedyny wpis, `refs/stash` i jego reflog są usuwane. Jeśli wpisów jest więcej, `drop()` obecnie tylko przenosi `refs/stash` i pozostawia reflog bez zmian, więc wpis nadal znajduje się w `listEntries()` ([#10](https://github.com/inline0/pitmaster/issues/10)).

## Szczegóły przechowywania

### 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 commitu indeksu

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

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

Tożsamość pochodzi kolejno z `GIT_COMMITTER_NAME`, `GIT_AUTHOR_NAME`, a następnie `user.name` w konfiguracji repozytorium, a na końcu z `Pitmaster` (w tej samej kolejności ustalany jest adres e-mail, kończący się na `pitmaster@example.invalid`). Data pochodzi kolejno z `GIT_COMMITTER_DATE`, `GIT_AUTHOR_DATE`, a następnie z bieżącego czasu. W przeciwieństwie do commitów stash nie odczytuje stałych `PITMASTER_*`.

### Przechowywanie Reflog

Wpisy stash są przechowywane w `.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 jest odczytywany chronologicznie. Lista stash odwraca tę kolejność (najnowsze wpisy są na początku). Reflog stash jest zapisywany niezależnie od `core.logallrefupdates`.

## Użycie 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
```

Komunikat jest argumentem po `push` (lub `save`); `-m` nie jest obsługiwane. `pop`, `apply` i `drop` zawsze działają na `stash@{0}`; CLI nie ma `--include-untracked`.

## Ograniczenia

- Obsługiwany jest tylko pełny stash katalogu roboczego (bez `--keep-index`); pliki nieśledzone można uwzględnić tylko przez `push($message, includeUntracked: true)`
- Brak trybu `--patch` (interaktywnego wyboru fragmentów zmian)
- Pop/apply przywracają stan drzewa roboczego, ale nie przywracają indeksu. Przygotowane edycje wracają jako nieprzygotowane, a nowo przygotowane pliki jako nieśledzone
- `stash@{n}` nie jest akceptowane przez `Repository::resolve()`
- Stash w połączonym worktree używa niewłaściwej konfiguracji i lokalizacji refloga ([#14](https://github.com/inline0/pitmaster/issues/14))
