Ga naar de inhoud

Stash

Git stash slaat wijzigingen in de werkdirectory en index op, zodat u naar een andere taak kunt overschakelen en ze later kunt herstellen. Pitmaster implementeert stash met refs/stash en de reflog, volgens de opslagindeling van git. Repository heeft geen stash-methode; gebruik de onderstaande class Stash.

Twee bekende problemen: bij meer dan één item laat drop() (en dus ook pop()) het item in de lijst staan (#10), en apply() overschrijft niet-vastgelegde wijzigingen in bestanden die de stash niet aanraakt (#11). Pas stashes toe op een schone werkboom.

Hoe stash werkt

Elke stashvermelding is een commit met twee ouders:

  • Ouder 0: de HEAD-commit ten tijde van de stash (de basistoestand)
  • Ouder 1: een commit die de indexstatus vastlegt (klaargezette wijzigingen)

De eigen boom van de stash-commit legt de status van de werkboom vast (inclusief niet-klaargezette wijzigingen).

De stash-'stack' is de reflog van refs/stash. De meest recente stash is refs/stash zelf. Oudere items zijn reflog-vermeldingen: stash@{0}, stash@{1}, enzovoort.

Code
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

Het Stash-object maken

PHP
use Pitmaster\Stash\Stash;

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

Push (wijzigingen opslaan)

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 doet het volgende:

  1. Leest de huidige HEAD-commit
  2. Bouwt een tree op basis van de huidige index (de gestagede status)
  3. Maakt een "index-commit" met HEAD als parent
  4. Bouwt een tree op basis van de huidige werkdirectory (inclusief niet-gestagede wijzigingen)
  5. Maakt de stash-commit (worktree-tree, parents = HEAD + index-commit)
  6. Werkt refs/stash bij zodat deze naar de stash-commit verwijst
  7. Voegt een reflog-vermelding toe voor de stack
  8. Zet de werkdirectory en index terug naar HEAD

Na push komen bijgehouden bestanden overeen met HEAD. Niet-bijgehouden bestanden blijven staan, tenzij includeUntracked is ingesteld; dan worden ze verwijderd en opgeslagen in de eigen tree van de stash-commit (git slaat ze in plaats daarvan op in een derde parent, zodat git ze als bijgehouden wijzigingen ziet). push() maakt ook een vermelding aan als er niets is gewijzigd; git meldt dan "No local changes to save".

Met standaardbericht

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

Bij een detached HEAD is <branch> gelijk aan HEAD.

Pop (toepassen en verwijderen)

PHP
$stash->pop();

Past de bovenste stash-vermelding toe op de werkdirectory en verwijdert deze uit de stack. Dit is gelijk aan apply() gevolgd door drop().

Een specifieke vermelding poppen

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

Apply (herstellen zonder verwijderen)

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

Apply voegt de stash bestand voor bestand samen met het huidige HEAD-bestand (stash-basis, huidig HEAD, stash-tree). De stash-vermelding blijft in de stack staan en de index wordt niet aangepast. Bij conflicten blijven markeringen met de labels Updated upstream en Stashed changes in de bestanden staan en wordt Pitmaster\Exceptions\MergeConflictException gegenereerd; pop() behoudt de vermelding vervolgens. Apply controleert vooraf niet op lokale wijzigingen (zie de opmerking bovenaan).

Vermeldingen weergeven

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
}

Elke vermelding bevat:

  • index: de stash-positie (0 = meest recent)
  • message: het stash-bericht
  • hash: de hash van de stash-commit

Drop (verwijderen zonder toe te passen)

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

Als je de enige vermelding verwijdert, worden refs/stash en de bijbehorende reflog verwijderd. Zijn er meer vermeldingen, dan verplaatst drop() momenteel alleen refs/stash en blijft de reflog ongewijzigd, waardoor de vermelding in listEntries() blijft staan (#10).

Opslagdetails

Structuur van de stash-commit

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

<stash message>

Structuur van de index-commit

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

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

De identiteit komt uit GIT_COMMITTER_NAME, vervolgens GIT_AUTHOR_NAME, daarna user.name in de repositoryconfiguratie en vervolgens Pitmaster (voor het e-mailadres geldt dezelfde volgorde, eindigend met pitmaster@example.invalid). De datum komt uit GIT_COMMITTER_DATE, vervolgens GIT_AUTHOR_DATE en daarna de huidige tijd. In tegenstelling tot commits leest stash de constanten PITMASTER_* niet.

Reflog-opslag

Stash-vermeldingen worden opgeslagen in .git/logs/refs/stash:

Code
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

De reflog wordt in chronologische volgorde gelezen. De stash-lijst keert die volgorde om (nieuwste eerst). De stash-reflog wordt geschreven ongeacht core.logallrefupdates.

Gebruik van 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

Het bericht is het argument na push (of save); -m wordt niet ondersteund. pop, apply en drop werken altijd op stash@{0}; de CLI heeft geen --include-untracked.

Beperkingen

  • Alleen een volledige stash van de werkdirectory (geen --keep-index); niet-bijgehouden bestanden alleen via push($message, includeUntracked: true)
  • Geen modus --patch (interactieve selectie van hunk-wijzigingen)
  • Pop/apply herstelt de status van de werkdirectory; de index wordt niet hersteld. Gestagede bewerkingen komen terug als niet-gestagede bewerkingen en nieuw gestagede bestanden komen terug als niet-bijgehouden bestanden
  • stash@{n} wordt niet geaccepteerd door Repository::resolve()
  • Stash in een gekoppelde worktree gebruikt de verkeerde configuratie en reflog-locatie (#14)