Към съдържанието

Скривалище

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

PHP
use Pitmaster\Stash\Stash;

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

Push (запазване на промените)

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 извършва следното:

  1. Чете текущия commit на HEAD
  2. Изгражда дърво от текущия индекс (добавеното за commit състояние)
  3. Създава „index commit“ с HEAD като родител
  4. Изгражда дърво от текущата работна директория (включително недобавените промени)
  5. Създава stash commit (дървото на работната директория, родители = HEAD + index commit)
  6. Обновява refs/stash, така че да сочи към stash commit
  7. Добавя запис в reflog за стека
  8. Връща работното дърво и индекса към HEAD

След push проследяваните файлове съвпадат с HEAD. Непроследяваните файлове остават, освен ако не е зададено includeUntracked; тогава те се премахват и се съхраняват в собственото дърво на stash commit (git ги съхранява в трети родител, така че git ги вижда като проследявани промени). push() създава запис дори когато нищо не се е променило; в този случай git съобщава „No local changes to save“.

Със съобщение по подразбиране

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

При отделен HEAD, <branch> е HEAD.

Pop (прилагане и премахване)

PHP
$stash->pop();

Прилага най-горния запис в stash към работната директория и го премахва от стека. Еквивалентно е на apply(), последвано от drop().

Pop на конкретен запис

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

Apply (възстановяване без премахване)

PHP
$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 не проверява предварително за локални промени (вижте бележката в началото).

Списък на записите

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
}

Всеки запис съдържа:

  • index: позицията в stash (0 = най-новият)
  • message: съобщението на stash
  • hash: хеша на stash commit

Drop (премахване без прилагане)

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