
# Stash

Git stash salva alterações no diretório de trabalho e no índice para que você possa mudar para outra tarefa e restaurá-las depois. Pitmaster implementa o stash usando `refs/stash` e o reflog, no layout de armazenamento de git. `Repository` não tem um método stash; use a classe `Stash` abaixo.

Dois problemas conhecidos: com mais de uma entrada, `drop()` (e, portanto, `pop()`) mantém a entrada na lista ([#10](https://github.com/inline0/pitmaster/issues/10)), e `apply()` sobrescreve alterações não confirmadas em arquivos que o stash não afeta ([#11](https://github.com/inline0/pitmaster/issues/11)). Aplique os stashes em uma árvore de trabalho limpa.

## Como o stash funciona

Cada entrada do stash é um commit com dois pais:
- **Pai 0**: o commit HEAD no momento do stash (o estado base)
- **Pai 1**: um commit que captura o estado do índice (alterações preparadas)

A própria árvore do commit do stash captura o estado da árvore de trabalho (incluindo alterações não preparadas).

A "pilha" de stash é o reflog de `refs/stash`. O stash mais recente é o próprio `refs/stash`. As entradas mais antigas são entradas do reflog: `stash@{0}`, `stash@{1}` etc.

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

## Criando o objeto Stash

```php
use Pitmaster\Stash\Stash;

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

## Push (salvar alterações)

```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 faz o seguinte:
1. Lê o commit HEAD atual
2. Cria uma árvore a partir do índice atual (estado preparado)
3. Cria um "commit de índice" com HEAD como pai
4. Cria uma árvore a partir do diretório de trabalho atual (incluindo alterações não preparadas)
5. Cria o commit stash (árvore do diretório de trabalho, pais = HEAD + commit de índice)
6. Atualiza `refs/stash` para apontar para o commit stash
7. Acrescenta uma entrada ao reflog da pilha
8. Redefine a árvore de trabalho e o índice para HEAD

Após o push, os arquivos rastreados correspondem a HEAD. Os arquivos não rastreados permanecem, a menos que `includeUntracked` esteja definido; nesse caso, são removidos e armazenados na própria árvore do commit stash (git os armazena em um terceiro pai, então git os vê como alterações rastreadas). `push()` cria uma entrada mesmo quando nada mudou; nesse caso, git informa "No local changes to save".

### Com mensagem padrão

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

Em um HEAD desanexado, `<branch>` é `HEAD`.

## Pop (aplicar e remover)

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

Aplica a entrada stash do topo ao diretório de trabalho e a remove da pilha. Equivale a `apply()` seguido de `drop()`.

### Remover uma entrada específica

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

## Apply (restaurar sem remover)

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

Apply mescla o stash no arquivo do HEAD atual por arquivo (base do stash, HEAD atual, árvore do stash). A entrada stash permanece na pilha, e o índice não é alterado. Conflitos deixam marcadores rotulados como `Updated upstream` e `Stashed changes` nos arquivos e lançam `Pitmaster\Exceptions\MergeConflictException`; `pop()` então mantém a entrada. Apply não verifica primeiro se há alterações locais (consulte a observação no início).

## Listar entradas

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

Cada entrada contém:
- `index`: a posição no stash (0 = mais recente)
- `message`: a mensagem do stash
- `hash`: o hash do commit stash

## Drop (remover sem aplicar)

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

Se a única entrada for removida, `refs/stash` e seu reflog são removidos. Com mais entradas, `drop()` atualmente apenas move `refs/stash` e deixa o reflog inalterado, então a entrada permanece em `listEntries()` ([#10](https://github.com/inline0/pitmaster/issues/10)).

## Detalhes de armazenamento

### Estrutura do commit stash

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

<stash message>
```

### Estrutura do commit de índice

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

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

A identidade vem de `GIT_COMMITTER_NAME`, depois `GIT_AUTHOR_NAME`, depois `user.name` na configuração do repositório e, por fim, `Pitmaster` (a mesma ordem vale para o e-mail, terminando em `pitmaster@example.invalid`). A data vem de `GIT_COMMITTER_DATE`, depois `GIT_AUTHOR_DATE` e, em seguida, da hora atual. Ao contrário dos commits, stash não lê as constantes `PITMASTER_*`.

### Armazenamento de Reflog

As entradas stash são armazenadas em `.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
```

O reflog é lido em ordem cronológica. A lista stash inverte essa ordem (mais recentes primeiro). O reflog do stash é gravado independentemente de `core.logallrefupdates`.

## Uso de 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
```

A mensagem é o argumento após `push` (ou `save`); `-m` não é compatível. `pop`, `apply` e `drop` sempre funcionam em `stash@{0}`; CLI não tem `--include-untracked`.

## Limitações

- Apenas stash de todo o diretório de trabalho (sem `--keep-index`); arquivos não rastreados apenas por meio de `push($message, includeUntracked: true)`
- Não há modo `--patch` (seleção interativa de trechos)
- Pop/apply restaura o estado da árvore de trabalho; não restaura o índice. Edições preparadas voltam como edições não preparadas, e arquivos recém-preparados voltam como arquivos não rastreados
- `stash@{n}` não é aceito por `Repository::resolve()`
- Stash em uma árvore de trabalho vinculada usa a configuração e o local do reflog incorretos ([#14](https://github.com/inline0/pitmaster/issues/14))
