Git وتدفقات العمل التعاونية

داخليات Git: الكائنات والمراجع

18 دقيقة الدرس 1 من 28

داخليات Git: الكائنات والمراجع

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

مخزن الكائنات: أربعة أنواع تشرح كل شيء

كل قطعة بيانات يتتبعها Git تعيش في .git/objects/. يخزن Git أربعة أنواع من الكائنات، يُعرَّف كل منها بتجزئة SHA-1 (أو SHA-256 في المستودعات الأحدث) لمحتوياته. التجزئة هي الهوية — غيّر بايتاً واحداً، تحصل على كائن مختلف تماماً. هذا هو نموذج العنونة بالمحتوى.

  • Blob — محتويات الملف الخام، لا شيء آخر. لا اسم ملف، لا صلاحيات. ملفان بمحتويات متطابقة يتشاركان blob واحداً.
  • Tree — لقطة دليل: قائمة من إدخالات (mode، اسم، SHA) تشير إلى blobs وأشجار أخرى. تمثل دليلاً واحداً في لحظة واحدة.
  • Commit — مؤشر إلى شجرة جذر، وصفر أو أكثر من SHAs للccommits الوالدة، وبيانات وصفية للمؤلف/الموفر، ورسالة. الcommit هو ما يمنح Git رسم تاريخه.
  • Tag — كائن وسم مشروح: يشير إلى أي كائن (عادةً commit) ويضيف هوية المُوسِّم وتاريخاً وتوقيعاً PGP لسلامة الإصدارات.
رؤية جوهرية: لأن كل كائن يُعرَّف بتجزئة محتواه، فإن Git يتسم بالثبات الجوهري. لا تعدّل أبداً كائناً — بل تنشئ كائناً جديداً. git commit --amend لا يحرر الcommit القديم؛ بل يكتب كائن commit جديداً تماماً بـSHA جديدة ويحرك مؤشر الفرع. الcommit القديم لا يزال موجوداً حتى يُجمع كمهملات.

تشريح الـ DAG

يُشكّل تاريخ الcommits رسماً بيانياً موجهاً لا دوري (DAG). يشير كل commit للخلف نحو والده (والديه). يملك commit الدمج والدَين. هذه البنية تجعل التفرع والدمج رخيصَين — لا تُنسخ بيانات، تُكتب مؤشرات فحسب.

Git Object DAG: blobs, trees, commits, refs Refs Commits Trees Blobs HEAD main feature Commit C3 a1b2c3d Commit C2 f4e5d6c Commit C1 9a8b7c6 Tree A d1e2f3a Tree B b5c6d7e Blob main.go Blob README.md Blob go.mod shared
رسم DAG لكائنات Git: تشير المراجع إلى commits، وتشير commits إلى الأشجار، وتشير الأشجار إلى blobs. تتشارك الملفات غير المتغيرة nفس الـblobs عبر الcommits — لا تخزين مكرر.

استكشاف مخزن الكائنات مباشرةً

كل ما يلي قابل للتشغيل في أي مستودع Git. استخدم git cat-file — سكين الجيش السويسري للتحقيق في الكائنات الخام.

# افحص كائن الcommit الحالي git cat-file -t HEAD # يطبع: commit git cat-file -p HEAD # طباعة جميلة: SHA الشجرة، SHA(s) الوالد، المؤلف، الرسالة # افحص الشجرة الجذر لذلك الcommit git cat-file -p HEAD^{tree} # يسرد mode، النوع، SHA، اسم الملف لكل إدخال # افحص blob (محتويات الملف الخام) git ls-tree -r HEAD # سرد جميع blobs بشكل متكرر مع SHAs git cat-file -p <blob-sha> # طباعة بايتات الملف الخام — لا بيانات وصفية # تجول في رسم الكائنات يدوياً git log --oneline --graph --all # نظرة عامة على DAG git rev-list --objects HEAD # كل كائن يمكن الوصول إليه من HEAD # ابحث عن SHA لأي مرجع git rev-parse HEAD # SHA كاملة من 40 حرفاً git rev-parse HEAD~3 # ثلاثة commits للخلف git rev-parse main@{yesterday} # مبني على reflog: أين كان main أمس

كل ملف ضمن .git/objects/ يستخدم أول حرفين سداسيين كاسم دليل والـ38 المتبقية كاسم ملف. الكائنات مضغوطة بـzlib. Packfiles (في .git/objects/pack/) تجمع كائنات كثيرة معاً للكفاءة — ستراها في أي مستودع مستنسخ.

المراجع (Refs): أسماء تشير إلى SHAs

المرجع هو ببساطة ملف يحتوي على SHA. هذا كل شيء. refs/heads/main ملف بحجم 41 بايت يحمل SHA لأحدث commit على main. عندما تنفذ git commit، يكتب Git كائن الcommit الجديد، ثم يعيد كتابة ذلك الملف بالـSHA الجديدة.

  • refs/heads/* — الفروع المحلية
  • refs/remotes/* — فروع التتبع عن بُعد (لقطات للقراءة فقط لما كان عليه الريموت آخر مرة جلبت)
  • refs/tags/* — وسوم خفيفة (مجرد ملف SHA) أو وسوم مشروحة (تشير إلى كائن tag)
  • HEAD — مرجع رمزي يشير إلى الفرع المُسحب حالياً، أو SHA مجردة عند الانفصال
# اطلع على جميع المراجع كملفات خام ls .git/refs/heads/ cat .git/refs/heads/main # يطبع SHA مباشرةً # HEAD كمرجع رمزي cat .git/HEAD # يطبع: ref: refs/heads/main (أو SHA مجردة إذا كنت منفصلاً) # المراجع المحزومة cat .git/packed-refs # سطر واحد "sha اسم-المرجع" لكل مرجع محزوم # Reflog: كل موضع كان HEAD أو فرع يشير إليه git reflog # آخر 30 حركة لـHEAD (افتراضي) git reflog show main # حركات طرف فرع main # استعادة commit "محذوف" عبر reflog (مهارة إنقاذ حرجة) git reflog | grep <keyword> git checkout -b rescue <sha-من-الreflog>
نمط الإنقاذ في الإنتاج: عندما يُخلّف git reset --hard أو push قسري عرضي حالة من الذعر بسبب commits "مفقودة"، فإن reflog يُنقذك في معظم الأحيان. تُحتفظ بإدخالات reflog لمدة 90 يوماً افتراضياً (gc.reflogExpire). على الريموتات المشتركة (GitHub، GitLab)، لا يكون reflog مكشوفاً — لكن محلياً يمكنك الاسترداد دائماً قبل تشغيل git gc.

كيف تُمكّن الحزم ورسم الكائنات التوسع

في المستودعات الكبيرة (فكر في Chromium بـ900 ألف commit، أو Linux بـ1.1 مليون)، سيكون مخزن الكائنات الفردية غير قابل للإدارة. يستخدم Git packfiles وضغط الدلتا: بدلاً من تخزين كل إصدار لملف، يخزن نسخة كاملة واحدة وفروقات ثنائية (deltas) بين blobs المتشابهة. git gc (جمع المهملات) يُشغّل الحزم. على GitHub، كل git push يُشغّل إعادة حزم جانب الخادم.

# اطلع على إحصائيات الحزمة لمستودعك git count-objects -vH # شغّل يدوياً إعادة حزمة كاملة (ما تفعله GitHub عند الاستيعاب) git gc --aggressive --prune=now # تحقق من سلامة قاعدة بيانات الكائنات بأكملها git fsck --full # ابحث عن أكبر الكائنات في تاريخ الحزمة (مفيد قبل ترحيل git-lfs) git rev-list --objects --all \ | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \ | sort -k3 -rn \ | head -20
لا تشغّل git gc --aggressive على ريموت مشترك نشط. يعيد كتابة جميع سلاسل دلتا الحزمة وقد يستغرق ساعات على المستودعات الكبيرة. على المنصات المُدارة (GitHub، GitLab، Bitbucket)، دع المنصة تتولى إعادة الحزم — تشغّلها بشكل غير متزامن مع ضمان استمرارية خدمة الاستنساخ. على Gitea المستضاف ذاتياً أو المستودعات المجردة، جدوّل git gc في فترات انخفاض حركة المرور.

نموذج العنونة بالمحتوى في الإنتاج

فهم أن Git يعتمد على المحتوى له تداعيات مباشرة على عمل DevOps:

  1. بنيات قابلة للاستنساخ: تثبيت اعتمادية على SHA لcommit في Git (لا على اسم فرع) يكون حتمياً — نفس SHA يعني دائماً نفس الشجرة بالضبط. هذا سبب إشارة مستودعات manifests Kubernetes ووحدات Terraform ووحدات Go جميعها إلى SHAs.
  2. التحقق من السلامة: يستطيع git fsck الكشف عن تلف البيانات من أعطال الأقراص. أي تغيير في بت يغير SHA ويُشير فوراً إلى خطأ.
  3. الاستنساخات الضحلة في CI: git clone --depth=1 يجلب commit الطرف وشجرته فقط — لا تاريخ. هذا سبب قدرة GitHub Actions على استنساخ مستودع بحجم 5 جيجابايت في 3 ثوانٍ لمهمة بناء. المقايضة: لا git log، لا git bisect.
  4. الاستنساخات الجزئية: git clone --filter=blob:none (الفحص المتفرق) يجلب commits والأشجار لكن يحمّل blobs عند الطلب بشكل كسول. هذه الطريقة التي تعمل بها فرق المستودعات الأحادية الكبيرة على نطاق Google مع أجزاء من مستودع دون تحميل تيرابايتات.
SHA-1 مقابل SHA-256: Git 2.29+ يدعم مستودعات SHA-256 (git init --object-format=sha256). لم تهاجر GitHub بعد، لكن المنصات الداخلية الكبرى تُقيّمها. نموذج الكائنات متطابق — دالة التجزئة فقط تتغير. في الوقت الحالي، جميع مستودعات الإنتاج التي ستواجهها تستخدم SHA-1. التصادم المعروف لـSHA-1 (SHAttered، 2017) مُخفَّف في Git عبر مكتبة كشف التصادم؛ الهجمات الفعلية على مستودعات Git تبقى نظرية.

بهذا الأساس — blobs والأشجار والcommits والمراجع والـDAG — يصبح كل سلوك آخر في Git قابلاً للتنبؤ. التفرع مجرد كتابة ملف بـ41 بايت. الدمج هو إنشاء commit بوالدَين. إعادة الأساس هي إعادة كتابة كائنات commit بـSHAs والدة جديدة. لديك الآن النموذج الذهني لتشخيص أي مشكلة في Git على مستوى الكائنات.