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

حل التعارضات في Git خطوة بخطوة

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

في درس الفروع رأيت تعارضاً بسيطاً في سطر واحد وحللته بيدك. في العمل الحقيقي التعارضات أكبر: عدة ملفات في دمج واحد، وأحياناً تريد نسخة زميلك كاملة، وأحياناً تريد إلغاء الدمج كله والتفكير من جديد. في هذا الدرس ستتعلم التعامل مع التعارضات بهدوء وبأدوات واضحة، حتى تصبح أمراً روتينياً لا يخيفك.

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

تخيّل زميلين يعدّلان مستنداً مطبوعاً: إذا عدّل كل منهما فقرة مختلفة فالجمع سهل. أما إذا أعاد كل منهما كتابة الجملة نفسها فلا بد من إنسان يقرر الصيغة النهائية.

في مشروعنا فرع dark-theme غيّر لون العنوان في style.css وعدّل عنوان index.html وأضاف ملفاً جديداً. وفي الوقت نفسه غيّرنا في main السطرين نفسيهما بطريقة أخرى:

نافذة طرفيّة
git merge dark-theme
الناتج
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Auto-merging style.css
CONFLICT (content): Merge conflict in style.css
Automatic merge failed; fix conflicts and then commit the result.

تعارضان في ملفين. الدمج لم يفشل، بل توقف في منتصفه ينتظر قرارك.

نافذة طرفيّة
git status
الناتج
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Changes to be committed:
new file: footer.html
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: index.html
both modified: style.css

اقرأ الرسالة جيداً، ففيها كل ما تحتاج:

  • Changes to be committed: ما دُمج تلقائياً بلا مشاكل (الملف الجديد footer.html).
  • Unmerged paths: الملفات التي تنتظر حلاً. و both modified تعني أن الفرعين عدّلا الملف.
  • وبين الأقواس تذكير بالأوامر المتاحة: الحل ثم commit، أو الإلغاء.

وهذا محتوى style.css الآن:

style.css
<<<<<<< HEAD
h1 { color: green; }
=======
h1 { color: white; }
>>>>>>> dark-theme
body { font-size: 16px; }

ما بين <<<<<<< HEAD و ======= نسختك الحالية، وما بعدها حتى >>>>>>> نسخة الفرع القادم. والسطر الأخير خارج العلامات: دُمج بلا مشاكل.

التعارض أكبر مما توقعت، أو لست متأكداً من القرار؟ ألغِ الدمج كله وارجع لما قبله:

نافذة طرفيّة
git merge --abort
git status --short
git log --oneline -1
الناتج
f29b601 ألوان خضراء وعنوان رسمي

git status --short لم تطبع شيئاً: المجلد نظيف، والعلامات اختفت من الملفات، والفرع عند آخر commit كأنك لم تبدأ الدمج. اسأل زميلك، ثم أعد المحاولة متى شئت.

الحل الأول: خذ نسخة كاملة

Section titled “الحل الأول: خذ نسخة كاملة”

نعيد الدمج. في style.css قررنا أن ألوان الوضع الليلي هي الصحيحة كاملة، فلا داعي لتحرير الملف بيدنا:

نافذة طرفيّة
git merge dark-theme
git checkout --theirs style.css
الناتج
Updated 1 path from the index
  • --theirs: نسخة الفرع القادم (dark-theme).
  • --ours: نسخة الفرع الحالي (main).

الملف الآن نظيف وفيه color: white، بلا أي علامات. ثم نخبر Git أن التعارض حُل:

نافذة طرفيّة
git add style.css
git status --short
الناتج
A footer.html
UU index.html
M style.css

UU تعني «ما زال متعارضاً». بقي ملف واحد.

الحل الثاني: التحرير اليدوي

Section titled “الحل الثاني: التحرير اليدوي”

في index.html لا نريد أياً من النسختين كما هي، بل صيغة تجمعهما. نفتح الملف، ونحذف العلامات الثلاث، ونكتب السطر النهائي، ونحفظ. ثم:

نافذة طرفيّة
git add index.html
git status
الناتج
On branch main
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
new file: footer.html
modified: style.css

«كل التعارضات حُلّت، لكنك ما زلت في منتصف الدمج». تبقى خطوة الإنهاء:

نافذة طرفيّة
git commit -m "دمج الوضع الليلي مع حل التعارضات"
git log --oneline --graph -4
الناتج
* 241edea دمج الوضع الليلي مع حل التعارضات
|\
| * 3d5adcd الوضع الليلي
* | f29b601 ألوان خضراء وعنوان رسمي
|/
* 31b963a إضافة style.css

الرسم يوضح القصة: خطان افترقا ثم التقيا في commit الدمج.

  1. git status لمعرفة الملفات المتعارضة.
  2. لكل ملف: حرّره يدوياً، أو خذ نسخة كاملة بـ --ours أو --theirs.
  3. git add لكل ملف بعد حله.
  4. git commit لإنهاء الدمج.
  5. في أي لحظة قبل الخطوة الرابعة: git merge --abort للتراجع.

أخطر خطأ: أن تنفّذ git add وفي الملف علامات تعارض منسية، فتدخل المشروع وتكسره. تحقق قبل الحفظ:

نافذة طرفيّة
git diff --check
الناتج
note.md:1: leftover conflict marker
note.md:3: leftover conflict marker
note.md:5: leftover conflict marker

الأمر يدلك على كل علامة منسية برقم سطرها. وبعد الحل شغّل المشروع وتأكد أنه يعمل: Git يضمن أن الملفات خالية من العلامات، لا أن الكود صحيح.

git pull يجلب تعديلات زملائك ثم يدمجها. فإذا عدّلوا السطور التي عدّلتها ظهرت الرسائل نفسها، والحل بالخطوات نفسها تماماً: status، ثم الحل، ثم add، ثم commit، ثم git push.

  • اسحب كثيراً: ابدأ يومك بـ git pull، وادمج main في فرعك كل يوم أو يومين.
  • فروع قصيرة العمر: فرع يعيش يومين يتعارض أقل بكثير من فرع يعيش شهراً.
  • commits صغيرة ومركزة: تعديل واحد واضح في كل commit.
  • تواصل مع فريقك: إذا كنتما ستعدّلان الملف نفسه، فاتفقا مسبقاً.
  • لا تعد تنسيق ملفات كاملة دون داعٍ: تغيير المسافات في كل السطور يتعارض مع كل من يعمل على الملف.
  1. في مستودع تجريبي أنشئ ملفاً من ثلاثة أسطر واحفظه.
  2. أنشئ فرعاً وعدّل فيه السطر الأول، ثم عد إلى main وعدّل السطر نفسه بشكل مختلف، واحفظ في كل فرع.
  3. ادمج، واقرأ git status، ثم ألغِ الدمج بـ git merge --abort.
  4. أعد الدمج وحل التعارض مرة بـ --ours، ثم كرر التجربة كلها وحلّه يدوياً.
  • الخلط بين ours و theirs: ours الفرع الذي تقف عليه، و theirs الفرع الذي تدمجه. راجع الملف بعد الاختيار.
  • نسيان git add بعد الحل: يبقى الملف UU ويرفض Git إنهاء الدمج.
  • حذف نسخة الزميل دون قراءة: اختيار نسختك دائماً يمحو عمل غيرك بصمت. افهم التعديلين قبل القرار.
  • علامات منسية: استخدم git diff --check وجرّب المشروع قبل commit.

سؤال

أنت على الفرع main وتدمج الفرع feature. أي أمر يأخذ نسخة feature كاملة لملف متعارض؟

سؤال

ماذا يفعل git merge --abort أثناء دمج فيه تعارضات؟

  • التعارض يحدث عند تعديل السطور نفسها في فرعين، والدمج يتوقف منتظراً قرارك.
  • git status يعرض الملفات المتعارضة والأوامر المتاحة.
  • git merge --abort يلغي الدمج بأمان في أي لحظة قبل الحفظ.
  • الحل: تحرير يدوي أو --ours / --theirs، ثم git add، ثم git commit.
  • git diff --check يكشف العلامات المنسية، والسحب المتكرر يقلل التعارضات.