حل التعارضات
تعلّم كيفية تحديد تعارضات الدمج التي تظهر أثناء العمل التعاوني وحلّها.
حل التعارضات
تعارضات Git أمر لا مفرّ منه في التطوير التعاوني. سيعلّمك هذا الدرس كيفية فهم تعارضات الدمج وتحديدها وحلّها بكفاءة للحفاظ على سير عمل سلس داخل الفريق.
ما هو تعارض Git؟
يحدث التعارض عندما يعجز Git عن دمج تعديلات فرعين تلقائيًا لأنهما يعدّلان الأسطر البرمجية نفسها بطريقة غير متوافقة.
أنواع التعارضات
1. تعارضات المحتوى
تعديلات متزامنة على الأسطر نفسها:
# الفرع A يعدّل:
function greet() {
return "Hello World!";
}
# الفرع B يعدّل:
function greet() {
return "Bonjour le monde!";
}
2. تعارضات إعادة التسمية
# الفرع A: يعيد تسمية file.txt إلى document.txt
# الفرع B: يعدّل محتوى file.txt
# النتيجة: Git لا يعرف ما يجب فعله
3. تعارضات الحذف
# الفرع A: يحذف file.txt
# الفرع B: يعدّل file.txt
# النتيجة: تعارض حذف/تعديل
تشريح التعارض
علامات التعارض
<<<<<<< HEAD
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
=======
function calculateTotal(items) {
let total = 0;
for (let item of items) {
total += item.price;
}
return total;
}
>>>>>>> feature/new-calculation
شرح العلامات:
<<<<<<< HEAD: بداية نسختك (الفرع الحالي)=======: الفاصل بين النسختين>>>>>>> feature/new-calculation: نهاية نسخة الفرع الآخر
تحديد التعارضات
أوامر التشخيص
# عرض حالة التعارضات
git status
# سرد الملفات المتعارضة
git diff --name-only --diff-filter=U
# عرض تفاصيل التعارضات
git diff
# عرض التعارضات مع سياق أوسع
git diff --conflict=diff3
المخرجات المعتادة لأمر git status
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: src/calculator.js
both modified: README.md
no changes added to commit (use "git add" or "git commit -a")
استراتيجيات الحل
1. الحل اليدوي
# 1. تحديد الملفات المتعارضة
git status
# 2. فتح الملف وتحريره يدويًا
# حذف العلامات <<<<<<، =======، >>>>>>>
# الاحتفاظ بالكود المطلوب
# 3. وضع علامة "تم الحل"
git add fichier-resolu.js
# 4. إنهاء الدمج
git commit
2. اختيار نسخة كاملة
# الاحتفاظ بنسختك (HEAD)
git checkout --ours fichier.js
git add fichier.js
# الاحتفاظ بنسختهم
git checkout --theirs fichier.js
git add fichier.js
# الإنهاء
git commit
3. أدوات الدمج
# إعداد أداة دمج
git config --global merge.tool vimdiff
# تشغيل الأداة
git mergetool
# أدوات شائعة أخرى
git config --global merge.tool vscode
git config --global merge.tool kdiff3
git config --global merge.tool meld
الحل خطوة بخطوة
مثال عملي كامل
# الحالة: تعارض أثناء عملية دمج
git merge feature/user-profile
# Auto-merging src/user.js
# CONFLICT (content): Merge conflict in src/user.js
# Automatic merge failed; fix conflicts and then commit the result.
# 1. فحص التعارض
git status
cat src/user.js
محتوى الملف مع التعارض:
class User {
constructor(name, email) {
this.name = name;
this.email = email;
<<<<<<< HEAD
this.id = Math.random().toString(36);
this.createdAt = new Date();
=======
this.id = generateUniqueId();
this.timestamp = Date.now();
>>>>>>> feature/user-profile
}
}
# 2. حل التعارض (تحرير يدوي)
# قرّرنا الجمع بين النهجين
الحل:
class User {
constructor(name, email) {
this.name = name;
this.email = email;
this.id = generateUniqueId(); // استخدام الدالة الأكثر متانة
this.createdAt = new Date(); // الاحتفاظ بصيغة Date
this.timestamp = Date.now(); // إضافة الـ timestamp أيضًا
}
}
# 3. وضع علامة "تم الحل"
git add src/user.js
# 4. التحقق من حل جميع التعارضات
git status
# 5. إنهاء عملية الدمج
git commit -m "Merge feature/user-profile
Resolved conflicts in user.js by combining both ID generation
approaches and keeping both timestamp formats for compatibility."
أدوات الحل
VS Code
يوفّر VS Code واجهة سهلة الاستخدام لحل التعارضات:
// settings.json
{
"git.mergeEditor": true,
"merge-conflict.autoNavigateNextConflict.enabled": true
}
الميزات:
- Accept Current Change : الاحتفاظ بنسختك
- Accept Incoming Change : الاحتفاظ بنسختهم
- Accept Both Changes : الجمع بين النسختين
- Compare Changes : عرض الفروقات جنبًا إلى جنب
GitKraken
واجهة رسومية متقدمة:
- عرض مرئي للفروع والتعارضات
- محرر دمج مدمج
- سجل لعمليات الحل
سطر الأوامر مع vimdiff
# الإعداد
git config --global merge.tool vimdiff
# الاستخدام
git mergetool
# التنقل في vimdiff
# :diffget LOCAL - أخذ النسخة المحلية
# :diffget REMOTE - أخذ النسخة البعيدة
# :diffget BASE - أخذ النسخة الأساسية
الوقاية من التعارضات
أفضل الممارسات
-
المزامنة المتكررة
# جلب التغييرات بانتظام git fetch origin git merge origin/main -
الفروع قصيرة العمر
# تجنّب الفروع طويلة العمر # دمج الميزات الصغيرة بشكل متكرر -
التواصل داخل الفريق
# تنسيق التعديلات على الملفات نفسها # استخدام issues في GitHub للتخطيط -
Rebase قبل الدمج
git fetch origin git rebase origin/main # حل التعارضات واحدًا تلو الآخر git push --force-with-lease origin feature-branch
تنظيم الكود
// تجنّب التعارضات عبر هيكلة الكود
// فصل الميزات في وحدات (modules) مستقلة
// ❌ كل شيء في ملف واحد
// app.js (500 سطر)
// ✅ وحدات منفصلة
// auth.js
// user.js
// api.js
// utils.js
التعارضات المتقدمة
التعارضات أثناء rebase
# بدء rebase
git rebase main
# عند حدوث تعارض
# 1. حل التعارض
# 2. إضافة الملفات التي تم حلها
git add .
# 3. متابعة rebase
git rebase --continue
# أو الإلغاء
git rebase --abort
التعارضات مع cherry-pick
# cherry-pick مع تعارض
git cherry-pick abc1234
# الحل ثم المتابعة
git add .
git cherry-pick --continue
# أو الإلغاء
git cherry-pick --abort
تعارضات merge مقابل rebase
تعارضات merge:
- تُحل دفعة واحدة
- يُحفظ السجل كما هو
- يُنشأ commit دمج
تعارضات rebase:
- تُحل commit بعد commit
- سجل خطي
- لا يوجد commit دمج
تمارين عملية
التمرين 1: تعارض بسيط
# 1. إنشاء مستودع تجريبي
mkdir conflict-demo
cd conflict-demo
git init
# 2. إنشاء ملف أولي
echo "function greet() {
return 'Hello';
}" > greet.js
git add greet.js
git commit -m "Initial greeting function"
# 3. إنشاء فرعين بتعديلات متعارضة
git checkout -b feature-french
echo "function greet() {
return 'Bonjour';
}" > greet.js
git add greet.js
git commit -m "Add French greeting"
git checkout main
git checkout -b feature-spanish
echo "function greet() {
return 'Hola';
}" > greet.js
git add greet.js
git commit -m "Add Spanish greeting"
# 4. الدمج وحل التعارض
git checkout main
git merge feature-french
git merge feature-spanish
# تعارض متوقع!
# 5. الحل عبر الجمع بين اللغات
echo "function greet(language = 'en') {
const greetings = {
'en': 'Hello',
'fr': 'Bonjour',
'es': 'Hola'
};
return greetings[language] || greetings['en'];
}" > greet.js
git add greet.js
git commit -m "Resolve conflict by supporting multiple languages"
التمرين 2: تعارض معقّد بين عدة ملفات
# 1. إنشاء مشروع من عدة ملفات
echo "# My App" > README.md
echo "const version = '1.0.0';" > version.js
echo "function add(a, b) { return a + b; }" > math.js
git add .
git commit -m "Initial project setup"
# 2. الفرع A: تعديلات متعددة
git checkout -b feature-updates
echo "# My Awesome App
Version 2.0 with new features!" > README.md
echo "const version = '2.0.0';" > version.js
echo "function add(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new Error('Invalid input');
}
return a + b;
}" > math.js
git add .
git commit -m "Update to version 2.0 with validation"
# 3. الفرع B: تعديلات مختلفة
git checkout main
git checkout -b feature-improvements
echo "# My App
Now with better performance!" > README.md
echo "const version = '1.1.0';" > version.js
echo "function add(a, b) {
// Optimized addition
return Number(a) + Number(b);
}" > math.js
git add .
git commit -m "Performance improvements v1.1"
# 4. الدمج وحل جميع التعارضات
git checkout main
git merge feature-updates
git merge feature-improvements
# حل كل ملف على حدة
استراتيجيات الفريق
سير عمل بأقل قدر من التعارضات
# 1. فروع ميزات قصيرة
git checkout -b feature/small-change
# ... تطوير سريع ...
git push origin feature/small-change
# ... PR ودمج سريع ...
# 2. Rebase قبل PR
git fetch origin
git rebase origin/main
git push --force-with-lease origin feature/small-change
# 3. دمج main بشكل متكرر
git checkout feature/long-running
git merge origin/main
# حل التعارضات أولًا بأول
التواصل بشأن التعارضات
# قالب رسالة لتعارض تم حلّه
## تعارض تم حلّه في PR #123
**الملفات المتأثرة:**
- src/user.js
- src/auth.js
**طبيعة التعارض:**
تعديلات متزامنة على دوال المصادقة
**الحل المعتمد:**
الجمع بين النهجين:
- الإبقاء على التحقق من feature-a
- دمج التحسين من feature-b
**الاختبارات المنفّذة:**
- [ ] نجاح اختبارات الوحدة
- [ ] نجاح اختبارات التكامل
- [ ] تحقق يدوي
cc @team-lead @reviewer
استكشاف الأخطاء المتقدم
التراجع عن حل تعارض
# إذا ارتكبت خطأ أثناء الحل
git reset --hard HEAD~1 # يلغي commit الدمج
git merge feature-branch # إعادة بدء الدمج
عرض سجل التعارضات
# عرض عمليات الدمج السابقة
git log --merges --oneline
# عرض تفاصيل عملية دمج
git show commit-hash
# عرض تعارضات عملية دمج محددة
git log --cc commit-hash
الحل التلقائي
# إعداد استراتيجيات الدمج
git config merge.ours.driver true # الاحتفاظ دائمًا بنسختنا
git config merge.theirs.driver "cp %B %A" # الاحتفاظ دائمًا بنسختهم
# في ملف .gitattributes
*.generated merge=ours
*.config merge=theirs
موارد إضافية
أدوات موصى بها
- Beyond Compare : أداة احترافية للمقارنة والدمج
- P4Merge : مجانية وفعّالة
- Meld : مفتوحة المصدر ومتعددة المنصات
- KDiff3 : دمج ثلاثي الاتجاهات
التوثيق
الخاتمة
يُعدّ حل التعارضات مهارة أساسية لكل مطوّر يعمل ضمن فريق. ومع الممارسة والأدوات المناسبة، تصبح التعارضات جزءًا طبيعيًا يمكن التحكم فيه من سير عمل التطوير.
في الدرس القادم، سنستكشف مسارات عمل Git المتقدمة المستخدمة في الفرق المحترفة.
مدة القراءة المقدّرة: 30 دقيقة
المستوى: متوسط
الخطوة السابقة: التعاون عبر GitHub
الخطوة التالية: مسارات عمل Git المتقدمة