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); apply() přepíše nepotvrzené změny v souborech, kterých se zásobník netýká (#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
use Pitmaster\Stash\Stash;
$stash = new Stash(
$repo->objectDatabase(),
$repo->refDatabase(),
$repo->gitDir(),
$repo->workDir(),
);
Push (uložení změn)
// 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í:
- Načte aktuální commit HEAD
- Sestaví strom z aktuálního indexu (připraveného stavu)
- Vytvoří „index commit“ s HEAD jako rodičem
- Sestaví strom z aktuálního pracovního adresáře (včetně nepřipravených změn)
- Vytvoří commit stash (strom pracovního adresáře, rodiče = HEAD + index commit)
- Nastaví
refs/stashtak, aby ukazoval na commit stash - Přidá záznam reflogu zásobníku
- 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
$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í)
$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
$stash->pop(1); // Pop stash@{1}
Apply (obnovení bez odebrání)
$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
$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 stashhash: hash commitu stash
Drop (odebrání bez použití)
$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).
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
# 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řespush($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)