الخلاصة: git revert يضيف commit جديدًا يلغي قديمًا — التاريخ محفوظ، وآمن على الفروع التي سحبها آخرون. أما git reset فيحرّك مؤشر الفرع إلى الخلف — التاريخ يعاد كتابته، ولا يكون آمنًا إلا على commits لم تدفعها. استخدم revert على الفروع المشتركة، وreset على التنظيف المحلي. وreset --hard يحذف أيضًا تغييرات شجرة العمل غير المرفوعة؛ فهو آكل العمل الحقيقي.
ما الفرق بين git revert وgit reset؟
كلاهما يجعل مشروعك يبدو وكأن commitًا ماضيًا لم يحدث. ويتخالفا حول الكيفية:
git revert <sha>يحسب الرقعة المعاكسة لـ commit ويُدرجها. يكبر تاريخك بـ commit يقول “ألغِ ذاك”. يبقى الـ commit السيئ في السجل يتبعه إلغاؤه. عناوين SHA لكل ما عداه لا تُلمس.git reset <sha>يحرّك مؤشر الفرع الحالي إلى<sha>. تنفصل الـ commits اللاحقة — ما تزال في قاعدة الكائنات حتى جمع النفايات، لكنها لم تعد قابلة للوصول من أي فرع أوgit log.
أحدهما لا يعيد كتابة شيئًا، والآخر يتظاهر أن شيئًا لم يحدث. تلك الخاصية وحدها تقرر أيهما تستدعي كل حالة:
# تجربة: مستودع مؤقت قابل للتشغيل
git init /tmp/revert-vs-reset && cd /tmp/revert-vs-reset
echo one > file.txt && git add . && git commit -m "one"
echo two > file.txt && git add . && git commit -m "two"
# revert: التاريخ يحفظ الـ commitين، مع ثالث يلغي "two"
git revert --no-edit HEAD
git log --oneline # 3 commits: revert, two, one
# reset: مؤشر الفرع يتحرك للخلف، "two" يختفي من السجل
git reset --hard HEAD~1
git log --oneline # 1 commit: one شغّل الاثنين وافحص git log — اللامتناظرة هي الجواب كله.
ماذا يفعل reset بـ —soft و—mixed و—hard فعلًا؟
تتحكم الأنماط الثلاثة في أين تنجو تغييراتك بعد تحرك المؤشر:
| النمط | مؤشر الفرع | منطقة التجهيز | شجرة العمل | تعديلاتك |
|---|---|---|---|---|
--soft | يتراجع | تبقى مجهزة | سليمة | محفوظة كاملة |
--mixed (الافتراضي) | يتراجع | غير مجهزة | سليمة | محفوظة، غير مجهزة |
--hard | يتراجع | يُصفّر | يُصفّر | محذوفة |
قراءات ملموسة للجدول:
git reset --soft HEAD~1— “رفعت الـ commit مبكرًا.” يعود كل شيء مجهزًا جاهزًا لإعادة الالتزام، ربما مع عمل إضافي.git reset HEAD~1(mixed) — “جهزت الملفات الخطأ.” تنجو التعديلات في شجرة العمل بلا تجهيز. واربطها بـ حذف الملفات غير المتتبعة حين تريد زوال الملفات الطائشة أيضًا.git reset --hard HEAD~1— “هذا الـ commit وتعديلاتي غير المرفوعة قمامة.” لن يسألك git مرتين؛ ولا تراجع للجزء الخاص بشجرة العمل إلا إذا خبأته بـ stash أو التزمته أولًا.
ولا أنماط عند revert لأنه لا يدمر شيئًا قط — يضيف commitًا تعويضيًا فحسب.
متى تستخدم git revert بدل git reset؟
اسأل سؤالًا واحدًا: هل سحب أحد آخر هذا الـ commit؟ إذا كان نعم، فالـ reset خارج النقاش.
إعادة ضبط فرع بنى الآخرون عملهم عليه تعيد كتابة التاريخ المشترك. يصطدم سحبهم التالي بفروع متباعدة، و”الإصلاح” عادة force-push يحوّل خطأك إلى مشكلة الجميع. يلحق revert بـ commit عادي، فيدمج git pull المجرد بسلامة للجميع — ولهذا يبقى revert الجواب الافتراضي على main وفي طلبات سحب GitHub، حيث زر “Revert” موجود بالضبط لأن إعادة كتابة تاريخ PR مدمج ليست خيارًا.
والـ reset للنافذة قبل المشاركة: الـ commit الذي أجريته قبل ثلاثين ثانية، خطأ التجهيز، فرع التجربة المحلي. إذا كانت النسخة الوحيدة من العمل على جهازك، فأنت حر في إعادة ترتيب التاريخ — تلك هي كل نقطة نافذة ما قبل الدفع.
وهناك حالة وسطى: دفعت إلى فرع ميزاتك الخاص ولا يبني عليه أحد. الدفع القسري بعد reset مقبول اجتماعيًا هناك، لكن revert ما يزال يكلف تنسيقًا أقل. احفظ القوى القسرية للفروع التي تملكها وحدك.
هل git revert أكثر أمانًا من git reset للفروع المشتركة؟
نعم، بنيويًا — لا بالاصطلاح فحسب:
- ينتج revert commitًا عاديًا؛ يجري CI عليه، والفرَق قابل للمراجعة، ويشرح
git logلنسختك المستقبلية لماذا اختفى التغيير. - يتجاهل reset الحالة الوسيطة بصمت. لن يرى أي مراجع لاحق ما أُلغي ولا متى، لأن السجل يعرض خطًا مستقيمًا لم يتضمنه قط.
حرفان حادان عمليان:
- إلغاء commit دمج يحتاج
git revert -m 1 <sha>(إبقاء خط الأب الأول). وبدون-mيرفض git ويرميك لقراءة الخطأ. - وrevert ليس آلة زمن: يلغي فرق commit واحد. وإذا لمست commits لاحقة الأسطر نفسها فقد تحل تعارضات — ذلك git يخبرك أن الإلغاء متشابك، وهو معلومة مفيدة لا خلل.
وللتراجع عن آخر commit لديك تحديدًا — مع إبقاء تغييراته أو إسقاطها — الخيارات مرصودة في التراجع عن آخر commit مع الاحتفاظ بالتغييرات.
وماذا عن git restore — أين موضعه؟
وصل git restore (وشريكه git switch) في git 2.23 ليستولي على شغلانات كان reset يؤديها رديئًا بسبب تكدس الأسماء:
| المهمة | الطريقة القديمة | الطريقة الواضحة |
|---|---|---|
| إسقاط تعديلات شجرة العمل في ملف | git checkout -- file / git reset --hard | git restore file |
| إزالة ملف من التجهيز | git reset HEAD file | git restore --staged file |
| تحريك فرع إلى commit آخر | git reset <sha> | git reset <sha> (لا بديل — هذا يبقى) |
فالانقسام الحديث: restore يصلح الملفات، وreset يحرك الفروع، وrevert يلغي الـ commits المنشورة. ويظهر أن الناس يبحثون عن “git revert vs reset vs restore” معًا لسبب وجيه — إنها نموذج ذهني واحد موزع على ثلاثة أوامر. صيغ git checkout القديمة ما تزال تعمل في كل مكان؛ والجديدة تحميك فقط من إرسال فرع كامل إلى المفرمة حين كنت تقصد إزالة ملف واحد من التجهيز.
وإذا كان هدفك نسخ commit جيد إلى الأمام لا محو commit سيئ، فتلك شغلانة cherry-pick — انظر Git cherry-pick: عدة commits وفروع وتعارضات.
ملخص القرار السريع
- الـ commit عام (مرفوع، وسحبه آخرون) →
git revert <sha>. - الـ commit محلي فقط وتريد إعادته →
git reset --softأو--mixedثم أعد الالتزام. - محلي فقط، وتريده يزول مع الفوضى غير المرفوعة →
git reset --hard، بعد نظرة صادقة واحدة لما تحمله شجرة العمل غيره. - تلخ ملف واحد →
git restore <file>واترك الفرع وشأنه.
الإدخال الخطير حقًا في تلك القائمة هو --hard — وكل ما عداه يُحاور بالعودة عبر git reflog. الخسائر الدائمة في git ضيقة وغالبًا ما تتطلب منك كتابتها صراحة؛ والعادة المستحقة هي توقف نصف ثانية قبل --hard، لا تفادي أدوات git الحادة كليًا.
— mrsaynothing
— mrsaynothing
ملاحظات ميدانية في الذكاء الاصطناعي وLinux والاستضافة الذاتية.
ناقش هذا المقال على dev.to dev.to ↗
احصل على الشرح التطبيقي التالي بالبريد
رسالة واحدة لكل مقال. أصلح المشكلة وامضِ.
ما هذا؟خدمة systemd لا تبدأ؟ هكذا تُصلحها
أعجبتك هذه الكتابات؟ بناء مثل هذا هو عملي. وظّفني