التراجع عن الأخطاء في Git: restore و revert و reset
عدّلت ملفاً فأفسدته، أو كتبت رسالة commit فيها خطأ إملائي، أو حفظت شيئاً ما كان يجب أن يُحفظ. كل مبرمج يخطئ، وميزة Git الكبرى أن كل شيء تقريباً قابل للتراجع. المهم أن تختار الأداة المناسبة لكل موقف. في هذا الدرس ستتعلم خمس أدوات، من الأبسط إلى الأقوى.
تخيّل Git مثل آلة زمن لمشروعك: تستطيع العودة لأي لحظة محفوظة. لكن لبعض الرحلات قواعد أمان يجب معرفتها.
إلغاء تعديل لم يُحفظ: git restore
Section titled “إلغاء تعديل لم يُحفظ: git restore”عدّلت index.html وندمت:
git status --short M index.htmlgit restore index.htmlgit status --shortالأمر الثاني لا يطبع شيئاً: الملف عاد كما كان في آخر commit، والمجلد نظيف.
إخراج ملف من منطقة التجهيز
Section titled “إخراج ملف من منطقة التجهيز”نفّذت git add . فدخل ملف لا تريده في الـ commit:
git status --shortA about.htmlA plan.txtgit restore --staged plan.txtgit status --shortA about.html?? plan.txt--staged تُخرج الملف من منطقة التجهيز دون المساس بمحتواه. عاد plan.txt غير متتبَّع، وبقي about.html جاهزاً للحفظ.
تصحيح آخر commit: –amend
Section titled “تصحيح آخر commit: –amend”حفظت برسالة فيها خطأ إملائي:
git commit -m "اضافه صفحه"git commit --amend -m "إضافة صفحة من نحن"git log --oneline410bcff إضافة صفحة من نحنb655445 إيقاف تتبع config.jsonb0e20db إضافة .gitignore وملفات المشروع66447fb إضافة الصفحة الرئيسية وملف README--amend تستبدل آخر commit بنسخة مصححة: لا commit إضافياً في التاريخ. وتصلح أيضاً لإضافة ملف نسيته:
git add about.cssgit commit --amend --no-edit--no-edit تعني «أبقِ الرسالة كما هي». الآن آخر commit يحتوي الملفين معاً. ولاحظ أن رقم الـ commit تغيّر بعد كل تعديل، لأنه في الحقيقة commit جديد حلّ محل القديم.
عكس commit قديم: git revert
Section titled “عكس commit قديم: git revert”أضفت شريطاً متحركاً للصفحة وحفظته، ثم تبيّن أنه فكرة سيئة:
git log --oneline -357477b1 إضافة شريط متحرك7683b31 إضافة صفحة من نحنb655445 إيقاف تتبع config.jsongit revert --no-edit HEADgit log --oneline -353be69f Revert "إضافة شريط متحرك"57477b1 إضافة شريط متحرك7683b31 إضافة صفحة من نحنrevert لا يحذف شيئاً: ينشئ commit جديداً يعكس تغييرات الـ commit المحدد. الشريط اختفى من الملف، والتاريخ بقي كاملاً وصادقاً: «أضفنا ثم تراجعنا». و HEAD تعني آخر commit، ويمكن كتابة رقم أي commit مكانها.
هذه الطريقة الآمنة للتراجع عن شيء رفعته على GitHub، لأنها تضيف للتاريخ ولا تغيّره.
الرجوع بالزمن: git reset
Section titled “الرجوع بالزمن: git reset”reset يحرّك الفرع إلى commit أقدم، كأن ما بعده لم يحدث. عندنا ثلاث تجارب محفوظة:
git log --oneline -4c072372 تجربة ثالثةae728a9 تجربة ثانية433e982 تجربة أولى53be69f Revert "إضافة شريط متحرك"HEAD~1 تعني «commit واحد قبل الأخير». وللأمر ثلاثة أوضاع تختلف في مصير التغييرات التي كانت في الـ commits الملغاة:
| الأمر | الـ commit | التغييرات |
|---|---|---|
git reset --soft HEAD~1 |
يُلغى | تبقى في منطقة التجهيز، جاهزة لـ commit جديد |
git reset HEAD~1 |
يُلغى | تبقى في الملفات، خارج منطقة التجهيز |
git reset --hard HEAD~1 |
يُلغى | تُحذف من الملفات أيضاً |
git reset --soft HEAD~1git status --shortA c.txtالـ commit الثالث اختفى من التاريخ، لكن ملفه ما زال مجهزاً. مفيد حين تريد إعادة الحفظ بشكل أفضل. أما الوضع العنيف:
git reset --hard HEAD~3HEAD is now at 53be69f Revert "إضافة شريط متحرك"اختفت التجارب الثلاث من التاريخ ومن المجلد.
شبكة الأمان: git reflog
Section titled “شبكة الأمان: git reflog”حذفت ثلاثة commits بـ reset --hard ثم ندمت؟ Git يحتفظ بسجل لكل مكان وقف عنده HEAD:
git reflog53be69f HEAD@{0}: reset: moving to HEAD~38aa8fdf HEAD@{1}: commit: تجربة ثالثةae728a9 HEAD@{2}: reset: moving to HEAD~1c072372 HEAD@{3}: commit: تجربة ثالثةae728a9 HEAD@{4}: reset: moving to HEAD~1كل حركة مسجلة برقم الـ commit. للعودة إلى ما قبل الحذف:
git reset --hard 8aa8fdfgit log --oneline -48aa8fdf تجربة ثالثةae728a9 تجربة ثانية433e982 تجربة أولى53be69f Revert "إضافة شريط متحرك"عادت التجارب الثلاث! الـ commits «المحذوفة» تبقى في المستودع نحو شهر قبل أن ينظفها Git. الدرس: ما حُفظ في commit يصعب جداً أن يضيع. ما يضيع فعلاً هو ما لم تحفظه.
أي أداة لأي موقف؟
Section titled “أي أداة لأي موقف؟”| الموقف | الأداة |
|---|---|
| عدّلت ملفاً وأريد نسخته المحفوظة | git restore file |
| أضفت ملفاً للتجهيز بالخطأ | git restore --staged file |
| خطأ في رسالة آخر commit أو ملف منسي | git commit --amend |
| commit قديم أو منشور أريد عكسه | git revert |
| commits محلية أريد إلغاءها | git reset |
| أضعت commits بعد reset | git reflog |
جرّب بنفسك
Section titled “جرّب بنفسك”- عدّل ملفاً في مستودع تجريبي، وشاهد الفرق بـ
git diff، ثم ألغه بـgit restore. - احفظ commit برسالة خاطئة ثم صححها بـ
--amend. - احفظ commit يضيف سطراً لملف، ثم اعكسه بـ
git revertوتحقق من محتوى الملف. - تحدٍّ: احفظ commitين، وألغهما بـ
git reset --hard HEAD~2، ثم استعدهما باستخدامgit reflog.
أخطاء شائعة
Section titled “أخطاء شائعة”reset --hardمع تعديلات غير محفوظة: تضيع نهائياً ولا ينقذهاreflog. احفظ أو استخدمgit stashأولاً (موضوع الدرس القادم).- amend بعد الرفع: يتغير رقم الـ commit فيرفض GitHub الرفع التالي. إذا رفعت فأضف commit جديداً بالتصحيح.
- الخلط بين revert و reset:
revertيضيف commit عاكساً، وresetيمحو من التاريخ. - الذعر: قبل أي أمر خطِر انظر
git statusوgit log --oneline، وتذكر أنreflogموجود.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
رفعت commit على GitHub ثم اكتشفت أنه خاطئ، وزملاؤك سحبوه. ما الطريقة الآمنة للتراجع؟
revert يضيف commit جديداً يعكس التغيير دون تعديل التاريخ المشترك مع الزملاء.
سؤال
ما مصير التغييرات بعد git reset --soft HEAD~1؟
الوضع soft يلغي الـ commit فقط، ويترك تغييراته مجهزة لحفظ جديد.
الخلاصة
Section titled “الخلاصة”git restoreيلغي تعديلاً غير محفوظ، و--stagedتُخرج ملفاً من التجهيز.git commit --amendيصحح آخر commit: رسالته أو ملفاته.git revertيعكس commit بإضافة commit جديد، وهو الآمن مع ما نُشر.git resetيرجع بالفرع:--softو الافتراضي و--hardتختلف في مصير التغييرات.git reflogيسجل كل الحركات ويستعيد ما ظننته ضاع.