گیت چگونه کامیتها، درختها و بلابها را به عنوان اشیای محتوا-محور در یک گراف جهتدار غیرمدور ذخیره میکند
گیت در پسِ ظاهر سیستم کنترل نسخه خود، در واقع به عنوان یک پایگاه داده تغییرناپذیر کلید-مقدار عمل میکند؛ جایی که فایلها و پوشهها به شکل هشهای رمزنگاریشده در یک گراف جهتدار غیرمدور (DAG) ذخیره و نگاشت میشوند.
به قلم نیما موسوی
این خبر را به اشتراک بگذارید
بهطور خلاصه
- گیت به عنوان یک سیستم فایل محتوا-محور عمل میکند و دادهها را به جای مسیر فایل، با یک هش رمزنگاریشده ۴۰ کاراکتری شناسایی میکند.
- این سیستم به جای ثبت لیستی زمانی از تفاوتها (diffs) یا تغییرات، اسنپشاتهای کاملی از کل پروژه را ذخیره میکند.
- چهار نوع شیء بنیادی — بلابها (blobs)، درختها (trees)، کامیتها (commits) و تگها (tags) — کل پایگاه داده تغییرناپذیر گیت را تشکیل میدهند.
در یک سیستم فایل سنتی مانند NTFS ویندوز یا APFS مکاواس، یک سند با درخواست یک مسیر فایل مشخص از سیستمعامل، مانند C:\project\main.py، بازیابی میشود. اما گیت مفهوم مکان را به کلی کنار میگذارد. در هسته خود، فراگیرترین سیستم کنترل نسخه جهان در واقع اصلاً یک سیستم کنترل نسخه نیست، بلکه یک پایگاه داده کاملاً محتوا-محور است.
وقتی یک توسعهدهنده فایلی را از گیت میخواهد، در واقع آدرس یک مکان را طلب نمیکند؛ بلکه یک هش رمزنگاریشده ۴۰ کاراکتری را میخواهد که نمایانگر محتوای دقیق آن فایل در یک میلیثانیه خاص از زمان است.[1][4]
در حالی که پلتفرمهای توسعهدهنده مدرن با وعدههای پر زرق و برقِ همکاری بیوقفه و نسخهبندی هوشمند برای خود بازاریابی میکنند، قابلیت واقعی که صنعت جهانی نرمافزار را به پیش میراند، به طرز شگفتآوری ساده و بیتکلف است.
بیشتر توسعهدهندگان از طریق دستورات سطح بالا (porcelain) مانند git diff یا git log با گیت تعامل دارند؛ چیزی که این توهم را ایجاد میکند که نرمافزار در حال ذخیره یک توالی زمانی از تغییرات است — یعنی یک فایل پایه که به دنبال آن لیستی از حذفیات و اضافات آمده است. این دقیقاً همان روشی است که سیستمهای قدیمیتر دهه ۱۹۹۰ مانند Subversion و CVS کار میکردند.[2][5]
اما واقعیت معماری گیت کاملاً متفاوت است. گیت اسنپشاتها را ذخیره میکند، نه تفاوتها (diffs) را. هر بار که کاربری تغییری را کامیت میکند، گیت یک تصویر کامل از وضعیت تمام فایلها در همان لحظه دقیق ثبت میکند. اگر فایلی تغییر نکرده باشد، گیت یک کپی اضافی از آن ذخیره نمیکند؛ بلکه به سادگی به همان فایل یکسان قبلی که از پیش در پایگاه دادهاش وجود دارد لینک میدهد. این رویکرد، بار پردازشی تولید لحظهای تفاوتها را فدای یکپارچگی مطلق دادهها و بازیابی آنی اسنپشاتها میکند.[4][5]
این سازوکار کاملاً بر ذخیرهسازی محتوا-محور استوار است. مستندات رسمی Git SCM توضیح میدهد: «گیت یک سیستم فایل محتوا-محور است. این بدان معناست که در هسته گیت یک ذخیرهساز ساده داده از نوع کلید-مقدار قرار دارد.» در این مدل، نام یک قطعه داده مستقیماً از خود آن داده و با استفاده از یک تابع هش رمزنگاریشده، به طور خاص الگوریتم ۱۶۰ بیتی SHA-1، استخراج میشود.[1][2]
وقتی فایلی به یک مخزن گیت اضافه میشود، سیستم یک رشته هگزادسیمال ۴۰ کاراکتری را بر اساس محتوای فایل و یک هدر کوتاه محاسبه میکند. این هش به هویت دائمی فایل تبدیل میشود. اگر حتی یک ویرگول در یک پایگاه کد ۱۰,۰۰۰ خطی تغییر کند، هش کاملاً تغییر میکند و یکپارچگی ۱۰۰ درصدی دادهها را تضمین مینماید. از آنجا که هش از محتوا به دست میآید، دو فایل کاملاً یکسان در پوشههای متفاوت، دقیقاً یک هش یکسان تولید کرده و تنها یک بار ذخیره میشوند.[2][3]
این موضوع ما را به چهار نوع شیء بنیادی میرساند که پوشه پنهان .git/objects گیت را پر میکنند. پایهایترینِ آنها «بلاب» (blob) است که مخفف Binary Large Object (شیء بزرگ باینری) است. یک بلاب هیچچیز جز دادههای خام فایل را ذخیره نمیکند. این شیء نام فایل، تاریخ ایجاد یا مجوزهای خود را نمیداند.
بلاب صرفاً بایتهای خام محتواست که با استفاده از zlib فشرده شده و با هش SHA-1 خود نامگذاری شده است. از آنجا که بلابها فاقد متادیتا هستند، دو فایل با نامهای متفاوت اما محتوای یکسان، دقیقاً به یک بلاب واحد هش میشوند که این امر باعث صرفهجویی قابلتوجهی در فضای ذخیرهسازی میگردد.[1][5]
از آنجا که بلابها فاقد بستر ساختاری هستند، گیت برای بازسازی سلسلهمراتب پوشههای یک پروژه به نوع دوم از اشیاء نیاز دارد: «درخت» (tree). یک شیء درخت عملکردی بسیار شبیه به یک پوشه در یک سیستمعامل استاندارد دارد و بلابها و زیردرختها را سازماندهی میکند. یک درخت حاوی لیستی از اشارهگرهاست.
هر ورودی در یک شیء درخت شامل حالت فایل (file mode)، نوع شیء (یک درخت دیگر یا یک بلاب)، هش SHA-1 آن شیء و نام قابلخواندن فایل برای انسان است. با پیوند دادن درختها به بلابها و سایر درختهای تو در تو، گیت کل ساختار پوشههای یک پروژه را در یک نقطه زمانی مشخص ترسیم میکند.[1][3]
با این حال، درختها و بلابها به تنهایی فقط یک وضعیت ایستا و منجمد در زمان را توصیف میکنند. برای ردیابی تاریخچه و انتساب تغییرات به نویسندگان خاص، گیت سومین و آشناترین نوع شیء را معرفی میکند: «کامیت» (commit). یک شیء کامیت به طرز شگفتآوری سبک است و صرفاً به عنوان یک پوشش (wrapper) پیرامون یک وضعیت خاص عمل میکند.
این شیء حاوی یک اشارهگر به شیء درخت ریشه در بالاترین سطح است که کل ساختار پوشه پروژه را نشان میدهد؛ به همراه متادیتاهایی مانند نام و ایمیل نویسنده، برچسب زمانی (timestamp) و یک پیام کامیت که زمینه تغییر را توضیح میدهد.[2][4]
نکته حیاتی این است که یک کامیت همچنین حاوی اشارهگری به کامیت والد خود است. کامیت اولیه در یک مخزن هیچ والدی ندارد. یک کامیت متوالی استاندارد دقیقاً یک والد دارد، در حالی که یک کامیت ادغام (merge commit) دارای دو یا چند والد است. همین پیوند والد-فرزندی است که اسنپشاتهای منزوی را به یک خط زمانی پیوسته و قابلپیمایش تبدیل میکند. بدون این اشارهگرهای والد، یک مخزن کد صرفاً تودهای از همگسیخته از وضعیتهای تاریخی بدون هیچ ترتیب زمانی میبود.[4][5]
این کامیتها در کنار هم یک گراف جهتدار غیرمدور (DAG) را تشکیل میدهند. در نظریه گراف، DAG شبکهای از گرههاست که توسط یالهایی به هم متصل شدهاند که در یک جهت جریان دارند و هرگز به روی خود حلقه نمیزنند. در DAG گیت، گرهها همان اشیای کامیت هستند و یالهای جهتدار، اشارهگرهای والد محسوب میشوند.
از آنجا که هشهای رمزنگاریشده تغییرناپذیرند، تاریخچه فقط میتواند رو به جلو حرکت کند. شما نمیتوانید یک کامیت گذشته را بدون تغییر اساسی هش آن دستکاری کنید؛ کاری که ارتباط آن را با تمام کامیتهای بعدی قطع کرده و نیازمند بازنویسی کل گرافِ پاییندست است.[3][5]
این تغییرناپذیری سختگیرانه، بستر اصلی قابلیت اطمینان گیت در میان تیمهای توزیعشده است. وقتی یک توسعهدهنده تلاش میکند با استفاده از دستوراتی مانند git rebase یا git commit --amend تاریخچه را بازنویسی کند، در واقع کامیتهای موجود را ویرایش نمیکند. در عوض، نرمافزار اشیای کامیت کاملاً جدیدی با هشهای تازه تولید کرده و قبلیها را رها میکند.
کامیتهای اصلی در پایگاه داده محلی دستنخورده و کاملاً یتیم باقی میمانند، تا زمانی که در نهایت توسط روتینهای داخلی جمعآوری زباله (garbage collection) گیت برای همیشه حذف شوند. این امر تضمین میکند که عملیاتهای مخرب به ندرت منجر به از دست رفتن فوری دادهها میشوند.[4][5]
آخرین قطعه از معماری هسته، شیء «تگ» (tag) است که یک برچسب دائمی و قابلخواندن برای انسان را به یک هش کامیت خاص اختصاص میدهد و اغلب برای نشانهگذاری نسخههای انتشار یافته مانند v1.0.0 استفاده میشود. آنچه توسعهدهندگان معمولاً به عنوان «برنچ» (branch) یا شاخه میشناسند، موجودیتهای ساختاری پیچیدهای در داخل DAG نیستند.
یک برنچ در گیت صرفاً یک فایل متنی ۴۱ بایتی است که حاوی هش ۴۰ کاراکتری SHA-1 یک کامیت، به علاوه یک کاراکتر خط جدید (newline) است. وقتی کامیت جدیدی روی یک برنچ انجام میشود، این فایل متنی به سادگی با هش جدید بازنویسی میشود.[1][5]
این جداسازی معماری بین پایگاه داده اشیای تغییرناپذیر — شامل بلابها، درختها و کامیتها — و اشارهگرهای تغییرپذیر مانند برنچها و تگها، همان چیزی است که به گیت اجازه میدهد عملیات پیچیده را با سرعتی تقریباً آنی انجام دهد. ایجاد یک برنچ جدید، فایلها را کپی نمیکند یا نیازی به محاسبات سنگین ندارد؛ بلکه صرفاً یک اشارهگر ۴۱ بایتی به یک گره موجود در گراف ایجاد میکند.
جابجایی بین برنچها به سادگی دایرکتوری کاری را بهروز میکند تا با درخت ارجاعدادهشده توسط آن اشارهگر مطابقت داشته باشد؛ موضوعی که تغییر بستر کاری (context switching) را به جای یک کپی سنگین در سیستم فایل، به یک عملیات پیشپاافتاده تبدیل میکند.[4][5]
در حالی که صنعت نرمافزار اغلب درباره ابزارهای جدید کدنویسی با هوش مصنوعی و پلتفرمهای همکاری ابری هیاهو به پا میکند، مکانیک زیربنایی گیت از زمانی که لینوس توروالدز این سیستم را در سال ۲۰۰۵ طراحی کرد، تا حد زیادی بدون تغییر باقی مانده است.
قابلیت واقعی که توسعه مدرن را به پیش میراند، نه در الگوریتمهای پیچیده تفاوتسنجی (diffing)، بلکه در سادگی ظریف گراف جهتدار غیرمدور نهفته است. گیت با در نظر گرفتن تاریخچه کد به عنوان یک گراف ریاضی از اسنپشاتهای تغییرناپذیر به جای یک توالی شکننده از ویرایشها، مشکل همکاری توزیعشده را برای همیشه حل کرد.[2][6]
درک این لایه پنهان و زیربنایی، جادوی ظاهری دستورات سطح بالا (porcelain) که کاربر با آنها سر و کار دارد را از بین میبرد. وقتی یک تداخل ادغام (merge conflict) رخ میدهد یا یک مخزن وارد وضعیت HEAD جداشده (detached HEAD) میشود، این یک خرابی فاجعهبار سیستم نیست، بلکه صرفاً مسئله پیمایش گراف و تراز کردن اشارهگرها در یک ذخیرهساز کلید-مقدار محتوا-محور است.
اصطلاحات کلیدی
- ذخیرهسازی محتوا-محور
- سازوکار ذخیرهسازی که در آن دادهها به جای مسیر یا مکان مشخص فایل، بر اساس هش رمزنگاریشده محتوایشان بازیابی میشوند.
- گراف جهتدار غیرمدور (DAG)
- شبکهای از گرهها که توسط یالهای جهتدار به هم متصل شدهاند و هرگز یک حلقه بسته تشکیل نمیدهند؛ گیت از این ساختار برای ترسیم تاریخچه کامیتها استفاده میکند.
- بلاب (Blob)
- مخفف Binary Large Object؛ نوعی شیء در گیت که محتویات خام و فرمتنشده یک فایل ردیابیشده را ذخیره میکند.
- درخت (Tree)
- شیئی در گیت که نمایانگر ساختار دایرکتوری است و حاوی اشارهگرهایی به بلابها (فایلها) و سایر درختها (زیرپوشهها) میباشد.
- SHA-1
- یک الگوریتم هش رمزنگاری ۱۶۰ بیتی که گیت برای تولید یک شناسه منحصربهفرد ۴۰ کاراکتری برای هر شیء در پایگاه داده خود از آن استفاده میکند.
- دستورات سطح بالا (Porcelain)
- دستورات کاربرپسند و سطح بالای گیت (مانند git add و git commit) که عملیات پیچیده پایگاه داده زیربنایی را از دید کاربر پنهان میکنند.
پرسشهای متداول
آیا گیت در هر کامیت یک کپی جدید از تمام فایلها ذخیره میکند؟
خیر. گیت یک اسنپشات کامل از پروژه ذخیره میکند، اما دادههای تکراری را حذف میکند. اگر فایلی بین کامیتها تغییر نکرده باشد، شیء درخت جدید به سادگی به همان هش بلاب کامیت قبلی اشاره میکند و در فضای ذخیرهسازی صرفهجویی میشود.
وقتی یک برنچ را ریبیس (rebase) میکنم، چه اتفاقی برای کامیت قدیمی میافتد؟
ریبیس کردن کامیتهای موجود را تغییر نمیدهد، زیرا اشیای گیت تغییرناپذیرند. در عوض، اشیای کامیت کاملاً جدیدی با هشهای جدید ایجاد میکند. کامیتهای قدیمی در پایگاه داده باقی میمانند تا زمانی که سیستم جمعآوری زباله گیت آنها را برای همیشه حذف کند.
چرا گیت از SHA-1 استفاده میکند در حالی که از نظر رمزنگاری شکسته شده محسوب میشود؟
گیت از SHA-1 در درجه اول برای یکپارچگی دادهها و شناسایی محتوا استفاده میکند، نه برای امنیت در برابر حملات هدفمند تصادم (collision). اگرچه نسخههای جدیدتر گیت در حال گذار به SHA-256 هستند، احتمال وقوع یک تصادم تصادفی SHA-1 در یک مخزن کد همچنان بینهایت کوچک است.
تفاوت بین یک درخت و یک بلاب چیست؟
یک بلاب فقط محتویات خام یک فایل را بدون هیچ متادیتایی ذخیره میکند. یک درخت مانند یک دایرکتوری عمل میکند و لیستی از اشارهگرها به بلابها و سایر درختها را به همراه نام فایلها و مجوزهای مرتبط با آنها ذخیره مینماید.
بررسی عمیق دیدگاهها
دیدگاه انتزاعی
توسعهدهندگان روزمره چگونه با گیت به عنوان یک خط زمانی از تغییرات تعامل دارند.
برای اکثریت قریب به اتفاق مهندسان نرمافزار، تجربه کار با گیت از طریق دستورات سطح بالای آن (porcelain) — انتزاعاتی مانند git pull، git commit و git rebase — شکل میگیرد. از این منظر، به نظر میرسد سیستم در حال ردیابی تفاوت (delta) بین فایلهاست و یک نسخه پایه و لیستی متوالی از ویرایشها را ذخیره میکند. این مدل ذهنی برای همکاری روزمره بسیار مؤثر است، زیرا با نحوه تفکر انسانها درباره ویرایش اسناد در طول زمان همخوانی دارد، حتی اگر مکانیک ذخیرهسازی زیربنایی را به طور بنیادین اشتباه نشان دهد.
دیدگاه ذخیرهسازی
مهندسان سیستم چگونه گیت را به عنوان یک پایگاه داده تغییرناپذیر کلید-مقدار درک میکنند.
در پسِ رابط کاربری، گیت دقیقاً به عنوان یک سیستم فایل محتوا-محور عمل میکند. در این دیدگاه، هیچ مفهومی از «تاریخچه» یا «تغییرات» وجود ندارد — تنها اسنپشاتهای ایستا و تغییرناپذیری از دادهها وجود دارند که با هشهای SHA-1 خود شناسایی میشوند. وقتی فایلی اصلاح میشود، گیت تفاوت را ذخیره نمیکند؛ بلکه یک بلاب کاملاً جدید ذخیره کرده و یک درخت جدید برای ارجاع به آن میسازد. این رویکرد، بار پردازشی در طول تولید تفاوتها (diff) را فدای یکپارچگی مطلق دادهها و بازیابی آنی اسنپشاتها میکند.
دیدگاه نظریه گراف
دانشمندان علوم کامپیوتر چگونه تاریخچه گیت را به عنوان یک گراف جهتدار غیرمدور تحلیل میکنند.
از منظر ریاضی، یک مخزن گیت یک گراف جهتدار غیرمدور (DAG) کلاسیک است. کامیتها گرهها هستند و ارجاعات والد، یالهای جهتدار محسوب میشوند. از آنجا که هویت هر گره (هش آن) به صورت رمزنگاریشده از محتوای آن و هویت والدش استخراج میشود، این گراف کاملاً غیرمدور و فقط-افزودنی (append-only) است. همین ویژگی ریاضی است که کنترل نسخه توزیعشده را ممکن میسازد، زیرا تضمین میشود که هر دو گره با هش یکسان، دقیقاً گراف اجدادی یکسانی را به اشتراک میگذارند و به سیستمهای مجزا اجازه میدهند تاریخچهها را بدون هماهنگی متمرکز ادغام کنند.
- کاربران سطح بالا
- بر رابط کاربری سطح بالای کنترل نسخه و تفاوتهای زمانی (diffs) تمرکز دارد.
- مهندسان زیربنایی
- بر ذخیرهسازی محتوا-محور و پایگاه داده اشیای تغییرناپذیر تمرکز دارد.
- نظریهپردازان گراف
- بر ویژگیهای ریاضی گراف جهتدار غیرمدور و اجماع توزیعشده تمرکز دارد.
دیدگاههایی که این گزارش پوشش نداده
- معماران سیستمهای کنترل نسخه جایگزین
- نگهدارندگان سیستم ذخیرهسازی فایلهای حجیم (LFS)
منابع
[1]Git SCMمهندسان زیربنایی10.2 Git Internals - Git Objects
مطالعه در Git SCM →
[2]GeeksforGeeksکاربران سطح بالاGit Internals
مطالعه در GeeksforGeeks →
[3]Substackمهندسان زیربناییA Nibble of Git's Object Store
مطالعه در Substack →
[4]Mediumکاربران سطح بالاUnderstanding the Magic Behind Git: A Deep Dive into Git Internals
مطالعه در Medium →
[5]Career Dastakنظریهپردازان گرافBeyond Commit and Push: A Deep Dive into Git Internals (Blobs, Trees, and Refs)
مطالعه در Career Dastak →
[6]تیم سردبیری کوهستاننظریهپردازان گرافتحلیل تیم سردبیری کوهستان
مطالعه در تیم سردبیری کوهستان →
بیشتر در فناوری
مشاهده همه →ایپیآی عاملها
اوپنایآی در رویداد DevDay 2026 از کُدکس ابری و ایپیآی کنترل رایانه رونمایی کرد
7 منبع
مقررات هوش مصنوعی
طرح قانون جدید آمریکا برای ممنوعیت هوش مصنوعی فوقهوشمند (ASI) و توقف توسعه پیشرفته آن
3 منبع
پیچیدگی محاسباتی
سد پیچیدگی P در برابر NP: چرا بررسی یک راهحل بهطور تصاعدی سریعتر از یافتن آن است؟
6 منبع
ساختار داده
چرا هشمپها بهطور پیشفرض روی ضریب بار ۰٫۷۵ تنظیم میشوند و چه زمانی باید آن را تغییر داد
7 منبع
نظرات
هر زاویه. هر روز.
اخبار فناوری با پوشش کامل منابع و تحلیل دیدگاهها، هر روز و رایگان.





