تخطَّ إلى المحتوى

التراجع عن الأخطاء في Git: restore و revert و reset

الدرس 7 من 10 · Git و GitHub

عدّلت ملفاً فأفسدته، أو كتبت رسالة commit فيها خطأ إملائي، أو حفظت شيئاً ما كان يجب أن يُحفظ. كل مبرمج يخطئ، وميزة Git الكبرى أن كل شيء تقريباً قابل للتراجع. المهم أن تختار الأداة المناسبة لكل موقف. في هذا الدرس ستتعلم خمس أدوات، من الأبسط إلى الأقوى.

تخيّل Git مثل آلة زمن لمشروعك: تستطيع العودة لأي لحظة محفوظة. لكن لبعض الرحلات قواعد أمان يجب معرفتها.

إلغاء تعديل لم يُحفظ: git restore

Section titled “إلغاء تعديل لم يُحفظ: git restore”

عدّلت index.html وندمت:

نافذة طرفيّة
git status --short
الناتج
M index.html
نافذة طرفيّة
git restore index.html
git status --short

الأمر الثاني لا يطبع شيئاً: الملف عاد كما كان في آخر commit، والمجلد نظيف.

إخراج ملف من منطقة التجهيز

Section titled “إخراج ملف من منطقة التجهيز”

نفّذت git add . فدخل ملف لا تريده في الـ commit:

نافذة طرفيّة
git status --short
الناتج
A about.html
A plan.txt
نافذة طرفيّة
git restore --staged plan.txt
git status --short
الناتج
A about.html
?? plan.txt

--staged تُخرج الملف من منطقة التجهيز دون المساس بمحتواه. عاد plan.txt غير متتبَّع، وبقي about.html جاهزاً للحفظ.

حفظت برسالة فيها خطأ إملائي:

نافذة طرفيّة
git commit -m "اضافه صفحه"
git commit --amend -m "إضافة صفحة من نحن"
git log --oneline
الناتج
410bcff إضافة صفحة من نحن
b655445 إيقاف تتبع config.json
b0e20db إضافة .gitignore وملفات المشروع
66447fb إضافة الصفحة الرئيسية وملف README

--amend تستبدل آخر commit بنسخة مصححة: لا commit إضافياً في التاريخ. وتصلح أيضاً لإضافة ملف نسيته:

نافذة طرفيّة
git add about.css
git commit --amend --no-edit

--no-edit تعني «أبقِ الرسالة كما هي». الآن آخر commit يحتوي الملفين معاً. ولاحظ أن رقم الـ commit تغيّر بعد كل تعديل، لأنه في الحقيقة commit جديد حلّ محل القديم.

أضفت شريطاً متحركاً للصفحة وحفظته، ثم تبيّن أنه فكرة سيئة:

نافذة طرفيّة
git log --oneline -3
الناتج
57477b1 إضافة شريط متحرك
7683b31 إضافة صفحة من نحن
b655445 إيقاف تتبع config.json
نافذة طرفيّة
git revert --no-edit HEAD
git log --oneline -3
الناتج
53be69f Revert "إضافة شريط متحرك"
57477b1 إضافة شريط متحرك
7683b31 إضافة صفحة من نحن

revert لا يحذف شيئاً: ينشئ commit جديداً يعكس تغييرات الـ commit المحدد. الشريط اختفى من الملف، والتاريخ بقي كاملاً وصادقاً: «أضفنا ثم تراجعنا». و HEAD تعني آخر commit، ويمكن كتابة رقم أي commit مكانها.

هذه الطريقة الآمنة للتراجع عن شيء رفعته على GitHub، لأنها تضيف للتاريخ ولا تغيّره.

reset يحرّك الفرع إلى commit أقدم، كأن ما بعده لم يحدث. عندنا ثلاث تجارب محفوظة:

نافذة طرفيّة
git log --oneline -4
الناتج
c072372 تجربة ثالثة
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~1
git status --short
الناتج
A c.txt

الـ commit الثالث اختفى من التاريخ، لكن ملفه ما زال مجهزاً. مفيد حين تريد إعادة الحفظ بشكل أفضل. أما الوضع العنيف:

نافذة طرفيّة
git reset --hard HEAD~3
الناتج
HEAD is now at 53be69f Revert "إضافة شريط متحرك"

اختفت التجارب الثلاث من التاريخ ومن المجلد.

حذفت ثلاثة commits بـ reset --hard ثم ندمت؟ Git يحتفظ بسجل لكل مكان وقف عنده HEAD:

نافذة طرفيّة
git reflog
الناتج
53be69f HEAD@{0}: reset: moving to HEAD~3
8aa8fdf HEAD@{1}: commit: تجربة ثالثة
ae728a9 HEAD@{2}: reset: moving to HEAD~1
c072372 HEAD@{3}: commit: تجربة ثالثة
ae728a9 HEAD@{4}: reset: moving to HEAD~1

كل حركة مسجلة برقم الـ commit. للعودة إلى ما قبل الحذف:

نافذة طرفيّة
git reset --hard 8aa8fdf
git log --oneline -4
الناتج
8aa8fdf تجربة ثالثة
ae728a9 تجربة ثانية
433e982 تجربة أولى
53be69f Revert "إضافة شريط متحرك"

عادت التجارب الثلاث! الـ commits «المحذوفة» تبقى في المستودع نحو شهر قبل أن ينظفها Git. الدرس: ما حُفظ في commit يصعب جداً أن يضيع. ما يضيع فعلاً هو ما لم تحفظه.

الموقف الأداة
عدّلت ملفاً وأريد نسخته المحفوظة git restore file
أضفت ملفاً للتجهيز بالخطأ git restore --staged file
خطأ في رسالة آخر commit أو ملف منسي git commit --amend
commit قديم أو منشور أريد عكسه git revert
commits محلية أريد إلغاءها git reset
أضعت commits بعد reset git reflog
  1. عدّل ملفاً في مستودع تجريبي، وشاهد الفرق بـ git diff، ثم ألغه بـ git restore.
  2. احفظ commit برسالة خاطئة ثم صححها بـ --amend.
  3. احفظ commit يضيف سطراً لملف، ثم اعكسه بـ git revert وتحقق من محتوى الملف.
  4. تحدٍّ: احفظ commitين، وألغهما بـ git reset --hard HEAD~2، ثم استعدهما باستخدام git reflog.
  • reset --hard مع تعديلات غير محفوظة: تضيع نهائياً ولا ينقذها reflog. احفظ أو استخدم git stash أولاً (موضوع الدرس القادم).
  • amend بعد الرفع: يتغير رقم الـ commit فيرفض GitHub الرفع التالي. إذا رفعت فأضف commit جديداً بالتصحيح.
  • الخلط بين revert و reset: revert يضيف commit عاكساً، و reset يمحو من التاريخ.
  • الذعر: قبل أي أمر خطِر انظر git status و git log --oneline، وتذكر أن reflog موجود.

سؤال

رفعت commit على GitHub ثم اكتشفت أنه خاطئ، وزملاؤك سحبوه. ما الطريقة الآمنة للتراجع؟

سؤال

ما مصير التغييرات بعد git reset --soft HEAD~1؟

  • git restore يلغي تعديلاً غير محفوظ، و --staged تُخرج ملفاً من التجهيز.
  • git commit --amend يصحح آخر commit: رسالته أو ملفاته.
  • git revert يعكس commit بإضافة commit جديد، وهو الآمن مع ما نُشر.
  • git reset يرجع بالفرع: --soft و الافتراضي و --hard تختلف في مصير التغييرات.
  • git reflog يسجل كل الحركات ويستعيد ما ظننته ضاع.