
# Скривалище

Git stash запазва промените в работната директория и индекса, за да можете да преминете към друга задача и да ги възстановите по-късно. Pitmaster реализира stash чрез `refs/stash` и reflog, според структурата за съхранение на git. В `Repository` няма метод stash; използвайте класа `Stash` по-долу.

Има два известни проблема: при повече от един запис `drop()` (и следователно `pop()`) оставя записа в списъка ([#10](https://github.com/inline0/pitmaster/issues/10)), а `apply()` презаписва незакомитени промени във файлове, които stash не засяга ([#11](https://github.com/inline0/pitmaster/issues/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](https://github.com/inline0/pitmaster/issues/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

```bash
# 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](https://github.com/inline0/pitmaster/issues/14))
