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

🔀 شرح Git

التراجع وإصلاح الأخطاء

الدرس 14 من 28· ⏱ 5 دقائق قراءة· 🗓 آخر تحديث: ١٨ أكتوبر ٢٠٢٦

ابدأ من سؤال: أين الخطأ؟

أوامر التراجع في Git كثيرة لأن "التراجع" يعني أشياء مختلفة حسب المكان الذي وصل إليه الخطأ. حدّد موقفك من الجدول، ثم انتقل لقسمه:

الموقفالأمر
عدّلت ملفًا ولم أجهّزه، وأريد إلغاء التعديلgit restore file
جهّزته بـ add بالخطأgit restore --staged file
آخر commit: رسالته خاطئة أو نسيت فيه ملفًاgit commit --amend
أريد إلغاء commits لم أرفعهاgit reset
أريد إلغاء commit رفعته للفريقgit revert
أريد نسخة قديمة من ملف واحدgit restore --source=<commit> file
مسحت شيئًا بـ reset --hardgit reflog

تعديلات لم تُحفظ: restore

git restore app.js              # أعد الملف كما في آخر commit
git restore .                   # كل الملفات المعدّلة

⚠️ هذا الأمر يمحو التعديلات نهائيًّا: لم تُحفظ في commit ولا في stash، فلا شيء في Git يستطيع إعادتها. إن كنت مترددًا، git stash بدلًا منه (درس stash).

إلغاء التجهيز

git add .
git restore --staged .env       # أخرجه من منطقة التجهيز

--staged لا تلمس الملف نفسه، بل تلغي git add فقط: التعديل باقٍ في مجلّد العمل.

ملفات جديدة غير متتبَّعة

restore لا يعرف الملفات التي لم يتتبّعها Git أصلًا. لحذف الملفات الجديدة (مخرجات بناء، ملفات تجريبية):

git clean -n        # اعرض ما سيُحذف فقط
git clean -f        # احذف فعلًا

ابدأ بـ -n دائمًا: clean -f يحذف من القرص ملفات لا نسخة لها في أي مكان.

آخر commit: amend

git add forgotten.txt
git commit --amend --no-edit            # أضف الملف لآخر commit دون تغيير الرسالة
git commit --amend -m "Fix login redirect"   # غيّر الرسالة

--amend لا "يعدّل" الـ commit؛ يستبدله بـ commit جديد برقم جديد. لذلك ينطبق عليه ما ينطبق على reset وrebase: آمن قبل الرفع، ويحتاج --force-with-lease بعده، وممنوع على تاريخ مشترك.

إلغاء commits محلية: reset

reset يعيد مؤشّر الفرع إلى commit سابق، فتختفي الـ commits بعده من الفرع. الفرق بين أنماطه الثلاثة هو ماذا يحدث لتغييرات تلك الـ commits:

git reset --soft HEAD~1     # ألغِ آخر commit، والتغييرات تبقى مجهّزة
git reset HEAD~1            # (--mixed الافتراضي) التغييرات تبقى غير مجهّزة
git reset --hard HEAD~1     # ألغِ الـ commit وتغييراته كليًّا
النمطمؤشّر الفرعمنطقة التجهيزملفاتكالاستخدام
--softيرجعكما هي (مجهّزة)كما هيأعد كتابة الـ commit برسالة أو تقسيم أفضل
--mixedيرجعتُفرَّغكما هيأعد التفكير فيما يدخل كل commit
--hardيرجعتُفرَّغتعود للقديمتخلَّ عن العمل تمامًا

HEAD~1 تعني "الـ commit الذي قبل الحالي"، وHEAD~3 ثلاثة للخلف. ويمكنك كتابة رقم commit مباشرة: git reset --hard a8b6e0e.

--soft و--mixed آمنان على عملك: لا يضيع شيء، فقط يُعاد تنظيمه. --hard هو الوحيد الذي يمحو، ويمحو معه أي تعديلات غير محفوظة كانت في مجلّد العمل، وتلك لا تُستعاد أبدًا.

إلغاء commit مرفوع: revert

إن كان الـ commit في main على GitHub وسحبه زملاؤك، فحذفه بـ reset يعني أن تاريخك صار مختلفًا عن تاريخهم، ويتطلّب رفعًا بالقوّة يكسر نسخهم. revert لا يحذف شيئًا، بل يضيف commit جديدًا يعكس التغيير:

git revert 76dcdc3
[main 5b33972] Revert "Break app"
5b33972 Revert "Break app"     ← جديد: يعيد السطور كما كانت
76dcdc3 Break app              ← ما زال في التاريخ
de4c8f2 Update app

التاريخ يقول الحقيقة: "أضفنا هذا، ثم تراجعنا عنه"، ويُرفع بـ git push عادي. بلا --no-edit، يفتح Git المحرّر لتكتب لماذا تراجعت، وهي معلومة تستحق التسجيل.

التراجع عن commit دمج

git revert <merge-hash>
error: commit 4541c92... is a merge but no -m option was given.

commit الدمج له أبوان، وGit يسأل: إلى أي خطّ أعود؟ -m 1 تعني "الأب الأول"، أي الفرع الذي دُمج فيه (main غالبًا)، فيُلغى ما جاء من الفرع المدموج:

git revert -m 1 <merge-hash>

نسخة قديمة من ملف واحد

git restore --source=HEAD~2 app.js        # app.js كما كان قبل commitين
git restore --source=v1.0.0 config.json   # كما كان في إصدار

يغيّر الملف في مجلّد العمل فقط، فتراجعه ثم تعمل commit إن أعجبك. بقية المشروع لا يتأثّر.

الخلاص الأخير: reflog

نفّذت reset --hard وأدركت أنك تحتاج الـ commit؟ ما دام قد حُفظ في commit يومًا، فهو غالبًا ما زال موجودًا:

git reflog
de4c8f2 HEAD@{0}: reset: moving to HEAD~1
76dcdc3 HEAD@{1}: commit: Break app
git reset --hard HEAD@{1}     # عُد إلى ما قبل reset

reflog سجلّ محلي لكل مكان وقف فيه HEAD. التفاصيل ومدّة الاحتفاظ في درس reflog.

reset أم revert؟

  • لم ترفع بعد: reset (أو --amend لآخر commit). التاريخ ما زال ملكك وحدك.
  • رفعت إلى فرع يستخدمه غيرك: revert دائمًا.
  • رفعت إلى فرع PR شخصي لا يبني عليه أحد: reset ثم push --force-with-lease مقبول.

أخطاء شائعة

  • reset --hard لإلغاء commit مع وجود تعديلات غير محفوظة: تضيع التعديلات معه. افحص git status أولًا، أو استخدم --soft.
  • reset على commit مرفوع ثم push --force على main: يكسر نسخ الفريق. استخدم revert.
  • git clean -f بلا -n قبلها.
  • --amend بعد الرفع وانتظار أن يعمل git push العادي: يُرفض، لأن الـ commit استُبدل.
  • git checkout file القديمة في السكربتات: تعمل، لكنها تمحو التعديلات بلا تحذير مثل restore. restore أوضح في النيّة.

🎯 التالي: انتقاء الـ commit (Cherry-pick).

شرح التراجع وإصلاح الأخطاء — Git بالعربي
التراجع وإصلاح الأخطاء — Git بالعربي · The Code Fix

📚 لمزيد من التعمّق في Git، راجِع التوثيق الرسمي لـ Git.

هل كان هذا الدرس مفيدًا؟