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), e apply() sobrescreve alterações não confirmadas em arquivos que o stash não afeta (#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
use Pitmaster\Stash\Stash;
$stash = new Stash(
$repo->objectDatabase(),
$repo->refDatabase(),
$repo->gitDir(),
$repo->workDir(),
);
Push (salvar alterações)
// 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:
- Lê o commit HEAD atual
- Cria uma árvore a partir do índice atual (estado preparado)
- Cria um "commit de índice" com HEAD como pai
- Cria uma árvore a partir do diretório de trabalho atual (incluindo alterações não preparadas)
- Cria o commit stash (árvore do diretório de trabalho, pais = HEAD + commit de índice)
- Atualiza
refs/stashpara apontar para o commit stash - Acrescenta uma entrada ao reflog da pilha
- 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
$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)
$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
$stash->pop(1); // Pop stash@{1}
Apply (restaurar sem remover)
$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
$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 stashhash: o hash do commit stash
Drop (remover sem aplicar)
$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).
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
# 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 depush($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 porRepository::resolve()- Stash em uma árvore de trabalho vinculada usa a configuração e o local do reflog incorretos (#14)