الوحدة 4 · عادات احترافية
التراجع بأمان: Revert و Reset والـ Reflog
18 دقراءة 4 داختر بحكمة
الدرس في لمحة
اختر طريقة التراجع المناسبة لأي خطأ (restore أو amend أو revert أو reset بأوضاعه الثلاثة)، واستخدم الـ reflog لاسترجاع commits ظننت أنها ضاعت.
- 1اختر الأداة بحسب المكان
- 2الـ Amend: أصلح آخر commit
- 3الـ Revert: التراجع علنًا
- 4الـ Reset: حرّك الفرع، بثلاث طرق
- 5الـ Reflog: الصندوق الأسود في Git
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
كل شخص يكسر شيئًا في Git عاجلًا أو آجلًا. تنشئ commit على الفرع الخطأ، أو ترفع خطأً برمجيًا، أو تمحو عمل صباح كامل بـ reset. والخبر الجيد: نادرًا ما يرمي Git البيانات، ولمعظم الأخطاء طريقة تراجع نظيفة. الفخّ هو أن تلجأ إلى أي أمر تراجع تتذكّره نصف تذكّر. اختر بطرح سؤال واحد: أين يعيش الخطأ؟
اختر الأداة بحسب المكان
| أين الخطأ | الأمر | هل يعيد كتابة السجلّ؟ |
|---|---|---|
| تعديلات غير مجهّزة في ملف | git restore <file> |
لا (لكنه يرمي التعديلات) |
| مجهّز، لم يُسجَّل في commit | git restore --staged <file> |
لا |
| آخر commit، لم يُرفع | git commit --amend |
نعم، الـ commit المحلي فقط |
| commits محلية، لم تُرفع | git reset |
نعم، محليًا فقط |
| commits رُفعت وتمّت مشاركتها | git revert <commit> |
لا |
يتبع الجدول قاعدة واحدة: حين يصبح commit ما لدى آخرين، تضيف فوقه commits جديدة بدل أن تغيّره.
الـ Amend: أصلح آخر commit
نسيت ملفًا أو كتبت رسالة سيئة، ولم ترفع بعد؟
git add contact.css
git commit --amend --no-edit # same message, file included
git commit --amend -m "Add contact form" # or a new messageالـ amend لا يعدّل الـ commit؛ بل يستبدله بآخر جديد بمعرّف جديد. هذا مقبول محليًا، ومشكلة إن كنت قد رفعت، للسبب نفسه الذي يجعل الـ rebase مشكلة.
الـ Revert: التراجع علنًا
دمجت في main commit عطّل قائمة التنقّل على الجوال. وقد رُفع، وسحبه زملاؤك. لا تُعِد كتابة أي شيء. أضف commit يعكسه:
git revert HEAD # if it's the latest commit
git revert 8f1e2aa # or any commit by IDينشئ Git commit جديدًا، Revert "Move nav into header"، يطبّق التغييرات المعاكسة تمامًا. ويظل السجلّ يُظهر الخطأ وإصلاحه، وهذا صادق وآمن: يحصل الجميع على الـ revert بـ pull عادي. وللتراجع عن merge commit، أخبر Git أيّ الأبوين يُعتمد خطًا رئيسيًا (mainline)، وهو الأول عادةً: git revert -m 1 <merge-commit>.
أحيانًا يكون جزء فقط من الـ commit خاطئًا. إن كان نقل قائمة التنقّل سليمًا باستثناء styles.css، فلا داعي للتراجع عن الـ commit كله. أعِد ذلك الملف وحده إلى ما كان عليه قبل الـ commit، وافحصه، وسجّل النتيجة في commit إصلاح عادي:
git restore --source=8f1e2aa~1 styles.css
git diff
git commit -am "Restore mobile nav styles"هذا ما زال «إضافة إلى السجلّ لا إعادة كتابة له»، لكن بنطاق أصغر.
الـ Reset: حرّك الفرع، بثلاث طرق
git reset <commit> يحرّك الفرع الحالي إلى commit آخر. وما يفعله بالـ index والـ working tree يعتمد على الوضع (mode)، وهنا يتأذّى الناس.
ثلاث حالات، وثلاثة أوضاع:
git reset --soft HEAD~2 # squash: the last two commits' changes become staged, ready for one commit
git reset HEAD~1 # un-commit (--mixed): changes return as unstaged edits to rework
git reset --hard HEAD~1 # discard: the commit and its changes are gone from the branch and filesاستخدام كلاسيكي: أنشأت commit على main بينما كنت تقصد أن تكون على فرع. سجّل أي تعديلات غير مسجّلة أو خبّئها أولًا (فـ --hard سيمحوها)، ثم أنشئ الفرع حيث أنت، ثم أرجِع main:
git branch contact-form # the branch keeps your commit
git reset --hard origin/main # main goes back to match GitHub
git switch contact-formالـ Reflog: الصندوق الأسود في Git
هذه هي شبكة الأمان التي تجعل كل ما سبق أقل إخافة. في كل مرة يتحرّك فيها الـ HEAD (commit، أو switch، أو reset، أو rebase، أو merge)، يكتب Git سطرًا في الـ reflog:
git reflog2b7f0c1 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
9e4a1d5 HEAD@{1}: commit: Add contact form validation
4c8b2e7 HEAD@{2}: commit: Add contact form
a71d3f0 HEAD@{3}: checkout: moving from footer to mainبعد ذلك الـ git reset --hard HEAD~3 العرَضي، لم تعد الـ commits الثلاثة على أي فرع، لكنها ما زالت موجودة، و HEAD@{1} يشير إلى أحدثها. استعدها:
git reset --hard HEAD@{1} # put main back where it was
# or, more cautiously, keep them on a new branch to inspect first
git switch -c rescue 9e4a1d5الـ reflog محلي (لا يُرفع ولا يُستنسخ)، وسطوره تنتهي صلاحيتها: افتراضيًا بعد 90 يومًا، أو 30 يومًا للـ commits التي لا يصل إليها أي فرع. وهذا وقت أكثر من كافٍ لملاحظة الخطأ.
ما لا يستطيع الـ reflog إنقاذه هو العمل الذي لم يُسجَّل قط. فـ git restore و git reset --hard يكتبان فوق التعديلات غير المسجّلة في ملفاتك، ولا يعرف أي سجلّ عنها شيئًا. لذا فعادة الأمان الحقيقية بسيطة: أنشئ commits مبكرًا وكثيرًا على فرعك. تستطيع دائمًا ضغط الـ commits قبل المراجعة؛ لكنك لا تستطيع استعادة تعديلات لم تسجّلها قط.
لكن بعض العمل الجاري ليس جاهزًا ليُسجَّل في commit. في الدرس التالي ستركنه جانبًا بـ git stash، وتمنع Git من تتبّع ملفات ما كان يجب أن يتتبّعها أصلًا.
أهم النقاط
- اختر طريقة التراجع بحسب مكان الخطأ (الـ working tree أو الـ index أو commit محلي أو commit مشارَك)، لا بحسب الأمر الذي تتذكّره.
git revert <commit>يضيف commit جديدًا يلغي commit قديمًا، وهي الطريقة الآمنة للتراجع عن أي شيء رُفع بالفعل.git resetيحرّك الفرع الحالي:--softيُبقي التغييرات مجهّزة، و--mixedيُبقيها غير مجهّزة، و--hardيرميها.git commit --amendيستبدل آخر commit، لذا استخدمه قبل الرفع فقط.git reflogيسرد الأماكن التي مرّ بها الـ HEAD، فيمكن العثور على commits ضاعت بسبب reset أو rebase واستعادتها لأسابيع بعد ذلك.