> ## 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.

# إدارة النسخ وGit للمبتدئين: لماذا نحتاجها وكيف تعمل
- URL: https://arabroot.io/articles/ادارة-النسخ-و-git-للمبتدئين/
- Published: 2026-10-05T13:15:00.000Z
- Updated: 2026-10-09T09:42:17.000Z
- Description: لماذا تضيع الملفات بأسماء final_v2 ويمسح الزملاء عمل بعضهم، وكيف يحل Git ذلك على مشروع حقيقي من أول commit.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, DevOps والتشغيل, DevOps وCI/CD, الاستضافة الذاتية

كل من عمل على مشروع لأكثر من أسبوع يعرف هذا المشهد: مجلد فيه `report_v1.docx` و`report_v2.docx` و`report_final.docx` ثم `report_final_v2_really_final.docx`، ولا أحد في الفريق يعرف أي ملف هو الأخير فعلاً. وفي الكود يصبح المشهد أسوأ، فتعدل أنت ملفاً وزميلك يعدل الملف نفسه في الوقت نفسه، ثم يحفظ نسخته فوق نسختك فيضيع عملك دون أن يعلم أحد، أو يتوقف التطبيق عن العمل فتسأل «كان يعمل أمس، ما الذي تغير؟» ولا تجد من يجيبك، لأنه لا يوجد سجل يقول من غير ماذا ومتى ولماذا.

والحل الذي يستخدمه كل فريق برمجي اليوم هو **نظام إدارة النسخ Version Control System**، واختصاراً VCS، وأشهرها وأكثرها انتشاراً هو Git. وقد شرحنا في دليل [تثبيت Gitea على خادمك](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-gitea-%D9%85%D8%B9-docker-compose/) كيف تشغل سيرفر Git خاصاً بفريقك، أما هذا المقال فيبدأ من قبل ذلك بخطوة، أي من المشكلة نفسها ومن طريقة تفكير Git، حتى تفهم ما الذي يحدث عندما تكتب كل أمر، وسوف نناقش في هذا المقال ما يلي:

- المشكلات التي تحلها إدارة النسخ، والحلول الأولى التي يبدأ بها أغلبنا ولماذا تفشل.
- أنواع أنظمة إدارة النسخ وتاريخها: المحلية Local والمركزية Centralized والموزعة Distributed، وكيف ولد Git في 2005، ومتى تحتاج أدوات أخرى للملفات الكبيرة.
- نموذج Git خطوة بخطوة على مشروع حقيقي: المستودع Repository، ومجلد العمل Working Directory، ومنطقة التجهيز Staging Area، وال Commit، وHEAD، والفروع Branches، والوسوم Tags، والمستودع البعيد Remote، مع المخرج الحقيقي لكل أمر.
- التعارض Merge Conflict: لماذا يحدث وكيف تحله.
- التراجع عن الأخطاء بأمان، والعمل مع فريق: منصات الاستضافة وطلبات الدمج Pull Requests ورسائل ال Commit والأسرار التي لا يجب أن تدخل المستودع.
- أشهر أخطاء المبتدئين، وجدول بأكثر 15 أمراً ستستخدمها.

أما الدمج بطريقة Rebase، ونقل Commit واحد بين الفروع Cherry-pick، والأوامر reset وrevert بالتفصيل، واستراتيجيات الفروع في الفرق، فلها مقال مستقل هو [Git في العمل اليومي: Merge أم Rebase، وCherry-pick وRevert](https://arabroot.io/articles/git-merge-%D9%88-rebase-%D9%88-cherry-pick/)، وهو المقال التالي بعد هذا.

## ما المشكلة التي تحلها إدارة النسخ؟

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

- **أي نسخة هي الأخيرة؟** كل تعديل مهم يتحول إلى ملف جديد باسم جديد، `final` ثم `final_v2` ثم `really_final`، وبعد شهر لا أحد يعرف الفرق بين هذه الملفات ولا أيها أرسل للعميل.
- **آخر من يحفظ يكسب:** عندما يفتح شخصان الملف نفسه من مجلد مشترك ويعدل كل منهما جزءاً مختلفاً، فإن من يحفظ ثانياً يكتب نسخته فوق نسخة الأول، فيضيع تعديل الأول بالكامل دون أي تنبيه.
- **«كان يعمل أمس»:** التطبيق توقف اليوم، ولا توجد طريقة لتعرف ما الذي تغير بين الأمس واليوم، ولا طريقة لترجع إلى حالة الأمس بالضبط.
- **من غير هذا السطر ولماذا؟** تجد سطراً غريباً في الكود ولا تعرف من كتبه ولا السبب، فلا تجرؤ على حذفه.
- **لا يوجد مكان آمن للتجربة:** تريد أن تجرب فكرة قد تكسر المشروع، فتنسخ المجلد كله إلى `project-test`، ثم تنسى أي التعديلات نقلتها إلى الأصل وأيها لم تنقلها.

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

![مخطط يقارن مجلداً مشتركاً فيه ملفات report_v1 وreport_v2 وreport_final وreport_final_v2_really_final، وشخصين يحفظان app.py فيضيع تعديل الأول، بالعمل نفسه في Git حيث يوجد ملف واحد وسجل Commits لكل تعديل باسم صاحبه، ويصل التعديلان إلى المستودع المشترك](https://arabroot.io/content/images/2026/10/git-basics-01-problem.webp)

دون إدارة النسخ: نسخ في كل مكان وآخر من يحفظ يكسب. ومع Git: ملف واحد وسجل كامل ولا يضيع شيء

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

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

1. **نسخ المجلد مع التاريخ في الاسم:** مثل `project-2026-10-01`، وهو أبسط الحلول، و[كتاب Pro Git نفسه](https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control?ref=arabroot.io) يذكر أنه الحل الأكثر انتشاراً لبساطته، ولكنه معرض للخطأ بدرجة كبيرة، فمن السهل أن تنسى في أي مجلد أنت فتعدل الملف الخطأ أو تنسخ فوق ملف لم تقصده، ولا يوجد فيه أي وصف لما تغير ولا لماذا، ويأكل المساحة لأن كل نسخة هي المشروع كاملاً.
2. **مجلد مشترك على الشبكة أو في التخزين السحابي:** وهو يحل مشكلة «أين الملف»، ولكنه لا يحل مشكلة «آخر من يحفظ يكسب»، بل يجعلها أسهل حدوثاً، وأغلب خدمات المزامنة تحفظ نسخاً سابقة للملف الواحد ولكنها لا تعرف أن عشرة ملفات تغيرت معاً لأجل ميزة واحدة.
3. **إرسال الملفات بالبريد أو في المحادثة:** وهذه أسوأ الطرق على الإطلاق، فكل شخص يملك نسخة مختلفة، ودمج التعديلات يدوي، وسطر ينسى نقله يعني خللاً لا يكتشفه أحد إلا بعد النشر.

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

## من نسخ المجلدات إلى Git: أنواع أنظمة إدارة النسخ

أنظمة إدارة النسخ مرت بثلاثة أجيال، وكل جيل جاء ليحل مشكلة في الجيل الذي قبله، وفهم هذا التسلسل يفسر لك كثيراً من قرارات Git التي قد تبدو غريبة في البداية.

### الجيل الأول: الأنظمة المحلية Local VCS

أشهر مثال لها هو RCS، واختصاراً Revision Control System، الذي [طوره Walter F. Tichy في جامعة Purdue في بداية الثمانينيات](https://www.gnu.org/software/rcs/?ref=arabroot.io) ونشر ورقته عنه في 1985\. وفكرته أن يحفظ لكل ملف قاعدة بيانات صغيرة على الجهاز نفسه فيها الفروقات Patches بين كل نسخة والتي تليها، فيستطيع أن يعيد بناء الملف كما كان في أي لحظة. وهو يحل مشكلة «كان يعمل أمس» لشخص واحد، ولكن كل شيء يبقى على جهاز واحد، ويعمل على مستوى الملف المنفرد لا المشروع كاملاً، فلا يناسب فريقاً.

### الجيل الثاني: الأنظمة المركزية Centralized VCS

لكي يعمل الفريق معاً ظهرت الأنظمة المركزية، وفكرتها أن يوجد سيرفر واحد يحمل تاريخ المشروع كاملاً، وكل مطور يسحب منه آخر نسخة من الملفات إلى جهازه، يعدل عليها، ثم يرسل تعديله إلى السيرفر. وأشهرها CVS الذي [بدأ في يوليو 1986 مجموعة سكربتات Shell كتبها Dick Grune، ثم صممه Brian Berliner وكتبه بلغة C في أبريل 1989](https://www.gnu.org/software/trans-coord/manual/cvs/html%5Fnode/What-is-CVS%5F003f.html?ref=arabroot.io)، ثم Subversion أو SVN الذي [أسسته شركة CollabNet في 2000](https://subversion.apache.org/?ref=arabroot.io) ليكون بديلاً أفضل من CVS، وفي الفئة نفسها برامج تجارية مثل Perforce.

وهنا ظهر سؤال مهم: ماذا يحدث عندما يعدل شخصان الملف نفسه؟ ووضع كتاب Subversion الرسمي [نموذجين للإجابة](https://svnbook.red-bean.com/en/1.7/svn.basic.version-control-basics.html?ref=arabroot.io):

- **نموذج القفل Lock-Modify-Unlock:** لا يعدل الملف إلا شخص واحد في كل مرة، فيقفله ثم يعدله ثم يفك القفل. وهذا يمنع التعارض، ولكنه يعطل الفريق، فإذا نسي زميلك أن يفك القفل ثم ذهب في إجازة بقي الملف مقفلاً حتى يتدخل المدير.
- **نموذج النسخ والدمج Copy-Modify-Merge:** كل شخص يأخذ نسخته ويعمل عليها بحرية، ثم يدمج النظام التعديلات، وعندما يتداخل تعديلان في المكان نفسه يطلب من الإنسان أن يقرر. وهذا هو النموذج الذي يعتمده Subversion أساساً، ويسمح بالقفل في الحالات التي لا يمكن فيها الدمج، مثل الملفات الثنائية Binary Files كالرسومات والصوت، وهو أيضاً النموذج الذي يعمل به Git.

والمشكلة الرئيسية في النموذج المركزي أن السيرفر نقطة فشل واحدة Single Point of Failure، و[كما يشرح كتاب Pro Git](https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control?ref=arabroot.io) فإذا توقف السيرفر ساعة فلا أحد يستطيع أن يحفظ تعديلاته في التاريخ خلال هذه الساعة، وإذا تلف قرصه دون نسخة احتياطية ضاع تاريخ المشروع كله، ولم يبق إلا آخر نسخة من الملفات على أجهزة المطورين. وحتى عندما يعمل السيرفر فإن كل عملية تحتاج إلى الشبكة، فعرض السجل أو مقارنة نسختين قديمتين يمر عبر السيرفر.

### الجيل الثالث: الأنظمة الموزعة Distributed VCS وولادة Git

في الأنظمة الموزعة لا يأخذ المطور آخر نسخة من الملفات فقط، وإنما يأخذ نسخة كاملة من المستودع بكل تاريخه، فكل جهاز عليه المشروع هو نسخة احتياطية كاملة، وتستطيع أن تحفظ تعديلاتك وتعرض السجل وتقارن النسخ وأنت دون إنترنت، والسيرفر المشترك في هذا النموذج ليس إلا النسخة التي اتفق الفريق أن يتبادل التعديلات عبرها.

وقصة Git مرتبطة بنواة Linux التي تحدثنا عن بدايتها في [ما هي البرمجيات مفتوحة المصدر وتراخيصها](https://arabroot.io/articles/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-%D9%88%D8%AA%D8%B1%D8%A7%D8%AE%D9%8A%D8%B5%D9%87%D8%A7/#linux)، فبحسب [التاريخ المختصر في موقع Git الرسمي](https://git-scm.com/book/en/v2/Getting-Started-A-Short-History-of-Git?ref=arabroot.io) بدأ مشروع النواة في 2002 يستخدم نظاماً موزعاً تجارياً اسمه BitKeeper، وفي 2005 انهارت العلاقة بين مجتمع النواة والشركة التي تطوره وسحبت منهم الاستخدام المجاني، فقرر مجتمع Linux، وعلى رأسه لينوس تورفالدز Linus Torvalds مبتكر Linux، أن يبني أداته الخاصة من الدروس التي تعلمها مع BitKeeper. وكانت الأهداف السرعة، والتصميم البسيط، والدعم القوي للتطوير المتوازي بآلاف الفروع، وأن يكون موزعاً بالكامل، وأن يتحمل مشروعاً بحجم نواة Linux. وأول Commit في تاريخ Git نفسه [كتبه تورفالدز في 7 أبريل 2005](https://github.com/git/git/commit/e83c5163316f89bfbde7d9ab23ca2e25604af290?ref=arabroot.io) برسالة طريفة: «Initial revision of "git", the information manager from hell».

وفي العام نفسه ظهر Mercurial، وهو نظام موزع آخر بدأه Matt Mackall، وأقدم Commit في [مستودعه الرسمي](https://repo.mercurial-scm.org/hg/rev/0?ref=arabroot.io) مؤرخ في مايو 2005، ويستخدم الأوامر `hg clone` و`hg pull` و`hg push` بالفكرة نفسها. والصورة التالية تبين الفرق بين النموذجين، ففي المركزي يعيش التاريخ في السيرفر وحده، وفي الموزع يحمل كل جهاز المستودع كاملاً:

![مخطط يقارن النظام المركزي حيث يحمل السيرفر وحده التاريخ الكامل وكل جهاز عليه آخر نسخة من الملفات فقط، بالنظام الموزع حيث يحمل كل جهاز مستودعاً كاملاً بالتاريخ ويتبادل التعديلات مع السيرفر المشترك عبر push وpull](https://arabroot.io/content/images/2026/10/git-basics-02-central-distributed.webp)

المركزي: التاريخ في سيرفر واحد. والموزع: كل نسخة مستودع كامل ونسخة احتياطية

### ماذا عن الألعاب والملفات الكبيرة؟

Git ممتاز مع الملفات النصية مثل الكود والإعدادات، ولكنه يحفظ كل نسخة من كل ملف في التاريخ، وكل من يستنسخ المستودع يحمل التاريخ كاملاً، فإذا كان مشروعك فيه ملفات ثنائية كبيرة تتغير كثيراً، مثل رسومات الألعاب ومقاطع الفيديو والنماذج ثلاثية الأبعاد، فإن حجم المستودع يكبر بسرعة، ولا يمكن دمج تعديلين على ملف ثنائي كما ندمج سطور الكود. ولهذا تجد استوديوهات الألعاب تستخدم أنظمة مصممة لهذه الحالة، مثل [Perforce P4](https://www.perforce.com/solutions/game-development?ref=arabroot.io) الذي كان اسمه Helix Core، و[Unity Version Control](https://unity.com/solutions/version-control?ref=arabroot.io) الذي كان اسمه Plastic SCM، وكلاهما يدعم قفل الملفات الثنائية حتى لا يعدل شخصان الملف نفسه.

وإذا كان مشروعك في Git وفيه بعض الملفات الكبيرة فالحل الأنسب هو [Git LFS](https://git-lfs.com/?ref=arabroot.io)، واختصاراً Large File Storage، وهو إضافة مفتوحة المصدر تستبدل الملف الكبير داخل Git بملف نصي صغير يشير إليه Pointer، وتحفظ المحتوى الفعلي على سيرفر منفصل، فيبقى المستودع خفيفاً ولا ينزل المطور إلا نسخ الملفات التي يحتاجها.

### مقارنة الأنواع

والجدول التالي يجمع الأنواع في مكان واحد:

| النوع             | أمثلة                                      | أين يعيش التاريخ؟         | العمل دون شبكة                                     | نقاط القوة                                                | نقاط الضعف                                                          |
| ----------------- | ------------------------------------------ | ------------------------- | -------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------- |
| محلي Local        | RCS (بداية الثمانينيات)                    | على جهاز واحد، لكل ملف    | نعم                                                | بسيط، وأفضل من نسخ المجلدات                               | لا يناسب الفرق، ويتابع كل ملف منفرداً                               |
| مركزي Centralized | CVS (1986)، Subversion (2000)، Perforce P4 | على السيرفر المركزي فقط   | تعديل الملفات فقط، ولا يوجد حفظ في التاريخ ولا سجل | صلاحيات دقيقة لكل مجلد، وقفل للملفات الثنائية، وسهل الفهم | نقطة فشل واحدة، وكل عملية تحتاج إلى الشبكة                          |
| موزع Distributed  | Git (2005)، Mercurial (2005)               | على كل جهاز وعلى السيرفر  | نعم، كل شيء عدا التبادل مع الآخرين                 | سريع، وكل نسخة احتياطية كاملة، والفروع رخيصة              | الملفات الثنائية الكبيرة تحتاج Git LFS، والمفاهيم تحتاج وقتاً للفهم |
| للملفات الكبيرة   | Perforce P4، Unity Version Control         | على السيرفر، مع نسخ محلية | جزئياً                                             | يتحمل مستودعات ضخمة وقفل الملفات الثنائية                 | تجارية في الغالب، وأقل انتشاراً خارج الألعاب                        |

وقد يتساءل البعض: إذا كان Mercurial ظهر في السنة نفسها وبالفكرة نفسها، فلماذا نتعلم Git؟ والإجابة أن Git هو ما سوف تجده في كل مكان تقريباً، فمنصات الاستضافة التي سوف تتعامل معها مثل GitHub وGitLab وGitea وForgejo مبنية حوله، وأدوات CI/CD والنشر كلها تتوقعه، ولذلك فإن بقية المقال عن Git وحده.

## كيف يعمل Git؟ المفاهيم بالترتيب على مشروع حقيقي

أفضل طريقة لفهم Git هي أن ترى ما يحدث بعد كل أمر، ولذلك سوف نبني مشروعاً صغيراً اسمه `invoice-app`، وهو برنامج Python يحسب مجموع الفاتورة، ونمر عليه بكل مفهوم بالترتيب. وقد نفذنا كل الأوامر التالية داخل Container على Alpine 3.24 مع Git 2.54.0، وكل مخرج في هذا القسم هو المخرج الحقيقي كما ظهر، وتستطيع أن تنفذها على جهازك مباشرة إذا كان Git مثبتاً عليه، سواء كان Linux أو macOS أو Windows مع Git Bash.

### قبل أول أمر: من أنت؟

كل Commit في Git يحمل اسم صاحبه وبريده، ولذلك فإن أول ما تفعله بعد تثبيت Git هو أن تعرفه بنفسك، والمثال التالي يبين ذلك:

```bash
git config --global user.name "Alice"
git config --global user.email "alice@example.com"
git config --global init.defaultBranch main
```

في الإعداد أعلاه لاحظ التالي:

- الخيار `--global` يحفظ الإعداد لكل مستودعاتك في ملف `~/.gitconfig`، وتستطيع أن تغيره لمستودع واحد بنفس الأمر دون هذا الخيار، كما فعلنا لاحقاً مع المستخدم الثاني Bob.
- استخدم بريد العمل الحقيقي الذي تستخدمه في منصة الاستضافة، والسبب أن منصات مثل Gitea تربط ال Commits بحسابك عن طريق هذا البريد.
- الإعداد `init.defaultBranch` يجعل اسم الفرع الأول `main`، والسبب أن الاسم الافتراضي في Git 2 ما زال `master`، وقد أعلن مشروع Git في [قائمة التغييرات القادمة](https://git-scm.com/docs/BreakingChanges?ref=arabroot.io) أن الاسم الافتراضي في الإصدار Git 3.0 سوف يكون `main` ليوافق ما تستخدمه أغلب منصات الاستضافة.

### المستودع Repository: الأمر git init

المستودع Repository هو مجلد مشروعك ومعه قاعدة بيانات Git التي تحفظ التاريخ كاملاً، وتنشئه بالأمر `git init` داخل مجلد المشروع:

```bash
mkdir invoice-app && cd invoice-app
git init
ls -a
git status
```

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

```text
Initialized empty Git repository in /work/invoice-app/.git/

.
..
.git

On branch main

No commits yet

nothing to commit (create/copy files and use "git add" to track)
```

لاحظ أن الأمر لم يغير شيئاً في ملفاتك، وكل ما فعله أنه أنشأ مجلداً مخفياً اسمه `.git`، وهذا المجلد هو المستودع الفعلي، ففيه كل ال Commits والفروع والإعدادات، وإذا حذفته أصبح المجلد مجلداً عادياً دون تاريخ، لذلك لا تقم بتعديل شيء داخله يدوياً.

### المناطق الثلاث: مجلد العمل ومنطقة التجهيز والمستودع

هذه هي الفكرة الأهم في Git، وأغلب حيرة المبتدئين تأتي من عدم فهمها. فالتعديل في Git يمر بثلاث مناطق على جهازك قبل أن يصل إلى فريقك:

1. **مجلد العمل Working Directory:** الملفات التي تراها وتعدلها في المحرر، وأي تعديل هنا ليس في التاريخ بعد.
2. **منطقة التجهيز Staging Area**، ويسميها توثيق Git أيضاً الفهرس Index: المكان الذي تضع فيه التعديلات التي تريدها في ال Commit القادم، وتنقلها إليه بالأمر `git add`.
3. **المستودع المحلي Local Repository:** وهو المجلد `.git`، وتدخل إليه التعديلات المجهزة بالأمر `git commit`، وعندها تصبح جزءاً من التاريخ.

ثم تأتي منطقة رابعة خارج جهازك هي المستودع البعيد Remote، أي السيرفر الذي يتشارك فيه الفريق، وترسل إليه بالأمر `git push`، وسوف نصل إليه في آخر هذا القسم. والصورة التالية تبين المناطق الأربع والأوامر التي تنقل التعديل بينها في الاتجاهين:

![مخطط بأربع مناطق: مجلد العمل ثم منطقة التجهيز Index ثم المستودع المحلي .git ثم المستودع البعيد origin، مع الأوامر git add وgit commit وgit push للأمام، وgit restore --staged وgit switch وgit restore وgit fetch وgit pull للخلف](https://arabroot.io/content/images/2026/10/git-basics-03-three-areas.webp)

التعديل ينتقل بالأمر add إلى التجهيز، ثم بالأمر commit إلى التاريخ، ثم بالأمر push إلى الفريق

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

الآن سوف نقوم بإنشاء ملفين للمشروع، الأول `app.py`:

```python
def total(items):
    return sum(price for _, price in items)

print("Invoice total:", total([("pen", 5), ("book", 40)]))
```

والثاني `README.md`:

```text
# Invoice App
A tiny app to calculate invoice totals.
```

ثم نسأل Git عن حالة المشروع:

```bash
git status
```

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

```text
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	README.md
	app.py

nothing added to commit but untracked files present (use "git add" to track)
```

لاحظ أن Git يرى الملفين ولكنه يصفهما بأنهما غير متتبعين Untracked، أي أنه لا يحفظ تاريخهما حتى تطلب ذلك صراحة. والآن نضيفهما إلى منطقة التجهيز ثم نسأل مرة أخرى:

```bash
git add app.py README.md
git status
```

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

```text
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   README.md
	new file:   app.py
```

الملفان الآن في منطقة التجهيز تحت عنوان «Changes to be committed»، ولم يدخلا التاريخ بعد. والآن ننشئ أول Commit، والخيار `-m` يحمل رسالته:

```bash
git commit -m "Add invoice total calculation"
git log
```

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

```text
[main (root-commit) d9c83ff] Add invoice total calculation
 2 files changed, 6 insertions(+)
 create mode 100644 README.md
 create mode 100644 app.py

commit d9c83ffdc5f349297734d2cfa683574b41465406
Author: Alice <alice@example.com>
Date:   Mon Oct 5 22:08:18 2026 +0000

    Add invoice total calculation
```

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

- الكلمة `root-commit` تعني أن هذا أول Commit في المستودع، فليس له أب Parent، وكل Commit بعده سوف يشير إلى الذي قبله.
- السلسلة `d9c83ffdc5f3…` هي معرف ال Commit، وهي هاش Hash من 40 حرفاً بنظام Hex، وسوف نشرح من أين تأتي بعد قليل، و`d9c83ff` هو أول سبعة حروف منها، ويكفي غالباً لتمييز ال Commit.
- كل Commit يحمل الاسم والبريد والوقت والرسالة، وهذا بالضبط ما كان ينقصنا في مشكلة «من غير هذا ولماذا».

### ماذا تغير؟ الأمر git diff

لنفرض أننا أضفنا ضريبة القيمة المضافة 15% إلى المجموع، فعدلنا سطر `return` في `app.py`، والآن نريد أن نرى ما تغير قبل أن نحفظه:

```bash
git status --short
git diff
```

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

```text
 M app.py

diff --git a/app.py b/app.py
index ff6f3fb..8d1acee 100644
--- a/app.py
+++ b/app.py
@@ -1,4 +1,4 @@
 def total(items):
-    return sum(price for _, price in items)
+    return round(sum(price for _, price in items) * 1.15, 2)  # VAT 15%

 print("Invoice total:", total([("pen", 5), ("book", 40)]))
```

السطر الذي يبدأ بعلامة `-` هو السطر القديم الذي حذف، والذي يبدأ بعلامة `+` هو الجديد، والسطور دون علامة هي سياق Context حول التعديل، والحرف `M` في المخرج المختصر يعني Modified. والآن لاحظ ماذا يحدث بعد `git add`:

```bash
git add app.py
git diff
git diff --staged
git commit -m "Add 15% VAT to the total"
```

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

```text
diff --git a/app.py b/app.py
index ff6f3fb..8d1acee 100644
--- a/app.py
+++ b/app.py
@@ -1,4 +1,4 @@
 def total(items):
-    return sum(price for _, price in items)
+    return round(sum(price for _, price in items) * 1.15, 2)  # VAT 15%

 print("Invoice total:", total([("pen", 5), ("book", 40)]))

[main eec0df0] Add 15% VAT to the total
 1 file changed, 1 insertion(+), 1 deletion(-)
```

الأمر `git diff` وحده لم يطبع شيئاً، والسبب أنه يقارن مجلد العمل بمنطقة التجهيز، وبعد `git add` أصبحا متطابقين، أما `git diff --staged` فيقارن منطقة التجهيز بآخر Commit، أي أنه يريك ما سوف يدخل في ال Commit القادم بالضبط، لذلك اجعله عادة قبل كل `git commit`.

### ملفات لا تدخل المستودع: الملف .gitignore

في أي مشروع حقيقي توجد ملفات لا يجب أن تدخل المستودع أبداً، مثل ملف `.env` الذي فيه كلمات المرور، والملفات التي يولدها البرنامج أثناء التشغيل مثل `__pycache__` وملفات السجل. لنفرض أننا أنشأنا ملف `.env` فيه كلمة مرور قاعدة البيانات، ثم شغلنا البرنامج فأنشأ Python مجلد `__pycache__`:

```bash
git status --short
```

```text
?? .env
?? __pycache__/
```

كلاهما غير متتبع، وهنا الخطر، فأمر واحد مثل `git add .` يضيف كل ما في المجلد، ومنه ملف الأسرار. والحل أن تنشئ ملفاً اسمه `.gitignore` في مجلد المشروع، وفيه الأنماط التي يتجاهلها Git:

```text
.env
__pycache__/
*.log
```

ثم نسأل مرة أخرى ونحفظ الملف نفسه في المستودع:

```bash
git status --short
git add .gitignore
git commit -m "Ignore secrets and Python cache"
git log --oneline
```

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

```text
?? .gitignore

[main af6a839] Ignore secrets and Python cache
 1 file changed, 3 insertions(+)
 create mode 100644 .gitignore

af6a839 Ignore secrets and Python cache
eec0df0 Add 15% VAT to the total
d9c83ff Add invoice total calculation
```

لاحظ أن `.env` و`__pycache__` اختفيا من المخرج، فلم يعد Git يراهما، ولاحظ أن `.gitignore` نفسه يدخل المستودع حتى تستفيد منه كل الفريق. ويجدر الإشارة هنا إلى أن `.gitignore` يمنع الملفات غير المتتبعة فقط، فإذا كان الملف قد دخل المستودع قبل أن تضيفه إلى القائمة فإن Git يستمر في متابعته، والتفاصيل وكل صيغ الأنماط في [توثيق gitignore](https://git-scm.com/docs/gitignore?ref=arabroot.io).

### ال Commit لقطة كاملة وليس فرقاً، والهاش بصمته

الأنظمة القديمة مثل RCS وSubversion تحفظ لكل ملف قائمة بالفروقات بين النسخ، أما Git فيفكر بطريقة مختلفة، فكما يشرح [كتاب Pro Git](https://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3F?ref=arabroot.io) فإن كل Commit هو لقطة كاملة Snapshot لكل ملفات المشروع في تلك اللحظة، والملف الذي لم يتغير لا يخزن مرة ثانية، وإنما تشير اللقطة الجديدة إلى النسخة نفسها المخزنة من قبل. ونستطيع أن نرى ذلك بأنفسنا بالأمر `git cat-file` الذي يعرض محتوى أي كائن Object داخل المستودع:

```bash
git cat-file -p HEAD
git cat-file -p HEAD^{tree}
```

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

```text
tree 5be0944bcf6446af247575f4a5021871cde2b2c3
parent eec0df048d96e56c0fc865d7276d230662378ee6
author Alice <alice@example.com> 1791238099 +0000
committer Alice <alice@example.com> 1791238099 +0000

Ignore secrets and Python cache

100644 blob bc5ba4cfdff2d9ccc1e27579d1bac2689106ec9d	.gitignore
100644 blob fbf7d7eac5d70e588b2508316ca67e846e39bf21	README.md
100644 blob 8d1acee5e0c82fd3a19bf21733a51bed3cf1fd7d	app.py
```

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

- ال Commit نفسه نص صغير فيه أربعة أشياء: الشجرة `tree` وهي قائمة كل ملفات المشروع في تلك اللحظة، والأب `parent` وهو هاش ال Commit الذي قبله، والكاتب والوقت، والرسالة. ولا يوجد فيه أي فرق Diff، والفرق الذي رأيته في `git diff` يحسبه Git عند الطلب بمقارنة لقطتين.
- الشجرة تشير إلى ثلاثة ملفات، كل منها كائن من نوع `blob` له هاش، ولاحظ أن هاش `app.py` هنا هو `8d1acee…`، وهو نفسه الذي ظهر في سطر `index ff6f3fb..8d1acee` في مخرج `git diff` قبل قليل، لأن محتوى الملف لم يتغير منذ ذلك ال Commit، فاستخدم Git الكائن نفسه.
- كل كائن في Git يسمى بهاش محتواه، فإذا تغير بايت واحد في ملف تغير هاشه، فتغير هاش الشجرة، فتغير هاش ال Commit وكل ما بعده، ولذلك لا يمكن تعديل التاريخ بصمت، وأي تعديل في Commit قديم يعني سلسلة جديدة بهاشات جديدة.

وهذا الهاش من نوع SHA-1، وطوله 160 بت يكتب في 40 حرفاً بنظام Hex، أي حرفين لكل بايت كما شرحنا في [قسم Hex في مقال الترميز](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-base64-%D9%88-base32-%D9%88-base58/#hex). وبما أن SHA-1 لم يعد يعتبر آمناً للاستخدامات الجديدة، فإن Git يدعم مستودعات تستخدم SHA-256، و[خطة الانتقال الرسمية](https://git-scm.com/docs/hash-function-transition?ref=arabroot.io) تصف كيف تتعامل هذه المستودعات مع سيرفرات SHA-1، وقد أعلن المشروع في [قائمة التغييرات القادمة](https://git-scm.com/docs/BreakingChanges?ref=arabroot.io) أن Git 3.0 سوف يجعل SHA-256 الافتراضي للمستودعات الجديدة، مع الإشارة إلى أن NIST أوقفت اعتماد SHA-1 في 2011، ولا يوجد حتى كتابة هذا المقال موعد محدد لصدور Git 3.0\. وتستطيع أن تجرب ذلك اليوم:

```bash
git init --object-format=sha256 s256
cd s256 && echo hi > a.txt && git add a.txt && git commit -qm "First commit"
git rev-parse HEAD
git rev-parse --show-object-format
```

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

```text
3b169a5a7780cdd30f605831c52e922ba46a7d029d20c5c6eb55846a4a3b74e9
sha256
```

لاحظ أن الهاش أصبح 64 حرفاً بدلاً من 40\. ولكن لا تقم بتحويل مستودعات فريقك إلى SHA-256 الآن دون أن تتأكد أن منصة الاستضافة وكل الأدوات التي تقرأ المستودع تدعمه، والسبب أن الخطة الرسمية نفسها تذكر أن مستودعات SHA-256 لا تقرؤها إصدارات Git القديمة.

### HEAD والفروع Branches: مؤشرات تتحرك

الفرع Branch في Git ليس نسخة من الملفات كما في الأنظمة القديمة، وإنما ملف صغير فيه هاش Commit واحد، أي مؤشر Pointer على Commit، وعندما تنشئ Commit جديداً يتحرك مؤشر الفرع الحالي إليه تلقائياً. أما HEAD فهو مؤشر خاص يخبر Git أين تقف أنت الآن، وغالباً يشير إلى اسم فرع. ولنر ذلك داخل المجلد `.git` نفسه:

```bash
git rev-parse HEAD
cat .git/HEAD
cat .git/refs/heads/main
```

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

```text
af6a839ae0b45f0a46813233c14b4400e3ab74e2
ref: refs/heads/main
af6a839ae0b45f0a46813233c14b4400e3ab74e2
```

الملف `.git/HEAD` يقول «أنا على الفرع main»، والملف `.git/refs/heads/main` فيه هاش آخر Commit في هذا الفرع، ولا شيء غير ذلك. ولهذا فإن إنشاء فرع في Git عملية فورية مهما كبر المشروع، لأنها كتابة 41 بايتاً في ملف جديد. والآن لنفرض أننا نريد أن نضيف خصماً Discount على الفاتورة، ولا نريد أن نكسر `main` أثناء العمل، فننشئ فرعاً للميزة وننتقل إليه:

```bash
git branch feature/discount
git branch
git switch feature/discount
```

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

```text
  feature/discount
* main

Switched to branch 'feature/discount'
```

النجمة تبين الفرع الحالي قبل الانتقال. والأمر `git switch` هو الأمر الأحدث للانتقال بين الفروع، وستجد في المقالات القديمة `git checkout` لنفس الغرض، ولكن checkout يقوم بأعمال كثيرة مختلفة، فاستخدم switch للفروع وrestore للملفات لأنهما أوضح. والآن عدلنا `app.py` ليقبل خصماً، ثم حفظنا التعديل، والخيار `-a` يجهز كل الملفات المتتبعة التي تغيرت دون الحاجة إلى `git add`:

```bash
git commit -am "Support a discount on the invoice"
git log --oneline --graph --all
cat .git/refs/heads/feature/discount
```

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

```text
[feature/discount 86db8a6] Support a discount on the invoice
 1 file changed, 4 insertions(+), 3 deletions(-)

* 86db8a6 Support a discount on the invoice
* af6a839 Ignore secrets and Python cache
* eec0df0 Add 15% VAT to the total
* d9c83ff Add invoice total calculation

86db8a6d7c1b9c7bf747d106835d88eb4ab24234
```

تحرك مؤشر `feature/discount` إلى ال Commit الجديد، وبقي `main` عند `af6a839`، ولو رجعت الآن إلى `main` لوجدت `app.py` دون الخصم، فالفرع مكان آمن للتجربة، وهذا هو الحل لمشكلة `project-test` التي بدأنا بها. والآن نرجع إلى `main` وندمج الميزة:

```bash
git switch main
git merge feature/discount
git branch -d feature/discount
```

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

```text
Switched to branch 'main'

Updating af6a839..86db8a6
Fast-forward
 app.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

Deleted branch feature/discount (was 86db8a6).
```

لاحظ الكلمة `Fast-forward`، ومعناها أن `main` لم يتغير منذ أنشأنا الفرع، فكل ما فعله Git أنه حرك مؤشر `main` إلى الأمام حتى `86db8a6`، ولم ينشئ أي Commit جديد. وبعد الدمج لم نعد نحتاج اسم الفرع فحذفناه، وال Commits نفسها باقية في `main`. والصورة التالية تبين اللحظات الثلاث بالهاشات الحقيقية من التجربة:

![ثلاثة صفوف من سلسلة Commits يشير كل منها إلى أبيه: في الأول فرع feature/discount جديد يشير إلى af6a839 مع main وHEAD عليه، وفي الثاني Commit جديد 86db8a6 يحرك feature/discount فقط، وفي الثالث بعد git merge يقفز main إلى 86db8a6 بطريقة Fast-forward](https://arabroot.io/content/images/2026/10/git-basics-04-branches.webp)

الفرع مؤشر على Commit، وال Commit الجديد يحرك الفرع الحالي فقط، والدمج Fast-forward يحرك main إلى الأمام

### الوسوم Tags: اسم ثابت لإصدار

الفرع يتحرك مع كل Commit، أما الوسم Tag فاسم ثابت يبقى على Commit واحد إلى الأبد، ويستخدم عادة لتمييز الإصدارات مثل `v1.0`، وأدوات CI/CD تستطيع أن تبني الإصدار وتنشره عندما يظهر وسم جديد. والوسم المشروح Annotated Tag بالخيار `-a` يحفظ معه اسم من أنشأه ورسالة، وهو المناسب للإصدارات:

```bash
git tag -a v1.0 -m "First release"
git log --oneline --decorate
```

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

```text
86db8a6 (HEAD -> main, tag: v1.0) Support a discount on the invoice
af6a839 Ignore secrets and Python cache
eec0df0 Add 15% VAT to the total
d9c83ff Add invoice total calculation
```

والجزء بين القوسين يلخص كل ما شرحناه: HEAD يشير إلى `main`، و`main` يشير إلى `86db8a6`، والوسم `v1.0` على ال Commit نفسه.

### التعارض Merge Conflict: عندما يعدل فرعان السطر نفسه

الدمج في أغلب الحالات تلقائي، فإذا عدل فرع دالة وعدل الآخر دالة غيرها دمجهما Git دون أن يسألك. ولكن عندما يعدل فرعان السطر نفسه بطريقتين مختلفتين فإن Git لا يستطيع أن يعرف أيهما الصحيح، فيتوقف ويطلب منك القرار. ولنصنع تعارضاً بأنفسنا، فننشئ فرعاً يغير عنوان `README.md` إلى «Invoice Calculator»، ثم نرجع إلى `main` ونغير السطر نفسه إلى «Simple Invoice App»:

```bash
git switch -c fix/readme-title
sed -i 's/^# Invoice App$/# Invoice Calculator/' README.md
git commit -am "Rename the app in README"
git switch main
sed -i 's/^# Invoice App$/# Simple Invoice App/' README.md
git commit -am "Clarify the app name in README"
git log --oneline --graph --all
```

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

```text
Switched to a new branch 'fix/readme-title'
[fix/readme-title ca7ab96] Rename the app in README
 1 file changed, 1 insertion(+), 1 deletion(-)
Switched to branch 'main'
[main aec57ac] Clarify the app name in README
 1 file changed, 1 insertion(+), 1 deletion(-)

* ca7ab96 Rename the app in README
| * aec57ac Clarify the app name in README
|/
* 86db8a6 Support a discount on the invoice
* af6a839 Ignore secrets and Python cache
* eec0df0 Add 15% VAT to the total
* d9c83ff Add invoice total calculation
```

الخيار `-c` في `git switch` ينشئ الفرع وينتقل إليه في خطوة واحدة، ولاحظ في الرسم أن التاريخ تفرع عند `86db8a6` إلى خطين. والآن نحاول الدمج:

```bash
git merge fix/readme-title
git status
cat README.md
```

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

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

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:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

<<<<<<< HEAD
# Simple Invoice App
=======
# Invoice Calculator
>>>>>>> fix/readme-title
A tiny app to calculate invoice totals.
```

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

- Git كتب داخل الملف علامات التعارض Conflict Markers: ما بين `<<<<<<< HEAD` و`=======` هو نسختك في الفرع الحالي `main`، وما بين `=======` و`>>>>>>> fix/readme-title` هو نسخة الفرع الذي تدمجه.
- السطر الثاني في الملف لم يتغير في أي فرع، فبقي كما هو دون علامات، فالتعارض محصور في السطر الذي تغير في الفرعين فقط.
- الأمر `git status` يخبرك بالخطوة التالية بالضبط، ويعطيك مخرجاً آمناً: `git merge --abort` يلغي الدمج كله ويرجعك إلى ما كنت عليه قبله.

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

```text
# Simple Invoice Calculator
A tiny app to calculate invoice totals.
```

ثم تخبر Git أنك حللت التعارض بالأمر `git add`، وتكمل الدمج بالأمر `git commit`، والخيار `--no-edit` يقبل رسالة الدمج التي اقترحها Git:

```bash
git add README.md
git commit --no-edit
git log --oneline --graph
```

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

```text
[main 7717533] Merge branch 'fix/readme-title'

*   7717533 Merge branch 'fix/readme-title'
|\
| * ca7ab96 Rename the app in README
* | aec57ac Clarify the app name in README
|/
* 86db8a6 Support a discount on the invoice
* af6a839 Ignore secrets and Python cache
* eec0df0 Add 15% VAT to the total
* d9c83ff Add invoice total calculation
```

هذه المرة لم يكن الدمج Fast-forward، لأن الفرعين تقدما كل في طريقه، فأنشأ Git ما يسمى Merge Commit، وهو Commit له أبوان: آخر Commit في `main` وآخر Commit في الفرع، وهذا ما يرسمه الخطان اللذان يلتقيان في `7717533`. والصورة التالية تلخص الخطوات الأربع:

![مخطط تعارض: README.md في 86db8a6 بعنوان Invoice App، ثم الفرع main يغيره إلى Simple Invoice App والفرع fix/readme-title يغيره إلى Invoice Calculator، ثم git merge يتوقف ويكتب علامات التعارض، ثم يكتب المطور Simple Invoice Calculator وينفذ git add وgit commit فينشأ Merge Commit 7717533 له أبوان](https://arabroot.io/content/images/2026/10/git-basics-06-conflict.webp)

Git يدمج تلقائياً كل ما لا يتعارض، ويتوقف عند السطر الذي تغير في الفرعين لأن القرار قرارك

⚠️

لا تقم بعمل Commit لملف ما زالت فيه علامات التعارض، والسبب أن Git لا يمنعك من ذلك، فإذا نفذت `git add` على الملف قبل أن تحذفها دخلت العلامات إلى الكود كما هي، وفي ملف كود سوف تكسر البرنامج. ابحث عن `<<<<<<<` في المشروع قبل أن تكمل الدمج، وأغلب المحررات مثل VS Code تعرض التعارض بأزرار تختار بها النسخة.

أما الطرق الأخرى لدمج الفروع، مثل Rebase الذي يعيد كتابة ال Commits فوق الفرع الآخر بدلاً من إنشاء Merge Commit، ومتى تختار كل طريقة، فقد شرحناها في [Git في العمل اليومي: Merge أم Rebase](https://arabroot.io/articles/git-merge-%D9%88-rebase-%D9%88-cherry-pick/).

### من غير هذا السطر ولماذا؟ git log وgit blame

نرجع الآن إلى المشكلة التي بدأنا بها، وهي السطر الغريب الذي لا تعرف من كتبه. فالأمر `git log` مع اسم ملف يعرض فقط ال Commits التي غيرت هذا الملف، والأمر `git blame` يعرض لكل سطر آخر Commit غيره ومن كتبه ومتى، وهذا مثال من نسخة المشروع في نهاية التجربة بعد أن عمل عليه شخصان:

```bash
git log --oneline -- app.py
git blame README.md
```

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

```text
86db8a6 Support a discount on the invoice
eec0df0 Add 15% VAT to the total
d9c83ff Add invoice total calculation

77175331 (Alice 2026-10-05 22:08:20 +0000 1) # Simple Invoice Calculator
^d9c83ff (Alice 2026-10-05 22:08:18 +0000 2) A tiny app to calculate invoice totals.
f5290b67 (Bob   2026-10-05 22:08:26 +0000 3) Supports discounts and 15% VAT.
5da576b7 (Alice 2026-10-05 22:08:28 +0000 4) License: MIT
```

ومن الهاش في أول السطر تصل إلى رسالة ال Commit بالأمر `git show f5290b67`، فتعرف لماذا كتب السطر. ولهذا فإن الرسالة الجيدة ليست رفاهية، فهي الإجابة التي سوف يقرؤها زميلك بعد سنة عندما يسأل «لماذا؟».

### المستودع البعيد Remote: clone وpush وfetch وpull

كل ما فعلناه حتى الآن على جهاز واحد، والآن نريد أن يعمل معنا شخص ثان. وفي الواقع يكون المستودع المشترك على منصة مثل Gitea، ولكن الفكرة نفسها تعمل مع أي مسار يصل إليه Git، ولذلك سوف نستخدم مستودعاً على القرص نفسه حتى تكون التجربة كاملة دون سيرفر. والمستودع الذي على السيرفر من نوع Bare، أي مستودع دون مجلد عمل، لأن لا أحد يعدل الملفات عليه مباشرة، وإنما يستقبل ال Commits ويرسلها فقط:

```bash
git init --bare /srv/git/invoice-app.git
git remote add origin /srv/git/invoice-app.git
git remote -v
git push -u origin main
git push origin v1.0
```

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

```text
Initialized empty Git repository in /srv/git/invoice-app.git/

origin	/srv/git/invoice-app.git (fetch)
origin	/srv/git/invoice-app.git (push)

To /srv/git/invoice-app.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

To /srv/git/invoice-app.git
 * [new tag]         v1.0 -> v1.0
```

في الإعداد أعلاه لاحظ التالي:

- `origin` هو مجرد اسم مختصر لعنوان المستودع البعيد، وهو الاسم الذي يستخدمه Git تلقائياً عند الاستنساخ، ومع Gitea يكون العنوان مثل `git@git.example.com:team/invoice-app.git` بدلاً من المسار المحلي، كما في قسم [أول مستودع والعمل عبر SSH](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-gitea-%D9%85%D8%B9-docker-compose/#first-repo).
- الخيار `-u` يربط الفرع المحلي `main` بالفرع البعيد `origin/main`، فيكفي بعد ذلك أن تكتب `git push` و`git pull` دون أسماء.
- الوسوم لا تدفع مع الفروع تلقائياً، لذلك دفعنا `v1.0` بأمر منفصل.

الآن يأتي الشخص الثاني، وسميناه Bob، فيستنسخ المستودع بالأمر `git clone`، الذي ينزل المستودع كاملاً بتاريخه ويضبط `origin` تلقائياً، ثم يضيف سطراً إلى README ويدفعه:

```bash
git clone /srv/git/invoice-app.git bob-invoice-app
cd bob-invoice-app
git branch -a
git commit -am "Document discount and VAT support"
git push
```

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

```text
Cloning into 'bob-invoice-app'...
done.

* main
  remotes/origin/HEAD -> origin/main
  remotes/origin/main

[main f5290b6] Document discount and VAT support
 1 file changed, 1 insertion(+)

To /srv/git/invoice-app.git
   7740024..f5290b6  main -> main
```

لاحظ في مخرج `git branch -a` الفرع `remotes/origin/main`، وهو ما يسمى فرع التتبع البعيد Remote-tracking Branch، أي ذاكرة جهازك عن آخر حالة رآها لفرع `main` على السيرفر. والآن نرجع إلى جهاز Alice، فهي لا تعرف بعد أن Bob دفع شيئاً، والأمر `git fetch` ينزل الجديد من السيرفر دون أن يلمس ملفاتها:

```bash
git fetch
git status
```

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

```text
From /srv/git/invoice-app
   7740024..f5290b6  main       -> origin/main

On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

nothing to commit, working tree clean
```

تحرك `origin/main` إلى `f5290b6`، وبقي `main` المحلي وملفاتها كما هي، وGit يخبرها أنها متأخرة بمقدار Commit واحد. والأمر `git pull` هو `git fetch` ثم دمج ما وصل في الفرع الحالي:

```bash
git pull
```

```text
Updating 7740024..f5290b6
Fast-forward
 README.md | 1 +
 1 file changed, 1 insertion(+)
```

والصورة التالية تبين ما ينقله كل أمر من الأوامر الأربعة بين جهازك والسيرفر:

![مخطط بين المستودع البعيد origin وجهاز المطور: git clone ينسخ المستودع كاملاً مرة واحدة، وgit push يرسل ال Commits الجديدة، وgit fetch ينزل الجديد ويحدث origin/main فقط، وgit pull يساوي fetch ثم merge في main ومجلد العمل](https://arabroot.io/content/images/2026/10/git-basics-05-remote.webp)

الأمر fetch لا يلمس ملفاتك، والأمر pull يجلب ويدمج في خطوة واحدة

### اكسرها لتفهمها: عندما يرفض السيرفر الدفع

لنفرض الآن أن Bob أنشأ Commit على جهازه، وفي الوقت نفسه أنشأت Alice Commit آخر ودفعته قبله، فماذا يحدث عندما يحاول Bob أن يدفع؟

```bash
git commit -m "Add example config"
git push
```

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

```text
[main 7c26f42] Add example config
 1 file changed, 1 insertion(+)
 create mode 100644 config.example

To /srv/git/invoice-app.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to '/srv/git/invoice-app.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
```

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

```bash
git pull --no-rebase --no-edit
git push
git log --oneline --graph -5
```

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

```text
From /srv/git/invoice-app
   f5290b6..5da576b  main       -> origin/main
Merge made by the 'ort' strategy.
 README.md | 1 +
 1 file changed, 1 insertion(+)

To /srv/git/invoice-app.git
   5da576b..40728d9  main -> main

*   40728d9 Merge branch 'main' of /srv/git/invoice-app
|\
| * 5da576b Add license line
* | 7c26f42 Add example config
|/
* f5290b6 Document discount and VAT support
* 7740024 Revert "Add run and requirements notes"
```

كل عمل الشخصين محفوظ، والسجل يبين من فعل ماذا. والخيار `--no-rebase` يطلب من pull أن يدمج بطريقة Merge صراحة، والسبب أن الإصدارات الحديثة من Git تتوقف عندما يتباعد الفرعان وتطبع الرسالة «You have divergent branches and need to specify how to reconcile them» حتى تختار الطريقة، وتستطيع أن تحدد الافتراضي مرة واحدة بالأمر `git config --global pull.rebase false` للدمج، والبديل هو Rebase الذي يعطي سجلاً مستقيماً دون Merge Commit، والمقارنة بينهما في [مقال Merge أم Rebase](https://arabroot.io/articles/git-merge-%D9%88-rebase-%D9%88-cherry-pick/).

🛑

عندما يرفض السيرفر الدفع لا تقم بإضافة الخيار `--force` لتتجاوز الرفض، والسبب أن الدفع بالقوة يجعل السيرفر يقبل نسختك ويحذف من الفرع ال Commits التي دفعها زملاؤك ولا توجد عندك، أي أنك تعيد مشكلة «آخر من يحفظ يكسب» بيدك. نفذ `git pull` ثم ادفع.

## التراجع عن الأخطاء بأمان

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

### تعديل لم تجهزه بعد: git restore

لنفرض أنك أضفت سطر Debug إلى `app.py` وتريد أن ترجع الملف كما كان في آخر Commit، وأنك أضفت ملف `notes.txt` إلى منطقة التجهيز بالخطأ وتريد أن تخرجه منها دون أن تحذفه:

```bash
git status --short
git restore app.py
git status --short
git add notes.txt
git status --short
git restore --staged notes.txt
git status --short
```

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

```text
 M app.py

A  notes.txt

?? notes.txt
```

الأمر `git restore app.py` أرجع الملف كما كان، فاختفى من الحالة تماماً، والأمر `git restore --staged notes.txt` أخرج الملف من منطقة التجهيز فرجع غير متتبع ولكنه ما زال على القرص. ويجب أن تنتبه إلى أن `git restore` دون `--staged` يمسح تعديلاتك غير المحفوظة في الملف نهائياً، ولا يوجد في Git ما يرجعها لأنها لم تدخل أي Commit، والتفاصيل في [توثيق git restore](https://git-scm.com/docs/git-restore?ref=arabroot.io).

### آخر Commit قبل الدفع: git commit --amend

إذا أنشأت Commit ثم تذكرت أنك نسيت ملفاً أو أخطأت في الرسالة، ولم تدفعه بعد، فالأمر `git commit --amend` يستبدل آخر Commit بآخر جديد يضم ما في منطقة التجهيز وبالرسالة الجديدة:

```bash
echo "Run it with: python app.py" >> README.md
git commit -am "Add run instructions"
git log --oneline -1
echo "Requires Python 3.10 or later." >> README.md
git add README.md
git commit --amend -m "Add run and requirements notes"
git log --oneline -3
```

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

```text
[main 484d039] Add run instructions
 1 file changed, 1 insertion(+)
484d039 Add run instructions

[main 0d34261] Add run and requirements notes
 Date: Mon Oct 5 22:08:21 2026 +0000
 1 file changed, 2 insertions(+)

0d34261 Add run and requirements notes
7717533 Merge branch 'fix/readme-title'
aec57ac Clarify the app name in README
```

لاحظ أن الهاش تغير من `484d039` إلى `0d34261`، وأن `484d039` اختفى من السجل، فال amend لا يعدل ال Commit القديم وإنما ينشئ Commit جديداً مكانه، ولهذا السبب بالضبط لا تستخدمه بعد الدفع.

### Commit وصل إلى الفريق: git revert

إذا كان ال Commit الخطأ قد وصل إلى السيرفر فالطريقة الآمنة هي `git revert`، الذي ينشئ Commit جديداً يعكس تعديلات ال Commit القديم دون أن يحذفه من التاريخ:

```bash
git revert --no-edit HEAD
git log --oneline -3
```

```text
[main 7740024] Revert "Add run and requirements notes"
 Date: Mon Oct 5 22:08:21 2026 +0000
 1 file changed, 2 deletions(-)

7740024 Revert "Add run and requirements notes"
0d34261 Add run and requirements notes
7717533 Merge branch 'fix/readme-title'
```

وقد شرحنا revert بالتفصيل، ومعه الأمر reset الذي يحرك الفرع إلى الخلف، والفرق بينهما ومتى تستخدم كل واحد، في [مقال Git في العمل اليومي](https://arabroot.io/articles/git-merge-%D9%88-rebase-%D9%88-cherry-pick/).

🛑

لا تقم بإعادة كتابة تاريخ دفعته إلى فرع مشترك، أي لا تستخدم `--amend` أو rebase أو reset على Commits وصلت إلى السيرفر ثم تدفع بالقوة، والسبب أن كل هذه الأوامر تنشئ Commits بهاشات جديدة، وزملاؤك ما زالوا يعملون فوق الهاشات القديمة، فتختلط النسختان عند أول pull. للتراجع عن شيء منشور استخدم `git revert`.

## العمل مع فريق

### أين يعيش المستودع المشترك؟

المستودع المشترك يحتاج إلى سيرفر يصل إليه الجميع، والمنصات التي تقدم ذلك تضيف إليه واجهة ويب لعرض الكود، وإدارة المستخدمين والصلاحيات، والمهام Issues، وطلبات الدمج، وخطوط CI/CD. وأمامك خياران:

- **الاستضافة الذاتية Self-hosted:** مثل [Gitea](https://about.gitea.com/?ref=arabroot.io) الخفيف الذي يعمل على سيرفر صغير، و[Forgejo](https://forgejo.org/?ref=arabroot.io) الذي انفصل عن Gitea ويديره مجتمعه، و[GitLab](https://about.gitlab.com/?ref=arabroot.io) الأكبر والأثقل. وهنا يبقى الكود على سيرفرك وتحت سيطرتك، وقد شرحنا ذلك خطوة بخطوة في [تثبيت Gitea على خادمك](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-gitea-%D9%85%D8%B9-docker-compose/)، ثم ربط حسابات الفريق بنظام دخول موحد في [ربط Gitea مع authentik عبر OIDC](https://arabroot.io/articles/%D8%B1%D8%A8%D8%B7-gitea-%D9%85%D8%B9-authentik-%D8%B9%D8%A8%D8%B1-oidc/).
- **المنصات السحابية:** مثل [GitHub](https://github.com/?ref=arabroot.io) و[GitLab.com](https://gitlab.com/?ref=arabroot.io)، ولا تحتاج فيها إلى إدارة أي سيرفر، والمقابل أن الكود على سيرفرات غيرك وتحت شروطهم.

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

### طلبات الدمج ومراجعة الكود

في الفرق لا يدفع أحد مباشرة إلى `main` عادة، وإنما يعمل كل شخص على فرع لميزته، ثم يدفع الفرع إلى السيرفر ويفتح طلب دمج، ويسمى في Gitea وGitHub طلب السحب Pull Request، وفي GitLab طلب الدمج Merge Request. وفي هذا الطلب يرى الزملاء الفروقات سطراً سطراً ويكتبون ملاحظاتهم، وهذا ما يسمى مراجعة الكود Code Review، وتشغل المنصة الاختبارات تلقائياً على الفرع، ولا يدمج إلا بعد الموافقة ونجاح الاختبارات. وقد شرحنا تشغيل هذه الاختبارات والبناء والنشر على سيرفرك في [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/)، أما استراتيجيات الفروع في الفرق وكيف تحافظ على الطلب نظيفاً فهي في [مقال Git في العمل اليومي](https://arabroot.io/articles/git-merge-%D9%88-rebase-%D9%88-cherry-pick/).

### رسالة ال Commit: اكتبها لزميلك بعد سنة

الرسائل مثل «fix» و«update» و«asdf» تجعل السجل بلا فائدة، والقاعدة البسيطة أن يكون السطر الأول ملخصاً قصيراً لما يفعله ال Commit بصيغة الأمر مثل «Add 15% VAT to the total»، ثم سطر فارغ، ثم شرح السبب إذا لم يكن واضحاً. وكثير من الفرق تتبع مواصفة [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/?ref=arabroot.io)، وصيغتها:

```text
<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
```

والنوع `feat` لميزة جديدة، و`fix` لإصلاح خطأ، وأنواع أخرى شائعة مثل `docs` للتوثيق، وعلامة `!` بعد النوع أو سطر `BREAKING CHANGE:` في الذيل تعني تغييراً يكسر التوافق، وهذه أمثلة من المواصفة نفسها:

```text
feat(lang): add Polish language
docs: correct spelling of CHANGELOG
feat!: send an email to the customer when a product is shipped
```

والفائدة العملية أن المواصفة تربط الأنواع بأرقام الإصدارات Semantic Versioning، ف `fix` يعني إصداراً من نوع PATCH، و`feat` يعني MINOR، والتغيير الكاسر يعني MAJOR، فتستطيع الأدوات أن تولد ملاحظات الإصدار Changelog وأرقامه من السجل تلقائياً.

### ما لا يجب أن يدخل المستودع أبداً

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

- **الأسرار Secrets:** كلمات المرور ومفاتيح ال API ومفاتيح SSH الخاصة وملفات `.env`، وضعها في متغيرات البيئة أو في أداة أسرار كما شرحنا في [الإعدادات والأسرار خارج ال Image](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/#config-secrets)، وأضف ملفاً مثل `.env.example` فيه أسماء المتغيرات دون قيم.
- **الملفات المولدة:** مثل `node_modules` و`__pycache__` ومخرجات البناء، لأنها تولد من الكود ولا معنى لتتبعها.
- **الملفات الكبيرة:** مثل نسخ قواعد البيانات الاحتياطية والفيديو، وإذا احتجت إليها فاستخدم Git LFS كما ذكرنا.

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

📌

في نوفمبر 2016 علمت شركة Uber أن مهاجمين دخلوا إلى بيانات العملاء المخزنة على سيرفرات مزود سحابي، باستخدام مفتاح وصول Access Key نشره أحد مهندسيها على موقع لمشاركة الكود، فنزلوا ملفات غير مشفرة فيها أكثر من 25 مليون اسم وبريد و22 مليون اسم ورقم جوال و600 ألف رقم رخصة قيادة، بحسب [الشكوى المعدلة من لجنة التجارة الفيدرالية الأمريكية FTC في أبريل 2018](https://www.ftc.gov/news-events/news/press-releases/2018/04/uber-agrees-expanded-settlement-ftc-related-privacy-security-claims?ref=arabroot.io)، والتي ذكرت أيضاً أن الشركة دفعت للمهاجمين 100 ألف دولار ولم تعلن عن الحادثة حتى نوفمبر 2017\. والدرس أن المفتاح في الكود مكشوف لكل من يصل إلى الكود، حتى لو ظننت أن المستودع خاص، لذلك تبقى الأسرار خارج المستودع، ويفحص خط CI كل دفع بحثاً عنها.

وأدوات اكتشاف الأسرار المسربة في الكود مثل Gitleaks وTruffleHog موضوع مستقل في [مرحلة DevOps وCI/CD في خارطة الطريق](https://arabroot.io/start/#stage-devops)، والفكرة أن يفحص خط CI كل Commit قبل أن يدمج، فيوقف الدمج إذا وجد ما يشبه مفتاحاً أو كلمة مرور.

## أخطاء شائعة عند البداية

- **الأمر `git add .` دون أن تنظر:** يضيف كل شيء، ومنه ملفات الأسرار والملفات المولدة، لذلك أنشئ `.gitignore` من أول يوم، ونفذ `git status` و`git diff --staged` قبل كل Commit.
- **Commit ضخم لكل شيء في آخر اليوم:** يجعل السجل غير مفيد ويجعل التراجع عن جزء منه صعباً، والأفضل Commit صغير لكل تغيير منطقي واحد برسالة واضحة.
- **العمل مباشرة على `main`:** يمنعك من التجربة بأمان ويصعب المراجعة، فأنشئ فرعاً لكل ميزة أو إصلاح.
- **نسيان `git pull` قبل البدء:** فتعمل ساعات فوق نسخة قديمة ثم تواجه تعارضات كثيرة، فابدأ يومك بجلب الجديد.
- **الدفع بالقوة `--force` إلى فرع مشترك:** يحذف عمل زملائك كما شرحنا، ومنصات مثل Gitea تسمح بحماية الفروع المهمة Protected Branches حتى ترفض هذا الدفع.
- **حل التعارض بحذف نسخة الزميل دون قراءتها:** التعارض يعني أن شخصين غيرا الشيء نفسه لسبب، فاقرأ النسختين، واسأل صاحب الأخرى إذا لم يكن السبب واضحاً.
- **الخوف من الأوامر:** أغلب الأوامر في هذا المقال آمنة، و`git status` يخبرك دائماً أين أنت وما الخطوة التالية، فاقرأ مخرجه قبل أن تبحث في الإنترنت.

## الأوامر التي سوف تستخدمها كل يوم

والجدول التالي يجمع أكثر 15 أمراً سوف تحتاجها في البداية، وكلها مرت في هذا المقال:

| الأمر                                           | ماذا يفعل                                                    |
| ----------------------------------------------- | ------------------------------------------------------------ |
| git config --global user.name "…"               | يعرف Git باسمك وبريدك، ويحمل كل Commit هذه البيانات          |
| git init                                        | ينشئ مستودعاً جديداً في المجلد الحالي                        |
| git clone <url>                                 | ينسخ مستودعاً بعيداً كاملاً بتاريخه ويضبط origin             |
| git status                                      | يعرض الفرع الحالي والملفات المعدلة والمجهزة والخطوة التالية  |
| git add <file>                                  | ينقل التعديل إلى منطقة التجهيز                               |
| git commit -m "…"                               | يحفظ ما في منطقة التجهيز كلقطة جديدة في التاريخ              |
| git diff وgit diff --staged                     | يعرض التعديلات غير المجهزة، ثم المجهزة لأجل ال Commit القادم |
| git log --oneline --graph --all                 | يعرض التاريخ مختصراً مع رسم الفروع                           |
| git switch <branch> وgit switch -c <branch>     | ينتقل إلى فرع، أو ينشئ فرعاً جديداً وينتقل إليه              |
| git branch                                      | يعرض الفروع، ومع \-d يحذف فرعاً مدموجاً                      |
| git merge <branch>                              | يدمج فرعاً في الفرع الحالي                                   |
| git fetch                                       | ينزل الجديد من السيرفر دون أن يغير ملفاتك                    |
| git pull                                        | ينزل الجديد ويدمجه في الفرع الحالي                           |
| git push                                        | يرسل Commits الفرع الحالي إلى السيرفر                        |
| git restore <file> وgit restore --staged <file> | يلغي تعديلاً غير محفوظ، أو يخرج ملفاً من منطقة التجهيز       |

وكل أمر له توثيق كامل تصل إليه بالأمر `git help <command>`، مثل `git help commit`، وهو نفسه المنشور في [موقع Git الرسمي](https://git-scm.com/docs/git-commit?ref=arabroot.io)، والكتاب المجاني [Pro Git](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell?ref=arabroot.io) أفضل مرجع إذا أردت أن تتعمق.

## تدريب عملي

1. أنشئ مستودعاً لمشروع صغير عندك، وأضف إليه `.gitignore` مناسباً، ثم أنشئ ثلاثة Commits برسائل بصيغة Conventional Commits، وتأكد بالأمر `git log --oneline` أن كل رسالة تشرح التغيير وحدها.
2. كرر تجربة التعارض: أنشئ فرعين يعدلان السطر نفسه، وادمجهما، واقرأ علامات التعارض، ثم جرب `git merge --abort` مرة، وحل التعارض في المرة الثانية.
3. إذا كان لديك Gitea، فأنشئ عليه مستودعاً فارغاً، واربطه بمستودعك بالأمر `git remote add origin`، وادفع إليه، ثم استنسخه في مجلد آخر وجرب سيناريو الدفع المرفوض.

## الخلاصة

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

- نظام إدارة النسخ يحل مشكلات النسخ المتعددة، وضياع عمل الزملاء، و«كان يعمل أمس»، ومن غير ماذا ولماذا، والتجربة الآمنة.
- الأنظمة المحلية مثل RCS لشخص واحد، والمركزية مثل CVS وSubversion تجمع التاريخ في سيرفر واحد هو نقطة فشل، والموزعة مثل Git وMercurial تعطي كل جهاز مستودعاً كاملاً.
- ولد Git في أبريل 2005 بعد أن فقد مجتمع Linux الاستخدام المجاني لأداة BitKeeper، وهو اليوم أساس كل منصات الاستضافة المعروفة، وللملفات الكبيرة استخدم Git LFS، وللمستودعات الضخمة في الألعاب توجد أنظمة مثل Perforce P4.
- التعديل يمر من مجلد العمل إلى منطقة التجهيز بالأمر add، ثم إلى التاريخ بالأمر commit، ثم إلى الفريق بالأمر push.
- ال Commit لقطة كاملة يسميها هاش محتواها، والفرع مؤشر يتحرك، وHEAD يحدد أين تقف، والوسم اسم ثابت لإصدار.
- التعارض يعني أن فرعين غيرا السطر نفسه، فتقرأ العلامات وتكتب النسخة الصحيحة ثم add وcommit.
- تراجع بالأمر restore عن ما لم تحفظه، وبالأمر amend عن آخر Commit قبل الدفع، وبالأمر revert عن ما وصل إلى الفريق، ولا تعد كتابة تاريخ منشور.
- الأسرار لا تدخل المستودع أبداً، وإذا دخلت فغيرها فوراً قبل أي شيء آخر.

وفي المقال التالي نكمل من حيث توقفنا: Merge أم Rebase، وCherry-pick، وreset وrevert بالتفصيل، واستراتيجيات الفروع في الفرق.

[![](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/gitea-cover-cover.webp)دليلGitea: خادم Git خاص لفريقك على سيرفرككيف تثبت Gitea على سيرفرك حتى يبقى كود فريقك عندك بتجربة تشبه GitHub، مع النسخ الاحتياطي والتحديث.](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-gitea-%D9%85%D8%B9-docker-compose/)[![](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/)[![](https://arabroot.io/content/images/2026/10/open-source-licenses-cover-cover.webp)دليلما هي البرمجيات مفتوحة المصدر؟ القصة وأشهر الرخصكيف بدأت البرمجيات الحرة والمفتوحة، وما الفرق بين الحر والمجاني، وكيف تعرف ما تسمح به رخصة أي مشروع قبل أن تبني عليه.](https://arabroot.io/articles/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-%D9%88%D8%AA%D8%B1%D8%A7%D8%AE%D9%8A%D8%B5%D9%87%D8%A7/)

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

- أكتوبر 2026: كتابة المقال، وتنفيذ كل الأمثلة داخل Container على Alpine 3.24.2 مع Git 2.54.0.