Treci la conținut

Stash

Git stash salvează modificările directorului de lucru și ale indexului, astfel încât să puteți trece la o altă sarcină și să le restaurați mai târziu. Pitmaster implementează stash-ul folosind refs/stash și reflog-ul, în structura de stocare a git. Repository nu are o metodă stash; folosiți clasa Stash de mai jos.

Două probleme cunoscute: când există mai multe intrări, drop() (și, prin urmare, pop()) lasă intrarea în listă (#10), iar apply() suprascrie modificările neconfirmate din fișierele pe care stash-ul nu le atinge (#11). Aplicați stash-uri într-un arbore de lucru curat.

Cum funcționează stash-ul

Fiecare intrare stash este un commit cu doi părinți:

  • Părinte 0: commit-ul HEAD de la momentul stash-ului (starea de bază)
  • Părinte 1: un commit care surprinde starea indexului (modificările staged)

Arborele propriu al commit-ului stash surprinde starea arborelui de lucru (inclusiv modificările nestaged).

„Stiva” stash este reflog-ul lui refs/stash. Cel mai recent stash este chiar refs/stash. Intrările mai vechi sunt înregistrări reflog: stash@{0}, stash@{1} etc.

Cod
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

Crearea obiectului Stash

PHP
use Pitmaster\Stash\Stash;

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

Push (salvarea modificărilor)

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 efectuează următoarele:

  1. Citește commit-ul HEAD curent
  2. Construiește un arbore din indexul curent (starea staged)
  3. Creează un „commit de index” cu HEAD ca părinte
  4. Construiește un arbore din directorul de lucru curent (inclusiv modificările nestaged)
  5. Creează commit-ul stash (arborele de lucru, părinți = HEAD + commit-ul de index)
  6. Actualizează refs/stash astfel încât să indice commit-ul stash
  7. Adaugă o înregistrare reflog pentru stivă
  8. Resetează arborele de lucru și indexul la HEAD

După push, fișierele urmărite corespund cu HEAD. Fișierele neurmărite rămân, cu excepția cazului în care este setat includeUntracked; atunci sunt eliminate și stocate în arborele propriu al commit-ului stash (git le stochează într-un al treilea părinte, astfel încât git le vede ca modificări urmărite). push() creează o intrare chiar și când nu s-a schimbat nimic, caz în care git raportează „No local changes to save”.

Cu mesaj implicit

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

Cu un HEAD detașat, <branch> este HEAD.

Pop (aplicare și eliminare)

PHP
$stash->pop();

Aplică intrarea stash din vârf în directorul de lucru și o elimină din stivă. Echivalent cu apply() urmat de drop().

Eliminarea unei intrări anume

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

Apply (restaurare fără eliminare)

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

Apply îmbină stash-ul în fișierele HEAD curent, unul câte unul (baza stash-ului, HEAD-ul curent, arborele stash-ului). Intrarea stash rămâne în stivă, iar indexul nu este atins. Conflictele lasă în fișiere marcajele Updated upstream și Stashed changes și aruncă Pitmaster\Exceptions\MergeConflictException; apoi pop() păstrează intrarea. Apply nu verifică mai întâi dacă există modificări locale (consultați nota de la început).

Listarea intrărilor

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
}

Fiecare intrare conține:

  • index: poziția stash-ului (0 = cel mai recent)
  • message: mesajul stash-ului
  • hash: hash-ul commit-ului stash

Drop (eliminare fără aplicare)

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

Dacă eliminați singura intrare, refs/stash și reflog-ul său sunt eliminate. Când există mai multe intrări, drop() doar mută în prezent refs/stash și lasă reflog-ul neschimbat, așa că intrarea rămâne în listEntries() (#10).

Detalii despre stocare

Structura commit-ului stash

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

<stash message>

Structura commit-ului de index

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

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

Identitatea provine din GIT_COMMITTER_NAME, apoi din GIT_AUTHOR_NAME, apoi din user.name din configurația depozitului, apoi din Pitmaster (aceeași ordine se aplică e-mailului, care se încheie cu pitmaster@example.invalid). Data provine din GIT_COMMITTER_DATE, apoi din GIT_AUTHOR_DATE, apoi din ora curentă. Spre deosebire de commit-uri, stash nu citește constantele PITMASTER_*.

Stocare Reflog

Intrările stash sunt stocate în .git/logs/refs/stash:

Cod
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-ul este citit în ordine cronologică. Lista stash-urilor inversează ordinea (cel mai recent primul). Reflog-ul stash este scris indiferent de core.logallrefupdates.

Utilizarea 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

Mesajul este argumentul de după push (sau save); -m nu este acceptat. pop, apply și drop funcționează întotdeauna pe stash@{0}; CLI nu are --include-untracked.

Limitări

  • Doar stash-ul întregului director de lucru (fără --keep-index); fișierele neurmărite doar prin push($message, includeUntracked: true)
  • Fără modul --patch (selectarea interactivă a fragmentelor)
  • Pop/apply restaurează starea arborelui de lucru; nu restaurează indexul. Modificările staged revin ca modificări nestaged, iar fișierele nou staged revin ca fișiere neurmărite
  • stash@{n} nu este acceptat de Repository::resolve()
  • Stash-ul într-un worktree asociat folosește locația greșită pentru configurație și reflog (#14)