> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# Trunk-Based Development: فروع قصيرة ودمج يومي
- URL: https://arabroot.io/articles/التطوير-على-الفرع-الرئيسي-trunk-based/
- Published: 2026-10-05T14:00:00.000Z
- Updated: 2026-10-09T09:42:20.000Z
- Description: لماذا تنفجر الفروع الطويلة يوم الإصدار، وكيف يدمج الفريق عمله في main كل يوم بأمان مع CI وFeature Flags.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, DevOps والتشغيل, DevOps وCI/CD, الاستضافة الذاتية

يبدأ أغلب الفرق العمل بالفروع Branches بنية حسنة، فكل مطور يفتح فرعاً لميزته حتى لا يكسر عمل غيره، ثم تمر الأيام وتصبح الأيام أسابيع، وفي يوم الإصدار يحاول الفريق أن يجمع كل هذه الفروع في فرع واحد فيكتشف أن كل فرع منها كتب بافتراضات تغيرت منذ مدة، وأن الدمج Merge الذي كان يظنه خطوة من دقائق صار يوماً كاملاً من حل التعارضات Conflicts، وأن الأخطاء التي ظهرت بعد الدمج لم يرها أحد قبل ذلك لأن كل فرع كان يختبر وحده. وفي الوقت نفسه يسأل فريق الاختبار QA سؤالاً منطقياً: ما الذي نختبره بالضبط؟ هل نختبر فرع الميزة كما هو، مع أنه لن يصدر بهذا الشكل بعد أن يدمج مع غيره؟

هذه المشكلة يعرفها كل فريق برمجيات تقريباً، والحل الذي توصي به أبحاث DORA ويطبقه كثير من الفرق هو **التطوير على الفرع الرئيسي Trunk-Based Development**، أي أن يدمج كل مطور عمله في فرع واحد مشترك كل يوم على الأقل، بدل أن يعيش في فرعه أسابيع. وإذا كانت أوامر Git نفسها جديدة عليك فابدأ بمقال [إدارة النسخ وGit للمبتدئين](https://arabroot.io/articles/%D8%A7%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D9%86%D8%B3%D8%AE-%D9%88-git-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)، وإذا أردت تفاصيل الدمج وإعادة الأساس Rebase ونسخ commit واحد Cherry-Pick فهي في [Git Merge وRebase وCherry-Pick](https://arabroot.io/articles/git-merge-%D9%88-rebase-%D9%88-cherry-pick/)، أما هذا المقال فيتحدث عن طريقة تنظيم الفروع نفسها، وسوف نناقش فيه ما يلي:

- لماذا تسبب الفروع طويلة العمر Long-Lived Branches كل هذه المشكلات، مع مثال حقيقي يظهر التعارضات يوم الإصدار.
- ما هو Trunk-Based Development بتعريف مصادره، وما الذي وجدته أبحاث DORA.
- ما الذي يجعله آمناً: ال CI السريع، وحماية الفرع Branch Protection، وطلبات الدمج الصغيرة، وال Feature Flags، وBranch by Abstraction، والإصدار من الفرع الرئيسي.
- مقارنة صريحة مع Git Flow وGitHub Flow، ومتى يناسب كل منها.
- مثال عملي كامل بمطورين وفروع قصيرة وFeature Flag ووسم إصدار وإصلاح ينسخ إلى فرع الإصدار، بمخرجات Git حقيقية.
- كيف تنقل فريقك من Git Flow خطوة بخطوة، والجانب الثقافي في الفريق، ثم الأخطاء الشائعة.

## المشكلة: فروع تعيش أسابيع

لنفرض أن لديك تطبيق متجر صغيراً، وأن الفريق يعمل بطريقة Git Flow المعروفة، أي أن هناك فرعاً اسمه `main` لما هو في الإنتاج Production، وفرعاً اسمه `develop` يجمع العمل، ولكل ميزة فرع يخرج من `develop` ويعود إليه عندما تكتمل. المطور الأول يعمل على كوبونات الخصم في `feature/coupons`، والثاني يضيف ضريبة القيمة المضافة VAT في `feature/vat`، وكلاهما يعدل الملف نفسه الذي يحسب المجموع، وخلال ذلك يظهر خطأ في الإنتاج فيصلح في فرع `hotfix/1.0.1`. والصورة التالية تبين ما يحدث لهذه الفروع خلال ثلاثة أسابيع:

![مخطط زمني لثلاثة أسابيع: فرع main وفرع develop وفرعان للميزات يبتعدان عن develop، وإصلاح عاجل يدمج مرتين في main وdevelop، وفريق QA يختبر فرع الضريبة وحده، ثم تعارضان في يوم الإصدار، ومثلث يكبر يمثل الكود غير المدمج](https://arabroot.io/content/images/2026/10/trunk-based-01-drift.webp)

كل أسبوع يمر على فرع الميزة يزيد الكود الذي لم يندمج مع عمل الآخرين، والفاتورة تدفع يوم الإصدار

لاحظ في الصورة أن المشكلة ليست في Git نفسه، وإنما في المدة التي يبقى فيها العمل منفصلاً، وهذه المدة تولد خمس مشكلات يعرفها كل من عمل في فريق:

- **جحيم الدمج Merge Hell:** كل فرع يتغير وحده أسابيع، وعندما يحين الدمج يجد المطور أن الكود الذي بنى عليه تغير، فيحل التعارضات وهو لا يتذكر لماذا كتب زميله ما كتبه.
- **أخطاء التكامل Integration Bugs تكتشف متأخرة:** كل فرع ينجح في اختباراته وحده، والخطأ يظهر فقط عندما تجتمع الفروع، أي في آخر يوم وتحت ضغط موعد الإصدار.
- **فريق QA يختبر شيئاً لن يصدر:** فرع `feature/vat` الذي اختبره الفريق يختلف عن الكود الذي سيصدر بعد دمجه مع الكوبونات والإصلاح العاجل، لذلك يعاد الاختبار بعد الدمج، أو لا يعاد ويخرج الخطأ إلى الإنتاج.
- **فروع إصدار لا تعود:** فرع الإصدار Release Branch يفتح للتثبيت ثم تضاف عليه إصلاحات، وإذا نسي أحد أن يعيدها إلى `develop` عاد الخطأ في الإصدار التالي.
- **الإصلاح العاجل يطبق مرتين:** قاعدة Git Flow أن فرع ال Hotfix يدمج في `main` وفي `develop` معاً، وكل دمج مزدوج فرصة لتعارض جديد أو لنسيان أحد الطرفين.

### التعارضات يوم الإصدار بمخرجات حقيقية

حتى لا يبقى الكلام نظرياً، قمنا ببناء السيناريو أعلاه بسكربت في Container من Alpine 3.24 مع Git 2.54.0: ملف `checkout.sh` يطبع المجموع، وفرع الكوبونات بثلاثة commits على مدى ثلاثة أسابيع، وفرع الضريبة بـ commitين، وإصلاح عاجل يجعل المبلغ إلزامياً ويدمج في `main` و`develop` كما تطلب Git Flow. وفي يوم الإصدار نقوم بدمج الفرعين في `develop`:

```bash
git switch develop
git merge --no-ff feature/vat -m "Merge feature/vat into develop"
```

والمخرج سوف يكون كما يلي:

```text
Auto-merging checkout.sh
CONFLICT (content): Merge conflict in checkout.sh
Automatic merge failed; fix conflicts and then commit the result.
```

وعند عرض التعارض بالأمر `git diff` تجد أن الإصلاح العاجل الذي دمج قبل أسبوعين يتعارض مع سطر غيره فرع الضريبة:

```text
diff --cc checkout.sh
index 091ba1f,9d92d81..0000000
--- a/checkout.sh
+++ b/checkout.sh
@@@ -1,4 -1,6 +1,11 @@@
  #!/bin/sh
  # Usage: ./checkout.sh <amount>
++<<<<<<< HEAD
 +amount="${1:?amount is required}"
 +echo "total: $amount"
++=======
+ amount="$1"
+ total=$(awk -v a="$amount" 'BEGIN { printf "%.2f", a * 1.15 }')
+ echo "total: $total"
+ # vat: 15% (week 2)
++>>>>>>> feature/vat
```

بعد أن يحل أحد أفراد الفريق هذا التعارض يدوياً، ننتقل إلى الفرع الثاني:

```bash
git merge --no-ff feature/coupons -m "Merge feature/coupons into develop"
git diff
```

والمخرج سوف يكون كما يلي:

```text
Auto-merging checkout.sh
CONFLICT (content): Merge conflict in checkout.sh
Automatic merge failed; fix conflicts and then commit the result.
diff --cc checkout.sh
index 46e1452,9c051aa..0000000
--- a/checkout.sh
+++ b/checkout.sh
@@@ -1,6 -1,6 +1,14 @@@
  #!/bin/sh
++<<<<<<< HEAD
 +# Usage: ./checkout.sh <amount>
 +amount="${1:?amount is required}"
 +total=$(awk -v a="$amount" 'BEGIN { printf "%.2f", a * 1.15 }')
 +echo "total: $total"
 +# vat: 15% (week 2)
++=======
+ # Usage: ./checkout.sh <amount> [coupon]
+ amount="$1"
+ [ "$2" = "SAVE10" ] && amount=$((amount * 90 / 100))
+ echo "total: $amount"
+ # coupons: SAVE10 only (week 3)
++>>>>>>> feature/coupons
```

في المخرج أعلاه لاحظ التالي:

- التعارض الثاني أصعب من الأول، لأن فرع الكوبونات بني على نسخة لا تعرف شيئاً عن الضريبة ولا عن الإصلاح العاجل، فمن يحل التعارض يجب أن يفهم الميزتين معاً.
- هناك مشكلة لا يظهرها Git أصلاً: الكوبونات تحسب بأعداد صحيحة `$((amount * 90 / 100))`، والضريبة تحسب بالكسور العشرية، وحتى لو حل التعارض نصياً فقد ينتج كود يعمل ويعطي أرقاماً خاطئة، وهذا هو خطأ التكامل الذي يكتشف متأخراً.
- لو دمج كل مطور عمله في اليوم الأول لظهر هذا الاختلاف في سطرين، وقت أن كانت التغييرات صغيرة وفي ذاكرة صاحبها.

وهذا هو شكل التاريخ History قبل الدمج، بالأمر `git log --graph --oneline --all`:

```text
*   5cf99c2 Merge feature/vat into develop
|\
| * 2a5883c vat: week 2
| * 918fadc vat: week 1, add 15% VAT
* |   39338d4 Merge hotfix/1.0.1 into develop
|\ \
| |/
|/|
| | * 74a32f8 Merge hotfix/1.0.1 into main
| |/|
|/|/
| * 9857a4e hotfix: fail when amount is missing
|/
| * 47a6013 coupons: week 3
| * d91e23a coupons: week 2
| * aa289fb coupons: week 1, SAVE10 code
|/
* d1a2ac3 Initial checkout
```

### الحلول الأولى التي تخطر على البال، ولماذا لا تكفي

1. **أن يدمج كل مطور develop في فرعه كل يوم:** هذا يقلل المفاجأة من جهة واحدة فقط، فأنت تأخذ ما وصل إلى `develop`، ولكن زميلك الذي يعمل في فرعه منذ أسبوعين لم يدمج شيئاً بعد، فالتعارض بينكما ما زال مؤجلاً.
2. **تجميد الكود Code Freeze قبل الإصدار:** يوقف الفريق إضافة أي ميزة أسبوعاً ليثبت ما لديه، وهذا يعالج العرض لا السبب، والأبحاث تقول العكس كما سيأتي، فالفرق الأعلى أداء لا تجمد الكود ولا تخصص مرحلة للتكامل.
3. **أسبوع للتكامل Integration Phase:** يخصص الفريق آخر أيام الدورة لدمج الفروع وإصلاح ما ينكسر، وهي أسوأ الطرق على الإطلاق، لأنها تجمع كل المخاطر في أضيق وقت.

والحل الأنسب هو أن لا يتراكم الكود غير المدمج أصلاً، وهذا ما يفعله Trunk-Based Development.

## ما هو Trunk-Based Development؟

يعرفه موقع [trunkbaseddevelopment.com](https://trunkbaseddevelopment.com/?ref=arabroot.io)، وهو المرجع الذي يجمع ممارساته، بأنه نموذج لإدارة الفروع في نظام التحكم بالنسخ يتعاون فيه المطورون على الكود في فرع واحد يسمى الجذع Trunk، ويقاومون أي ضغط لإنشاء فروع تطوير أخرى طويلة العمر باستخدام تقنيات موثقة. والجذع في Git هو عادة الفرع `main`، ويضيف الموقع أن كل أفراد الفريق يدمجون في الجذع مرة واحدة على الأقل كل 24 ساعة، وهذا هو الحد الأدنى لما يسمى التكامل المستمر Continuous Integration بمعناه الأصلي، أي أن الكلمة تصف عادة في الفريق قبل أن تصف أداة تشغل الاختبارات.

وتعرفه صفحة [Trunk-Based Development في موقع DORA](https://dora.dev/capabilities/trunk-based-development/?ref=arabroot.io) بأن كل مطور يقسم عمله إلى دفعات صغيرة Small Batches ويدمجها في الجذع مرة على الأقل في اليوم، وربما عدة مرات. وDORA هو برنامج البحث الذي يقف خلف تقارير State of DevOps وكتاب [Accelerate](https://itrevolution.com/product/accelerate/?ref=arabroot.io) الصادر في 2018 لـ Nicole Forsgren وJez Humble وGene Kim، وتذكر الصفحة أن الفرق تحقق أداء أعلى في التسليم والتشغيل عندما:

- يكون في مستودع التطبيق ثلاثة فروع نشطة أو أقل.
- تدمج الفروع في الجذع مرة على الأقل في اليوم.
- لا يوجد تجميد للكود ولا مراحل مخصصة للتكامل.

وهناك طريقتان للعمل بهذا الأسلوب، والاختيار بينهما يعتمد على حجم الفريق وطبيعته:

- **الدمج المباشر في الجذع:** يدفع المطور Push إلى `main` مباشرة بعد أن تنجح الاختبارات على جهازه، وهذا يناسب فريقاً صغيراً جداً يعمل بالبرمجة الثنائية Pair Programming أو لا يحتاج مراجعة رسمية.
- **الفروع قصيرة العمر Short-Lived Feature Branches:** وهي الطريقة الشائعة في أغلب الفرق، فالمطور يفتح فرعاً صغيراً ويرفعه بطلب دمج Pull Request يمر بالمراجعة Code Review وبال CI ثم يدمج ويحذف. ويحدد [trunkbaseddevelopment.com](https://trunkbaseddevelopment.com/short-lived-feature-branches/?ref=arabroot.io) عمر هذا الفرع بيومين على الأكثر، وإذا زاد عن ذلك صار في خطر أن يتحول إلى فرع طويل العمر، ويعمل عليه مطور واحد، أو اثنان إذا كانا يبرمجان معاً.

والصورة التالية تبين الشكل الذي يصبح عليه العمل في هذه الطريقة:

![مخطط لثلاثة أيام: فرع main أفقي، وفروع قصيرة لمطورين تخرج منه وتعود إليه في اليوم نفسه، وعلى طريق عودة كل فرع درع أخضر يمثل نجاح ال CI والمراجعة، ووسم v1.1.0 على main، وأسفل المخطط خمس خطوات من إنشاء الفرع إلى الإصدار](https://arabroot.io/content/images/2026/10/trunk-based-02-trunk.webp)

كل فرع يعيش ساعات ويعود إلى main بعد المراجعة وال CI، فلا يوجد فرع develop ولا فرع يعيش أسابيع

والقاعدة التي تجمع كل ذلك أن **الفرع main قابل للإصدار دائماً Always Releasable**، أي أن أي commit عليه نجح في ال CI يمكن أن يصدر في أي لحظة، والسبب أن الفريق لم يعد يجمع العمل في فرع وسيط مثل `develop` ثم يثبته، فإذا لم يكن `main` صالحاً للإصدار فلا يوجد مكان آخر يصلح لذلك.

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

## ما الذي يجعل الدمج اليومي آمناً؟

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

### CI سريع على كل commit وكل طلب دمج

كل Push إلى فرع، وكل Pull Request، يشغل خطاً آلياً Pipeline يفحص التنسيق Lint ويشغل الاختبارات Tests ويبني التطبيق Build، ولا يدمج شيء في `main` قبل أن ينجح هذا الخط. ومن الأخطاء الشائعة التي تذكرها DORA في صفحتها الدمج دون اختبارات آلية، فالاختبارات يجب أن تعمل على التغيير قبل دمجه، وتوصي بأن تنتهي عملية البناء والاختبار في دقائق قليلة، والسبب أن المطور الذي ينتظر نصف ساعة سوف يجمع تغييرات أكبر ليقلل عدد مرات الانتظار، وبالتالي تعود الفروع الكبيرة من الباب الخلفي. وقد شرحنا تشغيل خط CI على سيرفرك في دليل [Gitea Actions لتشغيل CI/CD على السيرفر](https://arabroot.io/articles/gitea-actions-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-ci-cd-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85%D9%83/).

### حماية الفرع main

القاعدة المكتوبة في ملف README لا تمنع أحداً من الدفع مباشرة إلى `main` في لحظة استعجال، لذلك اجعلها قاعدة يفرضها السيرفر. وفي Gitea، الذي شرحنا تثبيته في [دليل تثبيت Gitea](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-gitea-%D9%85%D8%B9-docker-compose/)، تجد هذه القواعد في إعدادات المستودع تحت Settings ثم Branches، وتوثق صفحة [Protected Branches](https://docs.gitea.com/usage/access-control/protected-branches?ref=arabroot.io) خياراتها، وأهمها لهذه الطريقة:

- **منع الدفع المباشر Disable Push:** فكل تغيير يصل إلى `main` عبر Pull Request.
- **منع الدفع القسري Disable Force Push:** حتى لا يعاد كتابة تاريخ الفرع الذي يعتمد عليه الجميع.
- **عدد الموافقات المطلوبة Required Approvals:** موافقة واحدة كافية لأغلب الفرق، وزيادتها تبطئ الدمج كما سيأتي.
- **فحص الحالة Status Check:** تحدد فيه أسماء مهام ال CI التي يجب أن تنجح قبل أن يسمح بالدمج.
- **منع الدمج إذا كان الطلب متأخراً عن الفرع Block merge if the pull request is outdated:** فيضطر صاحب الطلب إلى تحديثه على آخر `main`، وبالتالي يختبر ال CI الكود كما سيكون بعد الدمج.
- **إلزام المدراء Administrators must follow branch protection rules:** حتى لا يصبح حساب المدير طريقاً مختصراً حول القواعد.

### طلبات دمج صغيرة ومراجعة سريعة

طلب الدمج الذي يغير ثلاثين ملفاً لا يراجعه أحد حقيقة، وإنما يوافق عليه بعد نظرة سريعة، أما الذي يغير ملفين فيقرأ سطراً سطراً في دقائق. وتذكر DORA من الأخطاء الشائعة عملية مراجعة ثقيلة تتطلب موافقات كثيرة، ومراجعة غير متزامنة تترك الطلب ينتظر، فكلما تأخر الدمج زادت احتمالات التعارض، وتوصي بالمراجعة المتزامنة Synchronous Review، أي أن يراجع الزميل الطلب قريباً من وقت فتحه، أو أن يراجعه مع صاحبه مباشرة.

### Feature Flags: ادمج الكود واطلق الميزة لاحقاً

ال Feature Flag، ويسمى أيضاً Feature Toggle، شرط في الكود يقرر في وقت التشغيل Runtime هل يعمل المسار الجديد أم القديم، وقيمته تأتي من متغير بيئة Environment Variable أو من خدمة مخصصة. وبهذا يصبح الكود غير المكتمل جزءاً من `main` ومن كل إصدار، ولكنه لا يعمل لأحد حتى تقرر تشغيله، وهذا يفصل بين أمرين كانا مرتبطين: النشر Deployment، أي نقل الكود إلى السيرفر، والإطلاق Release، أي أن يرى المستخدمون الميزة. والصورة التالية تبين ذلك:

![مخطط من ست خطوات: كود في main يحتوي شرطاً على Flag الكوبونات، ثم سيرفر الإنتاج بالإصدار نفسه، ثم خدمة ال Flags Flag Service، ثم أربع حالات: مطفأ للجميع، ومشغل لفريق العمل وQA، وإطلاق تدريجي لنسبة من المستخدمين، وKill Switch أحمر يطفئ الميزة، وأخيراً حذف ال Flag بعد اكتمال الإطلاق](https://arabroot.io/content/images/2026/10/trunk-based-03-flag-1.webp)

الإصدار نفسه في الإنتاج، وال Flag وحده يقرر من يرى الميزة الجديدة

ويصنف Pete Hodgson في مقاله المرجعي [Feature Toggles](https://martinfowler.com/articles/feature-toggles.html?ref=arabroot.io) ال Flags إلى أنواع، أهمها لهذا المقال Release Toggles التي تسمح بشحن مسارات غير مكتملة إلى الإنتاج ككود كامن قد لا يشغل أبداً، وOps Toggles التي تتحكم في سلوك النظام. ومن هذه الفكرة تخرج ثلاثة استخدامات يحتاجها كل فريق:

- **الإطلاق المظلم Dark Launch:** الكود في الإنتاج وال Flag مطفأ، أو مفعل لفريق العمل وفريق QA فقط، فيختبرون الميزة على البيانات الحقيقية قبل أي مستخدم.
- **الإطلاق التدريجي Gradual Rollout:** تشغل الميزة لنسبة صغيرة من المستخدمين ثم تزيدها، وإذا ظهرت مشكلة فهي تصيب عدداً قليلاً.
- **Kill Switch:** إذا سببت الميزة مشكلة في الإنتاج تطفئها في ثوان، دون بناء جديد ودون نشر جديد.

وفي البداية يكفي متغير بيئة كما في المثال العملي أدناه، وعندما يزيد عدد ال Flags وتحتاج نسباً ومجموعات مستخدمين وسجلاً لمن غير ماذا، تنتقل إلى أداة مخصصة مثل Unleash أو Flagsmith أو GrowthBook، وهي مخطط لها في مرحلة [منصات للمطورين](https://arabroot.io/start/#stage-devplatform) في خارطة الطريق. أما تفعيل ال Flag في بيئة dev أو staging دون الإنتاج فيتعلق بالبيئات نفسها، وهو مشروح في مقال [البيئات ومعاينة الميزات وGitOps للفرق](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D9%88%D9%85%D8%B9%D8%A7%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D9%88-gitops-%D9%84%D9%84%D9%81%D8%B1%D9%82/).

⚠️

ال Flag يخفي الكود عن المستخدمين ولكنه لا يخفيه عن قاعدة البيانات، فإذا كانت الميزة تغير الجداول فاجعل كل ترحيل Migration متوافقاً مع الكود القديم والجديد معاً، كما شرحنا في قسم [ترحيل قاعدة البيانات مع كل نشر](https://arabroot.io/articles/%D9%82%D8%A7%D8%A6%D9%85%D8%A9-%D8%AA%D8%AD%D9%82%D9%82-%D8%A7%D9%84%D9%85%D8%B7%D9%88%D8%B1-%D9%82%D8%A8%D9%84-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%87/#migrations) من قائمة تحقق المطور، والسبب أن الإصدار نفسه سوف يعمل وال Flag مطفأ ومفعل.

### التغييرات الكبيرة: Branch by Abstraction وKeystone Interface

بعض التغييرات لا تختبئ خلف شرط واحد، مثل استبدال مكتبة الدفع أو طبقة الوصول إلى قاعدة البيانات، والحل البسيط هو فرع يعيش شهراً حتى يكتمل الاستبدال، وهو بالضبط ما نحاول تجنبه. والطريقة البديلة اسمها [Branch by Abstraction](https://trunkbaseddevelopment.com/branch-by-abstraction/?ref=arabroot.io)، وقد سماها Stacy Curl في 2007، وخطواتها كما يلي:

1. أضف طبقة تجريد Abstraction حول الكود الذي سوف تستبدله، واجعل كل من يستخدمه يمر عبرها، وادمج ذلك في `main`.
2. اكتب التنفيذ الجديد Implementation خلف الطبقة نفسها، وادمجه تدريجياً وهو مطفأ.
3. شغل التنفيذ الجديد لبقية الفريق ثم للمستخدمين.
4. احذف التنفيذ القديم.
5. احذف طبقة التجريد إذا لم تعد تحتاجها.

وهناك طريقة أبسط لكثير من الميزات يسميها Martin Fowler [Keystone Interface](https://martinfowler.com/bliki/KeystoneInterface.html?ref=arabroot.io)، وهي أن تبني كل الكود الخلفي Back-End وتدمجه وتختبره، وتؤجل الواجهة التي يراها المستخدم إلى النهاية، فهي مثل حجر القفل في القوس، توضع أخيراً فتكتمل الميزة وتظهر للمستخدمين، وهذا ما يفعله المطور الأول في المثال العملي: محرك الكوبونات يدمج في اليوم الأول دون أن يستدعيه أحد، والربط بصفحة الدفع يأتي في اليوم الثاني.

### الإصدار من الفرع الرئيسي: وسوم أو فرع إصدار قصير

عندما يكون `main` صالحاً للإصدار دائماً فهناك طريقتان للإصدار، والصورة التالية تبينهما:

![لوحتان: الأولى فرع main عليه وسوم v1.2.0 وv1.3.0 ثم إصلاح على main بوسم v1.3.1، والثانية فرع release/1.1 يخرج من الوسم v1.1.0، وإصلاح على main ينسخ بالأمر git cherry-pick -x إلى فرع الإصدار ويوسم v1.1.1، وفرع الإصدار يحذف لاحقاً دون أن يدمج في main، وسهم أحمر يحذر من كتابة الإصلاح على فرع الإصدار أولاً](https://arabroot.io/content/images/2026/10/trunk-based-04-release.webp)

الإصلاح يكتب على main أولاً ثم ينسخ إلى فرع الإصدار، ولا يحدث العكس

- **الإصدار بوسم Tag من main:** تضع وسماً مثل `v1.3.0` على commit نجح في ال CI بالأمر [git tag](https://git-scm.com/docs/git-tag?ref=arabroot.io)، والنشر يأخذ هذا ال commit تحديداً. وإذا ظهر خطأ فإنه يصلح على `main` ويصدر إصدار جديد، وهذا ما يسمى الإصلاح إلى الأمام Fix Forward، ويذكر [trunkbaseddevelopment.com](https://trunkbaseddevelopment.com/branch-for-release/?ref=arabroot.io) أن الفرق التي تصدر كثيراً لا تحتاج فروع إصدار أصلاً لهذا السبب. وهذه الطريقة تناسب تطبيقات الويب وخدمات SaaS التي فيها نسخة واحدة في الإنتاج.
- **فرع إصدار قصير Release Branch:** تفتح فرعاً مثل `release/1.1` من وسم الإصدار، فقط عندما تحتاج تثبيت إصدار بينما يستمر العمل على `main`، مثل تطبيق جوال ينتظر [مراجعة متجر التطبيقات](https://developer.apple.com/app-store/review/?ref=arabroot.io) أو منتج يثبت عند العملاء On-Premises ويدعم أكثر من إصدار. والقاعدة هنا كما في الموقع نفسه: أعد إنتاج الخطأ على الجذع، وأصلحه هناك مع اختبار، وانتظر نجاح ال CI، ثم انسخ الإصلاح إلى فرع الإصدار بالأمر `git cherry-pick`، والسبب أن الإصلاح الذي يكتب على فرع الإصدار أولاً قد ينسى نقله إلى الجذع، فيعود الخطأ إلى الإنتاج بعد أسابيع مع الإصدار التالي. وفرع الإصدار لا يدمج في الجذع أبداً، ويحذف بعد انتهاء دعم ذلك الإصدار.

وعند النسخ استخدم الخيار `-x`، والذي يضيف بحسب [توثيق git cherry-pick](https://git-scm.com/docs/git-cherry-pick?ref=arabroot.io) سطراً في رسالة ال commit يذكر ال commit الأصلي الذي نسخ منه، فيعرف من يقرأ تاريخ فرع الإصدار من أين جاء كل إصلاح. وتفاصيل Cherry-Pick نفسه وتعارضاته في [Git Merge وRebase وCherry-Pick](https://arabroot.io/articles/git-merge-%D9%88-rebase-%D9%88-cherry-pick/).

## Trunk-Based أم Git Flow أم GitHub Flow؟

هذه الطرق الثلاث هي الأكثر انتشاراً، وكل منها صمم لنوع مختلف من المنتجات، والصورة التالية تضعها جنباً إلى جنب:

![ثلاث بطاقات: Git Flow بفروع main وdevelop وhotfix وrelease وفرعين للميزات، وGitHub Flow بفرع main وفرعي ميزات بطلبات دمج يتبع كل دمج نشر، وTrunk-Based بفرع main وفروع قصيرة جداً عليها دروع ال CI وFeature Flags للميزات غير المكتملة، وتحت كل بطاقة عدد الفروع الدائمة وعمر فرع الميزة وطريقة الإصدار والمنتج المناسب](https://arabroot.io/content/images/2026/10/trunk-based-05-compare-1.webp)

الفرق الحقيقي بين الطرق الثلاث هو المدة التي يبقى فيها الكود خارج الفرع الرئيسي

### Git Flow

نشر Vincent Driessen نموذج [A successful Git branching model](https://nvie.com/posts/a-successful-git-branching-model/?ref=arabroot.io) في يناير 2010، وفيه فرعان دائمان هما `main` (كان اسمه `master`) و`develop`، وفروع مساعدة للميزات والإصدارات والإصلاحات العاجلة، وفروع الإصدار والإصلاح تدمج في الفرعين الدائمين معاً. وفي مارس 2020 أضاف الكاتب نفسه ملاحظة في أعلى المقال يذكر فيها أن النموذج صمم لبرمجيات لها أرقام إصدارات وتدعم أكثر من نسخة في الوقت نفسه، وأن الفريق الذي يعمل بالتسليم المستمر Continuous Delivery عليه أن يختار سير عمل أبسط بكثير مثل GitHub Flow بدل أن يجبر فريقه على Git Flow، وختم بأنه لا يوجد حل لكل شيء، فاعتبر سياقك وقرر بنفسك.

**نقاط القوة:** ترتيب واضح لمنتج يصدر بأرقام إصدارات ويدعم عدة إصدارات عند العملاء، ومكان واضح لكل نوع من العمل.

**نقاط الضعف:** فروع تعيش أسابيع ودمج مزدوج للإصلاحات، وفرع `develop` يصبح هو الذي يختبر بينما يصدر `main`، فيتكرر الاختبار، وكل ما رأيناه في المثال أعلاه.

### GitHub Flow

تصفه [صفحة GitHub Flow](https://docs.github.com/en/get-started/using-github/github-flow?ref=arabroot.io) بأنه سير عمل خفيف يعتمد على الفروع، وخطواته: أنشئ فرعاً، ثم اعمل تغييراتك، ثم افتح Pull Request، ثم عالج ملاحظات المراجعة، ثم ادمج، ثم احذف الفرع. ويوجد فيه فرع دائم واحد هو `main`، ولكنه لا يحدد عمر الفرع، فقد يبقى طلب الدمج مفتوحاً أسبوعين.

**نقاط القوة:** بسيط ويفهمه أي مطور في يوم واحد، ومناسب لتطبيقات الويب التي تنشر بعد كل دمج.

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

### Trunk-Based Development

هو في الواقع GitHub Flow مع قاعدتين إضافيتين: الفرع يعيش ساعات أو يوماً أو يومين، والعمل غير المكتمل يدمج مخفياً خلف Feature Flag، مع فروع إصدار قصيرة عند الحاجة فقط.

**نقاط القوة:** تكامل يومي فلا توجد مفاجآت يوم الإصدار، وفريق QA يختبر ما سوف يصدر فعلاً، وكل إصلاح يكتب مرة واحدة.

**نقاط الضعف:** لا يعمل بدون CI سريع واختبارات موثوقة، ويحتاج انضباطاً في تقسيم العمل، وال Flags نفسها تتحول إلى دين تقني Flag Debt إذا لم تحذف.

### الفرق في جدول واحد

|                   | Git Flow                                                | GitHub Flow                         | Trunk-Based                                                                                                            |
| ----------------- | ------------------------------------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| الفروع الدائمة    | main وdevelop                                           | main                                | main، وفرع إصدار قصير عند الحاجة                                                                                       |
| عمر فرع الميزة    | أيام إلى أسابيع                                         | حتى تنتهي المراجعة، بدون حد         | ساعات، ويومان على الأكثر                                                                                               |
| إيقاع الإصدار     | إصدارات مخططة عبر فرع release                           | نشر بعد كل دمج                      | أي commit نجح في ال CI، بوسم أو نشر مباشر                                                                              |
| العمل غير المكتمل | يبقى في فرعه                                            | يبقى في فرعه                        | يدمج مخفياً خلف Feature Flag                                                                                           |
| ما يحتاجه من CI   | مفيد، ويمكن العمل بدونه                                 | ضروري قبل كل دمج                    | ضروري وسريع على كل commit                                                                                              |
| الإصلاح العاجل    | فرع hotfix يدمج في main وdevelop                        | فرع عادي يدمج وينشر                 | يصلح على main، وينسخ إلى فرع الإصدار إن وجد                                                                            |
| يناسب             | منتجات تثبت عند العملاء On-Premises بعدة إصدارات مدعومة | تطبيقات ويب وخدمات SaaS بنسخة واحدة | تطبيقات ويب وSaaS، وتطبيقات الجوال مع فرع إصدار قصير لكل مراجعة متجر، ومنتجات On-Premises مع فرع إصدار لكل نسخة مدعومة |

وبهذا تكون الإجابة على سؤال «أي طريقة أفضل؟» أنها تتبع منتجك: إذا كان في الإنتاج نسخة واحدة تنشر كثيراً فاختر Trunk-Based أو GitHub Flow بعمر فرع محدد، وإذا كان تطبيق جوال فالطريقة نفسها مع فرع إصدار قصير لكل إصدار يرسل إلى المتجر، وإذا كنت تدعم عدة إصدارات عند العملاء فما زال لفروع الإصدار مكان، ولكن الإصلاحات تمر بالجذع أولاً دائماً.

## مثال عملي: مطوران وفروع قصيرة وFeature Flag وإصدار

الآن سوف نعيد قصة المتجر نفسها بطريقة Trunk-Based، بمطورين Alice وBob ومستودع مشترك، ونقوم بتشغيل كل شيء في Container من Alpine 3.24 مع Git 2.54.0\. وحتى يبقى المثال في Git والسكربتات فقط، استخدمنا مستودعاً مجرداً Bare Repository باسم `origin.git` مكان سيرفر Gitea، وسكربتاً اسمه `ci.sh` يقوم بما يقوم به ال CI، وفي فريق حقيقي يعمل هذا السكربت في [Gitea Actions](https://arabroot.io/articles/gitea-actions-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-ci-cd-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85%D9%83/) مع كل Pull Request، والدمج يتم بزر Merge بعد الموافقة ونجاح الفحص.

### نقطة البداية على main

الملف `checkout.sh` يطبع المجموع، ويقرأ ال Flags من `flags.env`:

```bash
#!/bin/sh
# Usage: ./checkout.sh <amount> [coupon]
. ./flags.env
amount="${1:?amount is required}"
echo "total: $amount"
```

والملف `flags.env` يجعل كل Flag مطفأ إلا إذا فعلته البيئة Environment بمتغير يحمل الاسم نفسه:

```bash
# Feature flags: off by default, each environment can turn one on
: "${FEATURE_COUPONS:=off}"
```

والاختبار `tests/total.sh`، ثم سكربت ال CI الذي يفحص صياغة السكربتات ويشغل كل الاختبارات:

```bash
[ "$(./checkout.sh 100)" = "total: 100" ] || { echo "FAIL tests/total.sh"; exit 1; }
```

```bash
#!/bin/sh
# The same checks the CI server runs on every push and pull request
set -e
for f in checkout.sh lib/*.sh; do if [ -f "$f" ]; then sh -n "$f"; fi; done
for t in tests/*.sh; do sh "$t"; done
echo "CI: all checks passed"
```

### اليوم الأول: فرعان قصيران يدمجان في اليوم نفسه

تبدأ Alice بمحرك الكوبونات، ولا تربطه بصفحة الدفع بعد، فهو كود جديد لا يستدعيه أحد، وهذا هو أسلوب Keystone Interface الذي ذكرناه:

```bash
git switch -c feat/coupon-engine
mkdir -p lib
cat > lib/coupons.sh <<'EOF'
# apply_coupon <amount> <code>: prints the amount after the coupon
apply_coupon() {
  case "$2" in
    SAVE10) awk -v a="$1" 'BEGIN { print a * 0.9 }' ;;
    *) echo "$1" ;;
  esac
}
EOF
cat > tests/coupons.sh <<'EOF'
. ./lib/coupons.sh
[ "$(apply_coupon 100 SAVE10)" = "90" ] || { echo "FAIL tests/coupons.sh"; exit 1; }
EOF
git add . && git commit -m "Add coupon engine (not used yet)"
./ci.sh
git push -u origin feat/coupon-engine
```

والمخرج سوف يكون كما يلي:

```text
Switched to a new branch 'feat/coupon-engine'
CI: all checks passed
To /lab/after/origin.git
 * [new branch]      feat/coupon-engine -> feat/coupon-engine
branch 'feat/coupon-engine' set up to track 'origin/feat/coupon-engine'.
```

وفي الوقت نفسه يفتح Bob الفرع `feat/vat` من `main` نفسه ويضيف الضريبة إلى `checkout.sh`، ويحدث الاختبار ليتوقع `total: 115`:

```bash
total=$(awk -v a="$amount" 'BEGIN { printf "%.0f", a * 1.15 }')
echo "total: $total"
```

يراجع Bob طلب Alice ويوافق عليه فيدمج، وقبل أن يدمج طلبه هو يحدث فرعه على آخر `main`، لأن حماية الفرع تمنع دمج طلب متأخر عنه:

```bash
git fetch
git rebase origin/main
```

```text
From /lab/after/origin
   0f794e9..7087665  main       -> origin/main
Rebasing (1/1)Successfully rebased and updated refs/heads/feat/vat.
```

لاحظ أن فرع Bob عاش ساعات، وأن ما تغير على `main` خلالها هو commit واحد من Alice في ملفات أخرى، لذلك مرت إعادة الأساس دون أي تعارض، ثم نجح ال CI ودمج الطلب وحذف الفرع.

### اليوم الثاني: ربط الميزة خلف ال Flag ووسم الإصدار

تربط Alice الكوبونات بصفحة الدفع، ولكن خلف ال Flag `FEATURE_COUPONS`، فيصبح `checkout.sh` كما يلي:

```bash
#!/bin/sh
# Usage: ./checkout.sh <amount> [coupon]
. ./flags.env
amount="${1:?amount is required}"
if [ "$FEATURE_COUPONS" = "on" ] && [ -n "$2" ]; then
  . ./lib/coupons.sh
  amount=$(apply_coupon "$amount" "$2")
fi
total=$(awk -v a="$amount" 'BEGIN { printf "%.0f", a * 1.15 }')
echo "total: $total"
```

ونقوم الآن بتجربة الحالتين، مرة بالإعداد الافتراضي، ومرة بتفعيل ال Flag كما ستفعل بيئة الاختبار:

```bash
./checkout.sh 100 SAVE10
env FEATURE_COUPONS=on ./checkout.sh 100 SAVE10
```

```text
total: 115
total: 103
```

في المخرج أعلاه لاحظ التالي:

- عندما يكون ال Flag مطفأ يتجاهل التطبيق الكوبون تماماً، فهذا الكود يمكن أن يدمج وينشر في الإنتاج اليوم دون أن يتغير شيء على العملاء.
- عندما يكون ال Flag مفعلاً يطبق الخصم ثم الضريبة، وفريق QA يستطيع أن يختبر الميزة على النسخة نفسها التي سوف تصدر، لا على فرع منفصل.
- الرقم `103` بدل `103.50` علامة على خطأ في تقريب الضريبة لم ينتبه له أحد بعد، وسوف يظهر في اليوم الثالث.

بعد دمج الطلب يضع الفريق وسم الإصدار على `main`، ولأن التطبيق في مثالنا يرسل إلى متجر يراجع كل إصدار، يفتحون فرع إصدار قصيراً من الوسم نفسه:

```bash
git tag -a v1.1.0 -m "Release 1.1.0"
git push origin v1.1.0
git switch -c release/1.1 v1.1.0
git push -u origin release/1.1
```

```text
Switched to a new branch 'release/1.1'
```

### اليوم الثالث: خطأ في الإصدار، والإصلاح يمر بالجذع أولاً

يستمر العمل على `main`، فتضيف Alice كوبوناً ثانياً `SAVE20` في فرع قصير ويدمج، ثم يبلغ أحد المستخدمين أن مبلغاً مثل 19.99 يظهر بعد الضريبة 23 بدل 22.99:

```bash
./checkout.sh 19.99
```

```text
total: 23
```

يفتح Bob فرع الإصلاح من `main`، لا من فرع الإصدار، ويغير `%.0f` إلى `%.2f`، ويضيف اختباراً للحالة التي كشفت الخطأ:

```bash
git switch -c fix/vat-rounding
sed -i "s/%.0f/%.2f/" checkout.sh
sed -i 's/total: 115"/total: 115.00"/' tests/total.sh
echo '[ "$(./checkout.sh 19.99)" = "total: 22.99" ] || { echo "FAIL tests/total.sh rounding"; exit 1; }' >> tests/total.sh
git commit -am "Fix VAT rounding: keep two decimals"
./ci.sh
```

```text
Switched to a new branch 'fix/vat-rounding'
CI: all checks passed
```

وقبل أن ننسخ الإصلاح، لنكسر الأمر عمداً لنفهم قيمة الاختبار: إذا أضفنا اختبار التقريب وحده إلى نسخة منفصلة من المستودع على الوسم `v1.1.0` وشغلنا ال CI عليها فإنه يفشل كما يلي، وهذا ما يمنع إصدار `v1.1.1` لو نسي أحد نسخ الإصلاح:

```bash
git switch --detach v1.1.0
echo '[ "$(./checkout.sh 19.99)" = "total: 22.99" ] || { echo "FAIL tests/total.sh rounding"; exit 1; }' >> tests/total.sh
./ci.sh; echo "exit=$?"
```

```text
FAIL tests/total.sh rounding
exit=1
```

بعد مراجعة الطلب ودمجه في `main`، ينسخ Bob ال commit نفسه إلى فرع الإصدار بالخيار `-x`، ثم يشغل ال CI ويضع الوسم:

```bash
git log --oneline -1 32eaa41
git switch release/1.1
git cherry-pick -x 32eaa41
./ci.sh
./checkout.sh 19.99
git tag -a v1.1.1 -m "Release 1.1.1"
git push origin release/1.1 v1.1.1
```

```text
32eaa41 Fix VAT rounding: keep two decimals
branch 'release/1.1' set up to track 'origin/release/1.1'.
Switched to a new branch 'release/1.1'
[release/1.1 995a7ec] Fix VAT rounding: keep two decimals
 Date: Mon Oct 5 22:19:38 2026 +0000
 2 files changed, 3 insertions(+), 2 deletions(-)
CI: all checks passed
total: 22.99
```

ورسالة ال commit على فرع الإصدار، بالأمر `git log -1 --format=%B`، تحمل مصدرها:

```text
Fix VAT rounding: keep two decimals

(cherry picked from commit 32eaa417d2071f096934e9b8663450ac7be86a38)
```

لاحظ أن فرع الإصدار أخذ الإصلاح وحده، ولم يأخذ الكوبون `SAVE20` الذي دمج على `main` بعد الإصدار، وهذا هو سبب وجود فرع الإصدار أصلاً، وأن الإصلاح مكتوب مرة واحدة على `main`، فالإصدار القادم من `main` يحمله تلقائياً دون أي دمج مزدوج.

### شكل التاريخ بعد ثلاثة أيام

والآن نقارن بين الشكلين، فهذا هو تاريخ المستودع بالأمر `git log --graph --oneline --decorate --all`:

```text
* 995a7ec (HEAD -> release/1.1, tag: v1.1.1, origin/release/1.1) Fix VAT rounding: keep two decimals
| *   e305f5f (origin/main, origin/HEAD, main) Merge pull request 'Fix VAT rounding' (fix/vat-rounding)
| |\
| | * 32eaa41 Fix VAT rounding: keep two decimals
| |/
| * 46d8d97 Merge pull request 'Add SAVE20' (feat/coupon-codes)
|/|
| * f102c34 Add SAVE20 coupon code
|/
*   3a480af (tag: v1.1.0) Merge pull request 'Coupons in checkout behind flag' (feat/coupon-ui)
|\
| * 040e238 Use coupons in checkout behind FEATURE_COUPONS
|/
*   e55ebe8 Merge pull request 'Add VAT' (feat/vat)
|\
| * d076121 Add 15% VAT to the total
|/
*   7087665 Merge pull request 'Add coupon engine' (feat/coupon-engine)
|\
| * e05e74d Add coupon engine (not used yet)
|/
* 0f794e9 (tag: v1.0.0) Initial checkout with CI and flags
```

والفروع الموجودة على السيرفر بعد كل ذلك، بالأمر `git branch -r`:

```text
  origin/HEAD -> origin/main
  origin/main
  origin/release/1.1
```

في المخرج أعلاه لاحظ التالي:

- كل فرع ميزة يخرج من `main` ويعود إليه بعد commit أو اثنين، فلا توجد خطوط متوازية تمتد أسابيع كما في التاريخ الأول.
- على السيرفر فرعان نشطان فقط، وهذا داخل حد الفروع الثلاثة الذي تذكره DORA، لأن كل فرع ميزة حذف بعد دمجه.
- كل تغيير جديد على `main` مر بال CI، وأي commit منها يصلح أن يوسم ويصدر.
- بعد أن يكتمل إطلاق الكوبونات ويفعل ال Flag للجميع، تكون الخطوة التالية طلب دمج يحذف ال Flag والمسار القديم.

وفي آخر التجربة فعلنا ال Flag على `main` مع الكوبون الجديد للتأكد من أن الميزتين والإصلاح يعملون معاً:

```bash
env FEATURE_COUPONS=on ./checkout.sh 100 SAVE20
```

```text
total: 92.00
```

### وأين يختبر فريق QA؟

في هذه الطريقة يختبر فريق QA ما على `main`، لأنه هو ما سوف يصدر، فكل دمج ينشر تلقائياً إلى بيئة dev أو staging، والميزة غير المكتملة تفعل بال Flag في تلك البيئة وحدها. وإذا أراد الفريق أن يختبر طلب دمج بعينه قبل دمجه فالحل بيئة معاينة Preview Environment تنشأ لكل Pull Request ثم تحذف، ونقل الإصدار نفسه بين dev وstaging وprod يتم بطريقة GitOps، وكل ذلك مشروح بالتفصيل في [البيئات ومعاينة الميزات وGitOps للفرق](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D9%88%D9%85%D8%B9%D8%A7%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D9%88-gitops-%D9%84%D9%84%D9%81%D8%B1%D9%82/)، فلا تقم بإنشاء فرع دائم لكل بيئة مثل `staging` و`qa`، والسبب أن كل فرع من هذه الفروع يعيد مشكلة الفروع الطويلة نفسها.

## كيف تنقل فريقك من Git Flow خطوة بخطوة

لا تقم بتغيير طريقة العمل كلها في يوم واحد، فالفريق الذي ليس لديه CI موثوق سوف يكسر `main` في الأسبوع الأول ويعود إلى الطريقة القديمة وهو مقتنع أن الجديدة لا تعمل. والترتيب التالي يبني كل خطوة على التي قبلها:

1. **قس الوضع الحالي:** كم فرعاً نشطاً في المستودع، وكم يوماً يعيش فرع الميزة في المتوسط، وكم ساعة يستغرق دمج الإصدار، فهذه الأرقام من مستودعك أنت هي ما سوف تقارن به بعد شهر.
2. **اجعل ال CI سريعاً وموثوقاً:** كل Pull Request يشغل الاختبارات والبناء، والاختبارات المتقلبة Flaky Tests تصلح أو تحذف، لأن الفريق الذي يتجاهل الفشل الأحمر مرة سوف يتجاهله دائماً.
3. **فعل حماية الفرع:** لا يوجد دفع مباشر، ومراجعة واحدة، وفحص الحالة إلزامي، كما في قسم حماية الفرع أعلاه.
4. **حدد عمر الفرع:** اتفق مع الفريق على أن فرع الميزة لا يتجاوز يومين، والعمل الأكبر يقسم إلى طلبات دمج صغيرة، وابدأ بالميزات الجديدة ولا تجبر الفروع القديمة المفتوحة على هذه القاعدة.
5. **أضف ال Feature Flags:** ابدأ بمتغيرات بيئة كما في المثال، وضع قاعدة أن كل ميزة لن تكتمل في يومين تبدأ خلف Flag مطفأ.
6. **أغلق فرع develop:** بعد أن تدمج الفروع القديمة، ادمج `develop` في `main` مرة أخيرة، واجعل `main` هو الفرع الافتراضي Default Branch في Gitea وهدف كل طلب دمج، ثم احذف `develop`.
7. **غير طريقة الإصدار والإصلاح:** الإصدار وسم على `main`، وفرع الإصدار يفتح فقط عند الحاجة، والإصلاح العاجل يكتب على `main` وينسخ بال cherry-pick، فلا توجد فروع hotfix تدمج مرتين.

### الجانب الثقافي: الأداة سهلة، والعادة هي الصعبة

- **وقت متفق عليه للمراجعة Review SLA:** طلب الدمج الصغير الذي ينتظر يومين يجعل صاحبه يبدأ عملاً جديداً فوقه، فيكبر الفرع، لذلك اتفقوا على مدة قصيرة ومكتوبة للرد على كل طلب، مثل نصف يوم عمل، وعلى أن مراجعة طلبات الزملاء تسبق البدء في عمل جديد.
- **البرمجة الثنائية Pair Programming:** عندما يكتب مطوران الكود معاً فالمراجعة تحدث أثناء الكتابة، وهي الطريقة الأسرع للمراجعة المتزامنة التي توصي بها DORA.
- **البناء الأحمر مسؤولية الجميع:** إذا فشل ال CI على `main` فإصلاحه يسبق أي عمل آخر، ومن دمج التغيير الذي كسره يتولى الأمر، والحل الأسرع غالباً هو التراجع عن ال commit بالأمر `git revert` ثم إصلاحه بهدوء في طلب جديد، والسبب أن `main` المكسور يوقف الفريق كله عن الدمج.
- **الطلب الصغير ليس عملاً ناقصاً:** بعض المطورين يرون أن رفع جزء من الميزة يعني أنها لم تكتمل، فوضح للفريق أن الجزء المدمج خلف Flag هو تقدم حقيقي يراه الجميع ويختبرونه.

## أخطاء شائعة

🛑

تسمية الطريقة Trunk-Based بينما لا يوجد CI يعمل على كل طلب دمج هي أخطر هذه الأخطاء، لأنك أزلت الحماية التي كانت توفرها الفروع المنفصلة ولم تضع مكانها الحماية التي تعتمد عليها هذه الطريقة، فيصبح كل دمج في `main` رهاناً على أن لا شيء انكسر.

- **Flags لا تحذف أبداً Flag Debt:** كل Flag يضاعف المسارات التي يجب أن تختبر، ويذكر [trunkbaseddevelopment.com](https://trunkbaseddevelopment.com/feature-flags/?ref=arabroot.io) أن ال Flags تنسى في الكود عندما ينتقل الفريق إلى أعمال جديدة، ويقترح أن يكتب مع كل Flag موعد لمراجعته وحذفه، ويصف Pete Hodgson الفرق المتمرسة بأنها تعامل ال Feature Toggles كمخزون له تكلفة وتحاول أن تبقيه في أقل حد. لذلك افتح مهمة حذف ال Flag في اليوم الذي تنشئه فيه.
- **طلبات دمج ضخمة:** فرع يعيش يوماً واحداً ولكنه يغير أربعين ملفاً هو فرع طويل في حجمه، والمراجعة عليه شكلية، فقسم العمل حسب ما يمكن مراجعته في دقائق، لا حسب ما يمكن كتابته في يوم.
- **الدفع المباشر دون مراجعة في الفرق الخاضعة للتنظيم:** الدمج المباشر في الجذع مناسب لفريق صغير جداً، أما الفرق التي تخضع لمتطلبات تدقيق Audit أو فصل الصلاحيات Separation of Duties فتحتاج دليلاً على أن شخصاً آخر راجع كل تغيير، لذلك استخدم الفروع القصيرة مع الموافقة الإلزامية وحماية الفرع حتى للمدراء، فالسجل في Gitea يصبح هو الدليل.
- **فرع إصدار يتحول إلى develop جديد:** إذا بدأ الفريق يكتب ميزات على `release/1.1` أو يبقيه مفتوحاً أشهراً دون حاجة فقد عاد إلى المشكلة الأولى، ففرع الإصدار يأخذ إصلاحات منسوخة من `main` فقط.
- **اختبار مسار واحد لل Flag:** إذا شغل ال CI الاختبارات وال Flag مطفأ فقط، فالمسار الجديد يصل إلى الإنتاج دون اختبار، ويوصي [الموقع نفسه](https://trunkbaseddevelopment.com/feature-flags/?ref=arabroot.io) بأن يغطي ال CI الحالات المتوقعة من ال Flags، أي ال Flag مطفأ ومفعل على الأقل.

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- مشكلة الفروع الطويلة ليست في Git، وإنما في الكود الذي يبقى غير مدمج أسابيع، فتظهر التعارضات وأخطاء التكامل في يوم الإصدار، ويختبر QA فرعاً لن يصدر، وتطبق الإصلاحات مرتين.
- Trunk-Based Development يعني أن كل مطور يدمج عمله في `main` مرة في اليوم على الأقل، عبر فروع تعيش ساعات أو يومين على الأكثر، و`main` صالح للإصدار دائماً، وتربط أبحاث DORA الأداء الأعلى بثلاثة فروع نشطة أو أقل ودمج يومي وغياب تجميد الكود.
- الطريقة تعمل بشروطها: CI سريع على كل طلب دمج، وحماية الفرع، وطلبات صغيرة ومراجعة سريعة، وFeature Flags تخفي العمل غير المكتمل، وBranch by Abstraction للتغييرات الكبيرة.
- الإصدار وسم على `main`، وفرع الإصدار القصير يفتح فقط لتطبيقات الجوال والمنتجات متعددة الإصدارات، والإصلاح يكتب على `main` أولاً ثم ينسخ بالأمر `git cherry-pick -x`.
- Git Flow ما زال مناسباً لبرمجيات تدعم عدة إصدارات عند العملاء، وكاتبه نفسه يوصي فرق التسليم المستمر بطريقة أبسط، وGitHub Flow يصبح Trunk-Based عندما تحدد عمر الفرع وتضيف ال Feature Flags.
- انتقل تدريجياً: CI وحماية الفرع أولاً، ثم حد لعمر الفرع وال Feature Flags، ثم إغلاق `develop`، واحذف كل Flag بعد اكتمال إطلاقه.

[![](https://arabroot.io/content/images/2026/10/environments-gitops-cover-cover.webp)دليلمن الفرع إلى الإنتاج: البيئات وGitOps للفرقكيف يختبر فريقك كل ميزة وحدها ويعرضها على العميل دون أن تصل ميزة غير جاهزة إلى الإنتاج.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A7%D8%AA-%D9%88%D9%85%D8%B9%D8%A7%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D9%88-gitops-%D9%84%D9%84%D9%81%D8%B1%D9%82/)[![](https://arabroot.io/content/images/2026/10/git-workflows-cover-cover.webp)دليلGit في العمل اليومي: الدمج والتراجع دون أن تخسر عملكمتى تستخدم Merge ومتى تستخدم Rebase، وكيف تنقل إصلاحاً إلى فرع الإصدار أو تسترجع عملاً ظننت أنه ضاع.](https://arabroot.io/articles/git-merge-%D9%88-rebase-%D9%88-cherry-pick/)[![](https://arabroot.io/content/images/2026/10/git-basics-cover-cover.webp)دليلإدارة النسخ وGit للمبتدئين: لماذا نحتاجها وكيف تعمللماذا تضيع الملفات بأسماء final\_v2 ويمسح الزملاء عمل بعضهم، وكيف يحل Git ذلك على مشروع حقيقي من أول commit.](https://arabroot.io/articles/%D8%A7%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D9%86%D8%B3%D8%AE-%D9%88-git-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/gitea-actions-cover-cover-2.webp)دليلGitea Actions: تشغيل CI/CD وبناء Docker Images على سيرفركاجعل سيرفرك يختبر الكود ويبني Docker Images تلقائياً مع كل push، دون أن يخرج كودك أو أسرارك إلى خدمة CI خارجية.](https://arabroot.io/articles/gitea-actions-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-ci-cd-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85%D9%83/)[![](https://arabroot.io/content/images/2026/10/dev-checklist-cover-cover.webp)دليلقبل أن تنشر تطبيقك على خادمك: قائمة تحقق للمطورالأخطاء التي لا تظهر إلا بعد نشر تطبيقك في Docker خلف Reverse Proxy، وكيف تعالج كل واحد منها قبل أن يصل إلى المستخدمين.](https://arabroot.io/articles/%D9%82%D8%A7%D8%A6%D9%85%D8%A9-%D8%AA%D8%AD%D9%82%D9%82-%D8%A7%D9%84%D9%85%D8%B7%D9%88%D8%B1-%D9%82%D8%A8%D9%84-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%87/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، وتنفيذ المثالين (الفروع الطويلة وTrunk-Based بمطورين وFeature Flag وفرع إصدار وCherry-Pick) في Container من Alpine 3.24.2 مع Git 2.54.0.