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.
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
use Pitmaster\Stash\Stash;
$stash = new Stash(
$repo->objectDatabase(),
$repo->refDatabase(),
$repo->gitDir(),
$repo->workDir(),
);
Push (salvarea modificărilor)
// 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:
- Citește commit-ul HEAD curent
- Construiește un arbore din indexul curent (starea staged)
- Creează un „commit de index” cu HEAD ca părinte
- Construiește un arbore din directorul de lucru curent (inclusiv modificările nestaged)
- Creează commit-ul stash (arborele de lucru, părinți = HEAD + commit-ul de index)
- Actualizează
refs/stashastfel încât să indice commit-ul stash - Adaugă o înregistrare reflog pentru stivă
- 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
$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)
$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
$stash->pop(1); // Pop stash@{1}
Apply (restaurare fără eliminare)
$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
$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-uluihash: hash-ul commit-ului stash
Drop (eliminare fără aplicare)
$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
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
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:
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
# 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 prinpush($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 deRepository::resolve()- Stash-ul într-un worktree asociat folosește locația greșită pentru configurație și reflog (#14)