Git Pull Force: كيفية تجاوز التغييرات المحلية باستخدام Git

دقائق القراءة: 7
عندما تبدأ في تعلم البرمجة، ستتعرف عاجلاً أم آجلاً على أنظمة التحكم في الإصدارات (Version Control Systems). وعلى الرغم من وجود العديد من الأدوات المتنافسة في هذا المجال، إلا أن إحداها تُعد المعيار الفعلي الذي يستخدمه الجميع تقريباً في الصناعة. إنها شائعة لدرجة أن هناك شركات تستخدم اسمها في علاماتها التجارية. نحن نتحدث بالطبع عن Git. في حين أن Git أداة قوية، إلا أن قوتها مخفية جيداً. هناك بعض المفاهيم الأساسية التي تحتاج إلى فهمها لتصبح بارعاً حقاً في استخدام Git. والخبر السار هو أنه بمجرد إتقانها، نادراً ما ستواجه مشكلة لا يمكنك حلها.

فهم سير العمل النموذجي في Git

في سير عمل Git النموذجي، ستستخدم مستودعاً محلياً (local repository)، ومستودعاً بعيداً (remote repository)، وفرعاً واحداً أو أكثر (branches). تخزن المستودعات جميع المعلومات حول المشروع، بما في ذلك تاريخه الكامل وجميع الفروع. الفرع هو في الأساس مجموعة من التغييرات التي تقود المشروع من حالته الفارغة إلى حالته الحالية. بعد استنساخ مستودع (cloning a repository)، تعمل على نسختك المحلية وتُدخل تغييرات جديدة. حتى تقوم بدفع التغييرات المحلية (push local changes) إلى المستودع البعيد، يظل كل عملك متاحاً فقط على جهازك. عندما تنهي مهمة ما، يحين وقت المزامنة مع المستودع البعيد. سترغب في سحب التغييرات البعيدة (pull the remote changes) لمواكبة تقدم المشروع، وسترغب في دفع التغييرات المحلية لمشاركة عملك مع الآخرين.

التغييرات المحلية وتعارضاتها المحتملة

كل شيء يسير على ما يرام عندما تعمل أنت وبقية فريقك على ملفات منفصلة تماماً. مهما حدث، لن تتعارضوا مع بعضكم البعض. ومع ذلك، هناك أوقات تقوم فيها أنت وزملاؤك بإدخال تغييرات في نفس المكان في وقت واحد. وهذا عادة ما يكون بداية المشاكل. هل سبق لك أن نفذت الأمر git pull لتظهر لك رسالة الخطأ المخيفة: error: Your local changes to the following files would be overwritten by merge:؟ عاجلاً أم آجلاً، يواجه الجميع هذه المشكلة. الأمر الأكثر إرباكاً هنا هو أنك لا تريد دمج أي شيء، فقط سحب التغييرات، أليس كذلك؟ في الواقع، عملية pull أكثر تعقيداً مما قد تتصور.

كيف يعمل أمر Git Pull بالضبط؟

عملية pull ليست عملية واحدة. إنها تتكون من جلب البيانات (fetching data) من الخادم البعيد ثم دمج التغييرات (merging the changes) مع المستودع المحلي. يمكن تنفيذ هاتين العمليتين يدوياً إذا أردت:

git fetch
git merge origin/ $CURRENT_BRANCH

الجزء origin/$CURRENT_BRANCH يعني أن Git سيقوم بدمج التغييرات من المستودع البعيد المسمى origin (الذي استنسخته منه) والتي تمت إضافتها إلى الفرع $CURRENT_BRANCH وليست موجودة بالفعل في فرعك المحلي الذي تعمل عليه. بما أن Git لا يقوم بعمليات الدمج إلا عندما لا تكون هناك تغييرات غير ملتزم بها (uncommitted changes)، فإن كل مرة تقوم فيها بتشغيل git pull مع تغييرات غير ملتزم بها قد توقعك في مشكلة. لحسن الحظ، هناك طرق للخروج من هذه المشاكل بسلام!

صورة توضيحية لتعارضات Git وكيفية حلها

استراتيجيات مختلفة للتعامل مع التغييرات المحلية

عندما تكون لديك تغييرات محلية غير ملتزم بها (uncommitted local changes) وما زلت ترغب في سحب إصدار جديد من الخادم البعيد، فإن حالتك عادة ما تندرج تحت أحد السيناريوهات التالية:

  • لا تهتم بالتغييرات المحلية وتريد تجاوزها.
  • تهتم بالتغييرات كثيراً وترغب في تطبيقها بعد التغييرات البعيدة.
  • تريد تنزيل التعديلات البعيدة ولكن لا ترغب في تطبيقها بعد.

كل من هذه الأساليب يتطلب حلاً مختلفاً.

السيناريو الأول: تجاهل التغييرات المحلية وتجاوزها

في هذه الحالة، كل ما تريده هو التخلص من جميع التغييرات المحلية غير الملتزم بها. ربما قمت بتعديل ملف للتجربة، ولكنك لم تعد بحاجة إلى هذا التعديل. كل ما يهمك هو أن تكون محدثاً مع الفرع الأصلي (upstream). هذا يعني أنك ستضيف خطوة واحدة إضافية بين جلب التغييرات البعيدة ودمجها. ستقوم هذه الخطوة بإعادة ضبط الفرع إلى حالته غير المعدلة، مما يسمح لـ git merge بالعمل.

git fetch
git reset --hard HEAD
git merge origin/ $CURRENT_BRANCH

إذا كنت لا ترغب في كتابة اسم الفرع في كل مرة تقوم فيها بتشغيل هذا الأمر، فإن Git يوفر اختصاراً لطيفاً يشير إلى الفرع الأصلي (upstream branch): @{u}. الفرع الأصلي هو الفرع في المستودع البعيد الذي تقوم بالدفع إليه (push to) والجلب منه (fetch from). هكذا ستبدو الأوامر المذكورة أعلاه مع الاختصار:

git fetch
git reset --hard HEAD
git merge '@{u}'

نقوم بوضع الاختصار بين علامتي اقتباس في المثال لمنع الـ shell من تفسيره.

السيناريو الثاني: الحفاظ على التغييرات المحلية وتطبيقها لاحقاً

عندما تكون تغييراتك غير الملتزم بها ذات أهمية كبيرة بالنسبة لك، هناك خياران. يمكنك الالتزام بها (commit them) ثم تنفيذ git pull، أو يمكنك تخزينها مؤقتاً (stash them). التخزين المؤقت يعني وضع التغييرات جانباً للحظة لإعادتها لاحقاً. بتعبير أدق، ينشئ الأمر git stash التزاماً (commit) غير مرئي على فرعك الحالي، ولكنه لا يزال قابلاً للوصول بواسطة Git. لإعادة التغييرات المحفوظة في آخر تخزين مؤقت، تستخدم الأمر git stash pop. بعد تطبيق التغييرات المخزنة مؤقتاً بنجاح، يزيل هذا الأمر أيضاً التزام التخزين المؤقت لأنه لم يعد ضرورياً. يمكن أن يبدو سير العمل حينها كالتالي:

git fetch
git stash
git merge '@{u}'
git stash pop

بشكل افتراضي، ستصبح التغييرات من التخزين المؤقت في منطقة التجهيز (staged). إذا كنت ترغب في إزالتها من منطقة التجهيز (unstage them)، استخدم الأمر git restore --staged (إذا كنت تستخدم إصدار Git أحدث من 2.25.0).

السيناريو الثالث: تنزيل التغييرات البعيدة فقط دون تطبيقها

السيناريو الأخير مختلف قليلاً عن سابقيه. لنفترض أنك في منتصف عملية إعادة هيكلة (refactoring) فوضوية للغاية. لا يعد فقدان التغييرات ولا تخزينها مؤقتاً خياراً. ومع ذلك، ما زلت ترغب في أن تكون التغييرات البعيدة متاحة لتشغيل git diff ضدها. كما ربما تكون قد أدركت، فإن تنزيل التغييرات البعيدة لا يتطلب git pull على الإطلاق! git fetch يكفي تماماً. شيء واحد يجب ملاحظته هو أنه بشكل افتراضي، سيجلب لك git fetch التغييرات من الفرع الحالي فقط. للحصول على جميع التغييرات من جميع الفروع، استخدم git fetch --all. وإذا كنت ترغب في تنظيف بعض الفروع التي لم تعد موجودة في المستودع البعيد، فإن git fetch --all --prune سيقوم بالمهمة!

صورة توضيحية لعملية Git Fetch وجلب التغييرات

أتمتة أوامر Git باستخدام الـ Aliases

هل سمعت عن ملف Git Config؟ إنه ملف يخزن فيه Git جميع إعدادات المستخدم. يوجد في دليلك الرئيسي (home directory): إما كـ ~/.gitconfig أو ~/.config/git/config. يمكنك تعديله لإضافة بعض الأسماء المستعارة المخصصة (custom aliases) التي ستُفهم كأوامر Git. على سبيل المثال، للحصول على اختصار يعادل git diff --cached (الذي يظهر الفرق بين الفرع الحالي والملفات في منطقة التجهيز)، ستضيف القسم التالي:

[alias]
dc = diff --cached

بعد ذلك، يمكنك تشغيل git dc كلما أردت مراجعة التغييرات. وبهذه الطريقة، يمكننا إعداد بعض الأسماء المستعارة المتعلقة بحالات الاستخدام السابقة.

[alias]
pull_force = ! "git fetch --all; git reset --hard HEAD; git merge @{u}"
pf = pull_force
pull_stash = ! "git fetch --all; git stash; git merge @{u}; git stash pop"

بهذه الطريقة، سيؤدي تشغيل git pull_force إلى تجاوز التغييرات المحلية، بينما سيحافظ git pull_stash عليها.

فهم الفرق: أمر Git Pull –force

قد تكون العقول الفضولية قد اكتشفت بالفعل أن هناك أمراً يسمى git pull --force. ومع ذلك، هذا أمر مختلف تماماً عما تم تقديمه في هذه المقالة. قد يبدو وكأنه شيء سيساعدنا في تجاوز التغييرات المحلية. بدلاً من ذلك، فإنه يسمح لنا بجلب التغييرات من فرع بعيد إلى فرع محلي مختلف. يقوم git pull --force بتعديل سلوك جزء الجلب (fetching part) فقط. لذلك فهو يعادل git fetch --force. مثل git push، يسمح لنا git fetch بتحديد الفرع المحلي والبعيد الذي نريد العمل عليه. الأمر git fetch origin/feature-1:my-feature سيعني أن التغييرات في الفرع feature-1 من المستودع البعيد ستظهر على الفرع المحلي my-feature. عندما تقوم مثل هذه العملية بتعديل التاريخ الحالي، فإن Git لا يسمح بها بدون معلمة --force صريحة. تماماً كما يسمح git push --force بتجاوز الفروع البعيدة، يسمح git fetch --force (أو git pull --force) بتجاوز الفروع المحلية. يتم استخدامه دائماً مع فروع المصدر والوجهة المذكورة كمعلمات. قد يكون هناك نهج بديل لتجاوز التغييرات المحلية باستخدام git --pull force وهو git pull --force "@{u}:HEAD".

الخلاصة التقنية

عالم Git واسع ومتشعب. لقد غطت هذه المقالة جانباً واحداً فقط من جوانب صيانة المستودعات: دمج التغييرات البعيدة في مستودع محلي. حتى هذا السيناريو اليومي تطلب منا التعمق قليلاً في الآليات الداخلية لأداة التحكم في الإصدارات هذه. يساعد تعلم حالات الاستخدام الفعلية على فهم أفضل لكيفية عمل Git تحت الغطاء، وهذا بدوره سيمكنك من الشعور بالثقة والقدرة على حل المشكلات كلما واجهت صعوبات. جميعنا نقع في المشاكل من وقت لآخر، وفهم الأدوات التي نستخدمها هو مفتاح التغلب عليها بفعالية. إن إتقان أوامر مثل git pull و git fetch وكيفية التعامل مع التغييرات المحلية يعد أمراً حيوياً لأي مطور يسعى للحفاظ على سير عمل سلس ومنتج.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *