انتقل إلى المحتوى

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

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

مشكلتان معروفتان: عند وجود أكثر من إدخال، يُبقي drop() (وبالتالي pop()) الإدخال في القائمة (#10)، كما أن apply() يستبدل التغييرات غير الملتزم بها في الملفات التي لا يمسها التخزين المؤقت (#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).

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

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

الكود
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

سطر الأوامر
# 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)