Přejít k obsahu

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.

Kód
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

PHP
use Pitmaster\Stash\Stash;

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

Push (uložení změn)

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 provede následující:

  1. Načte aktuální commit HEAD
  2. Sestaví strom z aktuálního indexu (připraveného stavu)
  3. Vytvoří „index commit“ s HEAD jako rodičem
  4. Sestaví strom z aktuálního pracovního adresáře (včetně nepřipravených změn)
  5. Vytvoří commit stash (strom pracovního adresáře, rodiče = HEAD + index commit)
  6. Nastaví refs/stash tak, aby ukazoval na commit stash
  7. Přidá záznam reflogu zásobníku
  8. 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

PHP
$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í)

PHP
$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

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

Apply (obnovení bez odebrání)

PHP
$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

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ždá položka obsahuje:

  • index: pozici stash (0 = nejnovější)
  • message: zprávu stash
  • hash: hash commitu stash

Drop (odebrání bez použití)

PHP
$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

Kód
commit <hash>
tree <worktree-tree-hash>
parent <HEAD-hash>
parent <index-commit-hash>
author <current identity>
committer <current identity>

<stash message>

Struktura index commitu

Kód
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:

Kód
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

Shell
# 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řes push($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)