درک اشیاء داخلی گیت (Git Internal Objects) پیش‌نیاز فهم عمیق رفتار مخزن، عیب‌یابی خطاهای پیچیده و کار با APIهای سطح پایین این سیستم کنترل نسخه است. گیت در ظاهر ابزاری برای ثبت تغییرات فایل‌ها است، اما در لایه‌ی زیرین، از یک پایگاه داده‌ی محتوایی (Content-Addressable Storage) استفاده می‌کند که در آن، هر واحد داده با هش SHA-1 یا SHA-256 شناسایی می‌شود. چهار نوع شیء اصلی در این پایگاه داده وجود دارد: blob، tree، commit و tag. شناخت این اشیاء، به توسعه‌دهنده امکان می‌دهد رفتار گیت را در سطح پروتکل تحلیل کند، مخازن خراب را بازیابی نماید و ابزارهای سفارشی بسازد. اگر با دستورات پایه‌ی گیت آشنایی ندارید، مطالعه‌ی یادگیری گیت از صفر نقطه‌ی شروع مناسبی است. در این نوشتار، از ساختار شیء‌محور تا فرایند بازیابی مخزن و بهینه‌سازی، مسیر کامل را با تمرکز بر مهندسی ترسیم می‌کنم.

مدل شیء‌محور گیت

گیت را می‌توان به‌عنوان یک پایگاه داده‌ی محتوایی در نظر گرفت که در آن، هر واحد داده با هش محتوای خود شناسایی می‌شود. این مدل، تفاوت بنیادی گیت با سیستم‌های کنترل نسخه‌ی قدیمی‌تر مانند SVN (Subversion) را روشن می‌کند. در SVN، هر نسخه با یک شماره ترتیبی شناسایی می‌شود؛ در گیت، با هش محتوا. اگر محتوا تغییر کند، هش تغییر می‌کند و همین، یکپارچگی داده را تضمین می‌کند. برای درک تفاوت این دو مدل، مطالعه‌ی مقایسه Git و SVN کمک‌کننده است.

هر شیء در گیت، از سه بخش تشکیل می‌شود:

  • هدر: شامل نوع شیء و طول محتوا.
  • محتوای خام: داده‌ی اصلی شیء.
  • هش SHA-1: شناسه‌ی یکتای شیء که از هش کل داده‌ی هدر + محتوا محاسبه می‌شود.

این ساختار، امکان آدرس‌دهی محتوایی (Content Addressing) را فراهم می‌کند. در این مدل، آدرس هر شیء، هش محتوای آن است و از این‌رو، تغییر یک بایت در محتوا، هش را کاملاً تغییر می‌دهد. همین ویژگی، پایه‌ی یکپارچگی داده در گیت است.

شیء blob و ذخیره‌ی محتوا

شیء blob (Binary Large Object) در گیت، معادل محتوای یک فایل است، بدون هیچ‌گونه فراداده‌ی مربوط به نام یا مسیر. گیت، محتوای هر نسخه‌ی فایل را به‌عنوان یک blob مستقل ذخیره می‌کند؛ اگر دو فایل محتوای یکسان داشته باشند، فقط یک blob برای آن‌ها ذخیره می‌شود. این ویژگی، به‌طور خودکار از تکرار داده جلوگیری می‌کند.

برای مشاهده‌ی هش blob یک فایل، از دستور زیر استفاده می‌شود:

git hash-object path/to/file

برای نوشتن محتوا به‌عنوان blob در پایگاه داده، از دستور زیر استفاده می‌شود:

git hash-object -w path/to/file

نکته‌ی مهم: blob هیچ فراداده‌ای درباره‌ی نام فایل یا مجوز (Permission) ندارد. این اطلاعات در شیء tree ذخیره می‌شوند. همین جداسازی، یکی از دلایل کارآمدی گیت در ذخیره‌سازی است؛ زیرا تغییر نام فایل بدون تغییر محتوا، به ایجاد blob جدید منجر نمی‌شود.

شیء tree و ساختار پوشه‌ها

شیء tree معادل یک پوشه در فایل‌سیستم است و شامل فهرستی از ورودی‌ها می‌شود که هر یک به یک blob یا tree دیگر اشاره می‌کند. هر ورودی شامل سه بخش است:

  • حالت (mode): تعیین‌کننده‌ی نوع فایل (معمولی، اجرایی، symlink یا زیرپوشه).
  • نام: نام فایل یا زیرپوشه.
  • هش: شناسه‌ی blob یا tree مقصد.

برای مشاهده‌ی محتوای یک tree، از دستور زیر استفاده می‌شود:

git cat-file -p HEAD^{tree}

شیء tree، امکان بازسازی ساختار پوشه‌ها را در هر نسخه فراهم می‌کند. جالب است که گیت هر نسخه از یک پوشه را به‌عنوان یک tree جدید ذخیره می‌کند، اما زیرپوشه‌های بدون تغییر، به‌همان tree قبلی اشاره می‌کنند. این ویژگی، ذخیره‌سازی ساختار درخت را بسیار کارآمد می‌کند.

در گیت، tree همان ساختار پوشه است و blob همان محتوا؛ جدایی این دو، اساس کارآمدی سیستم است.

شیء commit و زنجیره‌ی نسخه‌ها

شیء commit معادل یک نسخه در تاریخ مخزن است و شامل بخش‌های زیر می‌شود:

  • tree: هش tree ریشه‌ی مخزن در این نسخه.
  • parent: هش commit قبلی (یا چند commit در ادغام‌ها).
  • author: نام، ایمیل و زمان نویسنده.
  • committer: نام، ایمیل و زمان ثبت‌کننده (که ممکن است با نویسنده متفاوت باشد).
  • پیام commit: توضیح تغییرات.

برای مشاهده‌ی محتوای یک commit، از دستور زیر استفاده می‌شود:

git cat-file -p HEAD

نکته‌ی مهم: هر commit به commit قبلی اشاره می‌کند و همین اشاره‌ی زنجیره‌ای، تاریخ مخزن را می‌سازد. این ساختار، در تضاد با مدل خطی SVN است که در آن، هر نسخه فقط به نسخه‌ی بعدی متصل است. در گیت، شاخه‌ها (Branch) تنها ارجاع‌های نام‌دار به commits هستند و همین سادگی، امکان ایجاد و حذف سریع شاخه‌ها را فراهم می‌کند. اگر با مفهوم شاخه در گیت آشنایی ندارید، مطالعه‌ی راهنمای شاخه‌بندی در گیت کمک‌کننده است.

شیء tag و ارجاع‌های نام‌دار

گیت دو نوع tag دارد: lightweight و annotated. نوع lightweight، تنها یک ارجاع ساده به یک commit است و در واقع یک شیء مستقل ایجاد نمی‌کند. نوع annotated، یک شیء tag کامل است و شامل اطلاعاتی مانند پیام، امضا و نویسنده می‌شود.

برای مشاهده‌ی محتوای یک tag annotated، از دستور زیر استفاده می‌شود:

git cat-file -p v1.0.0

نکته‌ی مهم: tag annotated به‌دلیل امضای دیجیتال و اطلاعات تکمیلی، برای انتشار نسخه‌های رسمی مناسب‌تر است. تیم‌هایی که از tagها برای مدیریت نسخه‌ها استفاده می‌کنند، بهتر است در کنار آن‌ها یک سیاست نسخه‌گذاری منسجم داشته باشند.

هش‌سازی و آدرس‌دهی محتوایی

گیت به‌طور تاریخی از SHA-1 برای هش‌سازی استفاده می‌کرد و از نسخه‌های جدید به سمت SHA-256 نیز پیش می‌رود. هش هر شیء از هش کردن کل داده‌ی هدر + محتوا محاسبه می‌شود. این روش، دو ویژگی مهم را فراهم می‌کند:

  1. یکپارچگی: هر تغییر در محتوا، هش را تغییر می‌دهد و همین، تشخیص دستکاری را ساده می‌کند.
  2. بی‌اثری موقعیت: محتوای یکسان، صرف‌نظر از محل ذخیره، هش یکسان می‌گیرد؛ بنابراین دو فایل با محتوای یکسان، فقط یک بار ذخیره می‌شوند.

برای مشاهده‌ی هش تمام اشیاء مخزن، از دستور زیر استفاده می‌شود:

git rev-list --objects --all

این دستور برای تحلیل رفتار مخزن و عیب‌یابی حجم آن بسیار کاربردی است.

فایل‌های pack و ذخیره‌سازی کارآمد

گیت اشیاء را به‌طور پیش‌فرض در قالب فایل‌های مستقل در پوشه‌ی .git/objects ذخیره می‌کند. اما به‌مرور زمان، تعداد این فایل‌ها افزایش می‌یابد و کارایی سیستم کاهش پیدا می‌کند. برای حل این مشکل، گیت از فایل‌های pack استفاده می‌کند که در آن‌ها، اشیاء به‌صورت فشرده و با استفاده از دلتا (Delta) ذخیره می‌شوند.

در یک packfile، هر شیء می‌تواند به‌عنوان یک نسخه‌ی کامل یا به‌عنوان اختلاف با شیء دیگر ذخیره شود. همین رویکرد، حجم ذخیره‌سازی را چندین برابر کاهش می‌دهد. برای مشاهده‌ی محتوای یک packfile، می‌توان از دستور git verify-pack استفاده کرد.

کاربردهای عملی

درک اشیاء داخلی گیت، در چند سناریوی عملی بسیار کاربردی است:

  1. بازیابی فایل حذف‌شده: با جستجو در blobهای مخزن می‌توان فایل‌های حذف‌شده را بازیابی کرد.
  2. تحلیل حجم مخزن: با شناسایی blobهای سنگین می‌توان حجم مخزن را کاهش داد.
  3. ساخت ابزار سفارشی: ابزارهایی که مستقیماً با اشیاء گیت کار می‌کنند، به درک این لایه نیاز دارند.
  4. عیب‌یابی خطاهای پیچیده: خطاهایی مانند «مخزن خراب شده است» معمولاً ریشه در اشیاء ناسازگار دارند.

برای مطالعه‌ی بیشتر در این حوزه، دستورات ضروری گیت و رفع خطاهای رایج گیت منابع کاربردی محسوب می‌شوند.

بازیابی مخزن با اشیاء گیت

در سناریوهای مختلف، ممکن است مخزن گیت دچار مشکل شود. سه سناریوی رایج و راه‌حل آن‌ها:

نخست، بازیابی commit حذف‌شده. اگر یک commit به‌اشتباه حذف شده باشد اما هنوز در reflog موجود باشد، می‌توان با دستور git reflog آن را پیدا و بازیابی کرد.

دوم، بازیابی blob یتیم. اگر یک فایل حذف شده باشد اما blob آن هنوز در مخزن موجود باشد، می‌توان با دستور git fsck --lost-found آن را پیدا کرد.

سوم، ترمیم tree خراب. اگر یک tree خراب شده باشد، می‌توان با بازسازی آن از blobهای موجود، مخزن را ترمیم کرد.

برای مطالعه‌ی بیشتر در این حوزه، حل تعارض گیت و راهنمای Pull Request کمک‌کننده است.

پرسش‌های پرتکرار

چرا گیت از SHA-1 استفاده می‌کند؟

SHA-1 به‌دلیل سرعت و سادگی، در زمان طراحی گیت انتخاب شد. با این حال، به‌دلیل ضعف‌های امنیتی، گیت به سمت SHA-256 در حال حرکت است.

تفاوت blob و tree چیست؟

blob محتوای یک فایل را ذخیره می‌کند، بدون نام یا مسیر؛ tree ساختار پوشه‌ها را ذخیره می‌کند و به blobها اشاره می‌نماید.

چگونه حجم مخزن را کاهش دهیم؟

با شناسایی blobهای سنگین، استفاده از git gc برای فشرده‌سازی و حذف اشیاء یتیم.

آیا می‌توان یک مخزن گیت را روی دیتابیس دیگری ذخیره کرد؟

گیت به‌طور پیش‌فرض از فایل‌سیستم استفاده می‌کند، اما کتابخانه‌هایی مانند libgit2 یا JGit امکان ذخیره‌سازی در بسترهای دیگر را فراهم می‌کنند.

در لایه‌های زیرین گیت

درک اشیاء داخلی گیت، نه یک سرگرمی فنی، بلکه مهارتی ضروری برای توسعه‌دهندگانی است که می‌خواهند در سطح مهندسی با گیت کار کنند. از بازیابی مخزن خراب تا ساخت ابزارهای سفارشی، این دانش به تحلیل رفتار سیستم در لایه‌های زیرین کمک می‌کند. برای تیم‌هایی که با مخازن بزرگ یا پروژه‌های وردپرسی پیچیده کار می‌کنند، تسلط بر این مفاهیم به کاهش هزینه‌های نگهداری منجر می‌شود. اگر در پروژه‌های واقعی با سناریوهای عجیب مخزن روبرو شده‌اید، برای خوانندگان بعدی ارزشمند است بدانید کدام ابزار یا رویکرد بیشترین کمک را به شما کرده است.