Скривалище
Git stash запазва промените в работната директория и индекса, за да можете да преминете към друга задача и да ги възстановите по-късно. Pitmaster реализира stash чрез refs/stash и reflog, според структурата за съхранение на git. В Repository няма метод stash; използвайте класа Stash по-долу.
Има два известни проблема: при повече от един запис drop() (и следователно pop()) оставя записа в списъка (#10), а apply() презаписва незакомитени промени във файлове, които stash не засяга (#11). Прилагайте stash само към чисто работно дърво.
Как работи stash
Всеки запис в stash е комит с два родителя:
- Родител 0: HEAD комитът към момента на stash (базовото състояние)
- Родител 1: комит, който улавя състоянието на индекса (подготвените промени)
Дървото на самия stash комит улавя състоянието на работното дърво (включително неподготвените промени).
„Стекът“ на stash е reflog на refs/stash. Най-новият stash е самият refs/stash. По-старите записи са записи в reflog: stash@{0}, stash@{1} и т.н.
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
Създаване на обекта Stash
use Pitmaster\Stash\Stash;
$stash = new Stash(
$repo->objectDatabase(),
$repo->refDatabase(),
$repo->gitDir(),
$repo->workDir(),
);
Push (запазване на промените)
// 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 извършва следното:
- Чете текущия commit на HEAD
- Изгражда дърво от текущия индекс (добавеното за commit състояние)
- Създава „index commit“ с HEAD като родител
- Изгражда дърво от текущата работна директория (включително недобавените промени)
- Създава stash commit (дървото на работната директория, родители = HEAD + index commit)
- Обновява
refs/stash, така че да сочи към stash commit - Добавя запис в reflog за стека
- Връща работното дърво и индекса към HEAD
След push проследяваните файлове съвпадат с HEAD. Непроследяваните файлове остават, освен ако не е зададено includeUntracked; тогава те се премахват и се съхраняват в собственото дърво на stash commit (git ги съхранява в трети родител, така че git ги вижда като проследявани промени). push() създава запис дори когато нищо не се е променило; в този случай git съобщава „No local changes to save“.
Със съобщение по подразбиране
$stashId = $stash->push();
// Message: "WIP on <branch>: <short hash> <HEAD subject>"
// With a message: "On <branch>: <message>"
При отделен HEAD, <branch> е HEAD.
Pop (прилагане и премахване)
$stash->pop();
Прилага най-горния запис в stash към работната директория и го премахва от стека. Еквивалентно е на apply(), последвано от drop().
Pop на конкретен запис
$stash->pop(1); // Pop stash@{1}
Apply (възстановяване без премахване)
$stash->apply(); // Apply stash@{0}
$stash->apply(2); // Apply stash@{2}
Apply слива stash с текущия файл на HEAD файл по файл (база на stash, текущ HEAD, дърво на stash). Записът остава в стека, а индексът не се променя. При конфликти във файловете се оставят маркери с етикети Updated upstream и Stashed changes и се генерира Pitmaster\Exceptions\MergeConflictException; pop() след това запазва записа. Apply не проверява предварително за локални промени (вижте бележката в началото).
Списък на записите
$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
}
Всеки запис съдържа:
index: позицията в stash (0 = най-новият)message: съобщението на stashhash: хеша на stash commit
Drop (премахване без прилагане)
$stash->drop(); // Drop stash@{0}
$stash->drop(1); // Drop stash@{1}
Ако премахнете единствения запис, refs/stash и неговият reflog се премахват. При повече записи drop() в момента само премества refs/stash и оставя reflog непроменен, така че записът остава в listEntries() (#10).
Подробности за съхранението
Структура на stash commit
commit <hash>
tree <worktree-tree-hash>
parent <HEAD-hash>
parent <index-commit-hash>
author <current identity>
committer <current identity>
<stash message>
Структура на index commit
commit <hash>
tree <index-tree-hash>
parent <HEAD-hash>
author <current identity>
committer <current identity>
index on <branch>: <short hash> <HEAD subject>
Идентичността се взема от GIT_COMMITTER_NAME, после от GIT_AUTHOR_NAME, след това от user.name в конфигурацията на хранилището и накрая от Pitmaster (същият ред важи за имейла, който завършва с pitmaster@example.invalid). Датата се взема от GIT_COMMITTER_DATE, после от GIT_AUTHOR_DATE и накрая от текущото време. За разлика от commit-ите, stash не чете константите PITMASTER_*.
Съхранение в Reflog
Записите в stash се съхраняват в .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 се чете в хронологичен ред. Списъкът на stash обръща реда му (най-новите първи). Reflog на stash се записва независимо от core.logallrefupdates.
Употреба на 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
Съобщението е аргументът след push (или save); -m не се поддържа. pop, apply и drop винаги работят с stash@{0}; CLI няма --include-untracked.
Ограничения
- Поддържа се само stash на цялата работна директория (без
--keep-index); непроследяваните файлове се включват само чрезpush($message, includeUntracked: true) - Няма режим
--patch(интерактивен избор на парчета) - Pop/apply възстановява състоянието на работното дърво, но не и на индекса. Добавените за commit редакции се връщат като недобавени редакции, а новите добавени файлове се връщат като непроследявани
stash@{n}не се приема отRepository::resolve()- Stash в свързано работно дърво използва грешно местоположение за конфигурацията и reflog (#14)