
# التخزين المؤقت

يحفظ Git stash تغييرات دليل العمل والفهرس، لتتمكن من الانتقال إلى مهمة أخرى ثم استعادتها لاحقًا. ينفذ Pitmaster التخزين المؤقت باستخدام `refs/stash` وسجل reflog وفق بنية تخزين git. لا توجد طريقة stash في `Repository`؛ استخدم الفئة `Stash` أدناه.

مشكلتان معروفتان: عند وجود أكثر من إدخال، يُبقي `drop()` (وبالتالي `pop()`) الإدخال في القائمة ([#10](https://github.com/inline0/pitmaster/issues/10))، كما أن `apply()` يستبدل التغييرات غير الملتزم بها في الملفات التي لا يمسها التخزين المؤقت ([#11](https://github.com/inline0/pitmaster/issues/11)). طبّق العناصر المخزنة مؤقتًا على شجرة عمل نظيفة.

## آلية عمل التخزين المؤقت

كل إدخال تخزين مؤقت هو التزام له والدان:
- **الوالد 0**: التزام HEAD وقت التخزين المؤقت (الحالة الأساسية)
- **الوالد 1**: التزام يسجل حالة الفهرس (التغييرات المُدرجة)

تسجل شجرة الالتزام الخاص بالتخزين المؤقت حالة شجرة العمل نفسها (بما فيها التغييرات غير المُدرجة).

«مكدس» التخزين المؤقت هو سجل reflog الخاص بـ`refs/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. يقرأ التزام HEAD الحالي
2. ينشئ شجرة من الفهرس الحالي (الحالة المُدرجة)
3. ينشئ «التزام الفهرس» ويجعل HEAD والده
4. ينشئ شجرة من دليل العمل الحالي (بما فيه التغييرات غير المُدرجة)
5. ينشئ التزام التخزين المؤقت (شجرة دليل العمل، والوالدان هما HEAD والتزام الفهرس)
6. يحدّث `refs/stash` ليشير إلى التزام التخزين المؤقت
7. يضيف إدخال reflog للمكدس
8. يعيد ضبط شجرة العمل والفهرس إلى HEAD

بعد Push، تطابق الملفات المتتبعة HEAD. وتبقى الملفات غير المتتبعة ما لم يُضبط `includeUntracked`؛ عندها تُزال وتُخزّن في شجرة التزام التخزين المؤقت نفسها (أما 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();
```

يطبق أعلى إدخال في التخزين المؤقت على دليل العمل ويزيله من المكدس. ويعادل `apply()` يتبعه `drop()`.

### سحب إدخال محدد

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

## Apply (استعادة دون إزالة)

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

يدمج Apply التخزين المؤقت في ملف HEAD الحالي ملفًا تلو الآخر (أساس التخزين المؤقت، وHEAD الحالي، وشجرة التخزين المؤقت). يبقى الإدخال في المكدس ولا يُمس الفهرس. تترك التعارضات علامات باسمَي `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`: موضع التخزين المؤقت (0 = الأحدث)
- `message`: رسالة التخزين المؤقت
- `hash`: تجزئة التزام التخزين المؤقت

## 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)).

## تفاصيل التخزين

### بنية التزام التخزين المؤقت

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

<stash message>
```

### بنية التزام الفهرس

```
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`، ثم الوقت الحالي. وعلى خلاف الالتزامات، لا يقرأ التخزين المؤقت ثوابت `PITMASTER_*`.

### تخزين Reflog

تُخزّن إدخالات التخزين المؤقت في `.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 بترتيب زمني. وتعكس قائمة التخزين المؤقت هذا الترتيب (الأحدث أولًا). ويُكتب سجل reflog للتخزين المؤقت بصرف النظر عن `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`.

## القيود

- يدعم التخزين المؤقت لكامل دليل العمل فقط (لا `--keep-index`)؛ وتُضمّن الملفات غير المتتبعة عبر `push($message, includeUntracked: true)` فقط
- لا يوجد وضع `--patch` (لاختيار الأجزاء تفاعليًا)
- يعيد Pop/Apply حالة شجرة العمل؛ ولا يستعيد الفهرس. تعود التعديلات المُدرجة كتعديلات غير مُدرجة، وتعود الملفات الجديدة التي أُدرجت كملفات غير متتبعة
- لا يقبل `stash@{n}` بواسطة `Repository::resolve()`
- يستخدم التخزين المؤقت في شجرة عمل مرتبطة موقع الإعدادات وسجل reflog الخطأ ([#14](https://github.com/inline0/pitmaster/issues/14))
