
# Stash

Git stash salva le modifiche alla directory di lavoro e all’indice, così potete passare a un’altra attività e ripristinarle in seguito. Pitmaster implementa lo stash usando `refs/stash` e il reflog, secondo il layout di archiviazione di git. `Repository` non dispone di un metodo stash; usate la classe `Stash` qui sotto.

Due problemi noti: con più di una voce, `drop()` (e quindi `pop()`) lascia la voce nell’elenco ([#10](https://github.com/inline0/pitmaster/issues/10)), mentre `apply()` sovrascrive le modifiche non registrate nei file che lo stash non tocca ([#11](https://github.com/inline0/pitmaster/issues/11)). Applicate gli stash su un albero di lavoro pulito.

## Come funziona lo stash

Ogni voce stash è un commit con due genitori:
- **Genitore 0**: il commit HEAD al momento dello stash (lo stato di base)
- **Genitore 1**: un commit che acquisisce lo stato dell’indice (modifiche in staging)

L’albero del commit stash stesso acquisisce lo stato dell’albero di lavoro (incluse le modifiche non in staging).

Lo stack di stash è il reflog di `refs/stash`. Lo stash più recente è `refs/stash` stesso. Le voci meno recenti sono voci del reflog: `stash@{0}`, `stash@{1}` e così via.

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

## Creazione dell'oggetto Stash

```php
use Pitmaster\Stash\Stash;

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

## Push (salvare le modifiche)

```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 esegue le seguenti operazioni:
1. Legge il commit HEAD corrente
2. Crea un albero dall'indice corrente (stato di staging)
3. Crea un «commit dell'indice» con HEAD come genitore
4. Crea un albero dalla directory di lavoro corrente (incluse le modifiche non in staging)
5. Crea il commit stash (albero della directory di lavoro, genitori = HEAD + commit dell'indice)
6. Aggiorna `refs/stash` affinché punti al commit stash
7. Aggiunge una voce reflog per lo stack
8. Reimposta la directory di lavoro e l'indice su HEAD

Dopo il push, i file tracciati corrispondono a HEAD. I file non tracciati rimangono, a meno che non sia impostato `includeUntracked`; in tal caso vengono rimossi e salvati nell'albero del commit stash (git li memorizza invece in un terzo genitore, quindi git li considera modifiche tracciate). `push()` crea una voce anche quando non è cambiato nulla; git indica «No local changes to save».

### Con il messaggio predefinito

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

Con HEAD scollegato, `<branch>` è `HEAD`.

## Pop (applicare e rimuovere)

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

Applica la voce stash in cima alla directory di lavoro e la rimuove dallo stack. Equivale a `apply()` seguito da `drop()`.

### Rimuovere una voce specifica

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

## Apply (ripristinare senza rimuovere)

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

Apply unisce lo stash al file HEAD corrente, file per file (base dello stash, HEAD corrente, albero dello stash). La voce stash rimane nello stack e l'indice non viene toccato. In caso di conflitti, nei file vengono lasciati marcatori etichettati `Updated upstream` e `Stashed changes` e viene generato `Pitmaster\Exceptions\MergeConflictException`; `pop()` mantiene quindi la voce. Apply non verifica prima la presenza di modifiche locali (vedere la nota in alto).

## Elencare le voci

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

Ogni voce contiene:
- `index`: la posizione dello stash (0 = la più recente)
- `message`: il messaggio dello stash
- `hash`: l'hash del commit stash

## Drop (rimuovere senza applicare)

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

Se si rimuove l'unica voce, `refs/stash` e il relativo reflog vengono rimossi. Se ci sono altre voci, al momento `drop()` sposta soltanto `refs/stash` e lascia invariato il reflog, quindi la voce resta in `listEntries()` ([#10](https://github.com/inline0/pitmaster/issues/10)).

## Dettagli di archiviazione

### Struttura del commit stash

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

<stash message>
```

### Struttura del commit dell'indice

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

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

L'identità proviene da `GIT_COMMITTER_NAME`, poi da `GIT_AUTHOR_NAME` e infine da `user.name` nella configurazione del repository, poi da `Pitmaster` (lo stesso ordine vale per l'email, che termina con `pitmaster@example.invalid`). La data proviene da `GIT_COMMITTER_DATE`, poi da `GIT_AUTHOR_DATE` e infine dall'ora corrente. A differenza dei commit, stash non legge le costanti `PITMASTER_*`.

### Archiviazione di Reflog

Le voci stash sono memorizzate in `.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
```

Il reflog viene letto in ordine cronologico. L'elenco degli stash lo inverte (dal più recente al meno recente). Il reflog dello stash viene scritto indipendentemente da `core.logallrefupdates`.

## Utilizzo di 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
```

Il messaggio è l'argomento dopo `push` (o `save`); `-m` non è supportato. `pop`, `apply` e `drop` operano sempre su `stash@{0}`; CLI non ha `--include-untracked`.

## Limitazioni

- Solo stash dell'intera directory di lavoro (senza `--keep-index`); i file non tracciati si includono solo tramite `push($message, includeUntracked: true)`
- Nessuna modalità `--patch` (selezione interattiva degli hunk)
- Pop/apply ripristina lo stato della directory di lavoro, ma non quello dell'indice. Le modifiche in staging tornano come modifiche non in staging e i nuovi file messi in staging tornano come file non tracciati
- `stash@{n}` non è accettato da `Repository::resolve()`
- Lo stash in un worktree collegato usa la configurazione e la posizione del reflog errate ([#14](https://github.com/inline0/pitmaster/issues/14))
