توی این سری قصد داریم، راجعبه گیت صحبت کنیم. و فرضمونم اینِ که تا حدودی (تقریباً متوسط) با گیت آشنا هستید و با یک سری از مفاهیم پیچیده گیت مساله دارید.
توی این مطلب، قصد داریم تا جایی که میشه روند ذخیرهسازی و مدیریت فایلها توسط گیت رو بررسی کنیم. برای اینکار سری به فایلها و آبجکتهایی که گیت استفاده میکنه میزنیم.
تمام کارهای باحالی که گیت انجام میده داخل یک دایرکتوری خاص انجام میشه که اسمش git. هست. وقتی شما یک پروژه رو با گیت ایجاد میکنید این دایرکتوری به پروژه شما اضافه میشه و زیرساختهای لازم، واسه گیت رو فراهم میکنه:
میشه اینجوری هم گفت که داشتن این دایرکتوری این امکان رو به ما میده که تمام فایلهای دیگهای که گیت مدیریت میکنه رو از روی اون بسازیم(بازیابی کنیم) و حذف این دایرکتوری هم مساوی هست با حذف تمام تاریخچهی کامیتها!
اول از همه، میخوایم ببینیم داخل این دایرکتوری چه فایلهای وجود داره و کاربرد هر کدومشون چی هست:
خب از چیزهای راحت شروع کنیم:
چیزی که از متن بالا میشه فهمید، این هست که خود HEAD یک اشارهگر به یک فایل دیگه هست و آدرس اون فایل داخل HEAD قرار داره. اگر بریم اون مسیر رو بررسی کنیم:
گیت خطا میده، چون که ما توی این مخزن هیچ کامیتی نداشتیم، اگر یک کامیت به این برنچ اضافه کنیم:
میبینیم که داخل اون فایل، هشِ یک کامیت ذخیره شده.
نتیجهگیری: فایل HEAD در حالت نرمال به branch فعلی ما اشاره میکنه و برنچ فعلی ما هم یک اشارهگره به آخرین کامیت هست و هر بار که یک کامیت به مخزن استفاده کنیم این متن با متن جدید جایگزین میشه. میشه هم نتیجه گرفت که برنچها یک اشارهگری هستند که همیشه قابل بروزرسانی/جابجایی هستند.
در ادامه دایرکتوری objects رو بررسی میکنیم.
احتمالا این گزاره رو شنیدید که گیت برخلاف ورژن کنترلرهای دیگه تغییرات رو ذخیره نمیکنه بلکه در هر
کامیت، یک اسنپشات از کل فایل ها رو نگه میداره.
مطابق عکس بالا (که یک ورژن کنترلر متمرکز رو با گیت مقایسه کرده)، گیت تفاوتها در هر نسخه ذخیره نکرده، بلکه در هر کامیت دقیقا خود فایل ذخیره شده. در حقیقت هر بار که شما یک تغییری توی فایلها ایجاد میکنید و کامیت میکنید، دو کار اتفاق میفته:
خب بریم دایرکتوری objects رو با هم چک کنیم:
این حاصل کاری هست که گیت فقط برای اضافه کردن یک فایل انجام داده. اما ما فقط یک فایل اضافه کردیم چرا سه تا هش اینجا وجود داره؟
گیت یک فایلسیستم برای خودش داره، توی ادبیات اون به جای دایرکتوری گیت از یک آبجکت به اسم tree استفاده میکنه. داخل هر tree یا treeهای دیگه قرار دارند یا یکسری فایل هست، دقیقا مشابه دایرکتوری. و همینطور برای ذخیرهسازی فایل از یک آبجکت به اسم blob استفاده میکنه. گیت آبجکتهای دیگهای هم داره:
اگر بخواهیم از دید گیت به مخزنمون نگاه کنیم، دستور git ls-tree این امکان رو به ما میده:
خروجی بالا به ما نشون میده که تا الان فقط یک فایل (blob) داشتیم. اگر پروژهی ما شامل یک دایرکتوری (tree) هم بود، گیت اون رو اینجوری ذخیره میکرد:
احتمالا، شما میدونید که گیت برای هش گرفتن از sha1 استفاده میکنه، اما اگر سعی کنید هش اون رو خودتون با sha1 بگیرید میبینید که خروجیشما با گیت متفاوت هست. این به این دلیل هست که گیت به ابتدای اون یکچیزی هم اضافه میکنه:
برای کنجکاوی بیشتر میتونیم به دایرکتوری objects بریم و از خود گیت بپرسیم که هر آبجکت اونجا نماینگر چه چیزی هست. اما قبل از اینکه این کار رو بکنیم بذارید یک جمعبندی از ساختار فعلیمون داشته باشیم. توی مخزن ما، یک دایرکتوری اصلی (test_git) داریم که توی اون فقط یک فایل خالی وجود داره و فقط هم یک کامیت داریم. در نتیجه گیت باید همهی اینارو الان داشته باشه:
وقتی لیست آبجکتهای رو با کمک find میگیریم سه آبجکت اونجا حضور داره. بنظر میرسه که همهچی همونجوری که حدس میزدیم هست.
فقط ممکن هست متوجه این نکته بشید که هش ها به دو بخش تقسیم شدن، دو کاراکتر اولش اسم دایرکتوری شده و مابقی اسم اون آبجکت مورد نظر هست. دلیل این کار هم این هست که اکثر فایلسیستمها به شما اجازه ذخیره بیش از مقداری رو نمیدن و اگر گیت این کار رو نمیکرد خیلی زود با فایلسیستمِ (هر؟) سیستمعاملی به مشکل میخورد.
فراموش هم نمیکنیم که تمام فایلهای داخل این دایرکتوری به صورت فشرده ذخیره شدن:
برای اینکه بتونیم محتوای این آبجکتها رو ببینیم، بهترین ابزار دستور cat-file هست، که با آپشن t- امکان تشخیص نوع اون آبجکت رو به ما میده.
آخرین کنجکاوی ما بررسی ساختار یک کامیت هست. هر کامیت هم باید مشخص کنه داخل کدوم دایرکتوری (tree) قرار داره و باید مشخص باشه که چه کسی اون نویسنده و committerاش کی بوده و در آخر باید یک متن کامیت هم داشته باشه.
با کمک ابزار cat-file محتوای آبجکت commit رو بررسی میکنیم:
به نظر میرسه هر کامیت، مشخصات خودش رو میدونه به همراه آدرس یک tree که داخل اون فایلهای مربوط به قرار دارند.
یک جملهی اول این پست گفتیم که گیت در هر کامیت یک snapshot از فایلها داره نه diff اونا. بیاید این رو هم با هم دیگه چک کنیم.
برای اینکار هم دو کامیت به مخزن بالا اضافه میکنیم.
اول محتوای فایل اولمون رو تغییر میدیم:
بعدش یک فایل دیگه به مخزنمون اضافه میکنیم.
حالا باید توی کامیت کامیت سوم، فایل first_file دوباره ذخیره نشده باشه یعنی توی کامیت سوم هش first_file وجود داشته باشه، ثانیاً چیزی شبیه خروجی diff نباید ببینیم بلکه محتوای خود فایل باشه. پس تا الان سه تا کامیت داریم:
پایین حاصل بررسی این دو کامیت و محتوای tree هر کدوم رو میبینیم:
اگر دقت کنیم میبینیم که هش first_file توی هر دو تا tree یکی هست و محتوای blobها هم به شرح زیر است:
حالا که پروژهمون حسابی بزرگ شده، بیاید یگ تگ هم بهش بزنیم. تگهای راهی ساده برای اسمگذاری کامیتها هستند و اکثر توسعهدهنده ها از اون برای مشخص کردن هر release استفاده میکنند.
به محض اجرا شدن، دستور بالا گیت یک اشارهگر (رفرنس) میسازه که داخل اون هش کامیت مورد نظر رو نگه داری میکنه:
همینطور گیت یک آبجکت جدید داخل دایرکتوری git./objects اضافه میکنه که این آبجکت اطلاعات لازم راجعبه اون تگ رو مثل چه کسی تگاش کرده، تگ به کدوم کامیت بوده و ... رو نگهداری میکنه:
بنظر میرسه تگها مشابه برنچها هستند. دوتاشون یک اشارهگر به کامیت مورد نظر دارند. با این تفاوت که تگها بر خلاف برنچها امکان جابجایی یا به روزرسانی ندارند.
هدف از این مطلب، افزایش درک ما نسبت به روند کار گیت بود و یاد گرفتیم که گیت محتوای ما رو فشرده میکنه و برای ارجاع به هر چیزی از تابع هشِ sha1 استفاده میکنه. همینطور دیدیم که گیت چطور با استفاده از چهار آبجکت این کار رو انجام میده. آبجکت tree که معادل همون دایرکتوریِ و آبجکت blob که گفتیم برای ذخیره سازی محتوای فایلهای به کار میره و آبجکت commit رو دیدیم که اطلاعات هر کامیت رو نگه داری میکرد و نهایتاً آبجکت tag رو در آخر با هم بررسی کردیم.
بروزرسانی
از اینجا میتونید قسمت دوم این سری رو هم بخونید که بیشتر به مباحت نسبتا پیچیده گیت پرداخته شده و طبق معمول هدفمون هم توضیح او مسائل به زبان ساده هست.
امیدوارم که از این مطلب چیزی یاد گرفته باشید و اگر خوشتون اومده اون رو با دوستان و همکارای خودتون به اشتراک بذارید.