GreatBahram
GreatBahram
خواندن ۱۰ دقیقه·۵ سال پیش

یک‌بار برای همیشه - گیت 1

Post logo
Post logo

معرفی

توی این سری قصد داریم، راجعبه گیت صحبت کنیم. و فرض‌مونم اینِ که تا حدودی (تقریباً متوسط) با گیت آشنا هستید و با یک سری از مفاهیم پیچیده گیت مساله دارید.

توی این مطلب، قصد داریم تا جایی که میشه روند ذخیره‌سازی و مدیریت فایل‌ها توسط گیت رو بررسی کنیم. برای این‌کار سری به فایل‌ها و آبجکت‌هایی که گیت استفاده می‌کنه می‌زنیم.

مقدمه

تمام کارهای باحالی که گیت انجام میده داخل یک دایرکتوری خاص انجام میشه که اسمش git. هست. وقتی شما یک پروژه رو با گیت ایجاد می‌کنید این دایرکتوری به پروژه شما اضافه میشه و زیرساخت‌های لازم، واسه گیت رو فراهم می‌کنه:

How does git change our working directory?
How does git change our working directory?

میشه اینجوری هم گفت که داشتن این دایرکتوری این امکان رو به ما میده که تمام فایل‌های دیگه‌ای که گیت مدیریت می‌کنه رو از روی اون بسازیم(بازیابی کنیم) و حذف این دایرکتوری هم مساوی هست با حذف تمام تاریخچه‌ی کامیت‌ها!

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

Let's examine content of .git directory!
Let's examine content of .git directory!

خب از چیزهای راحت شروع کنیم:

  • فایل description که یک متن داخل اون وجود داره و برای نمایش توی وب استفاده میشه. مثل متن پایین که هایلات شده:
The purpose of description file.
The purpose of description file.
  • فایل config، که تنظیمات مربوط به پروژه رو برای شما نگه‌داری می‌کنه (Project specific configuration). به صورت پیش‌فرض مقادیر زیر رو داره:
The default content of .git/config file
The default content of .git/config file
  • دایرکتوری info، یک فایلی داره به اسم exclude که دقیقا مشابه gitignore. فایل هست و امکان جلوگیری از track کردن گیت از اون فایل‌ها یا patternها رو میده. فرق‌اش با gitignore. توی این هست که توسط گیت track نمیشه یا جایی نمیشه share اش کرد.
  • دایرکتوری hooks: اینجا می‌تونیم یک‌سری اسکریپت به گیت معرفی کنیم که قبل/بعد از یک عمل خاص اجرا بشن. مثلاً قبل از هر کامیت چک کنیم که متن کامیت خالی نباشه. خود گیت‌ هم چندین sample داخل این دایرکتوری داره و احتمالاً در آینده یک مطلب راجعبه این بخش خواهیم داشت:
List of built-in git hooks
List of built-in git hooks
  • فایل HEAD: که در حالت نرمال به branch فعلی اشاره می‌کنه، اگر محتواش رو چک کنیم:
Let me see you Head!
Let me see you Head!

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

I want to see inside of you
I want to see inside of you

گیت خطا میده، چون که ما توی این مخزن هیچ کامیتی نداشتیم، اگر یک کامیت به این برنچ اضافه کنیم:

What Git does after committing a single-empty file.
What Git does after committing a single-empty file.

می‌بینیم که داخل اون فایل، هشِ یک کامیت ذخیره شده.

نتیجه‌گیری: فایل HEAD در حالت نرمال به branch فعلی ما اشاره می‌کنه و برنچ فعلی ما هم یک اشاره‌گره به آخرین کامیت هست و هر بار که یک کامیت به مخزن استفاده کنیم این متن با متن جدید جایگزین میشه. میشه هم نتیجه گرفت که برنچ‌ها یک اشاره‌گری هستند که همیشه قابل بروزرسانی/جابجایی هستند.

در ادامه دایرکتوری objects رو بررسی می‌کنیم.

بررسی کامیت

احتمالا این گزاره رو شنیدید که گیت برخلاف ورژن کنترلرهای دیگه تغییرات رو ذخیره نمی‌کنه بلکه در هر

کامیت، یک اسنپ‌شات از کل فایل ها رو نگه می‌داره.

Contrast Subversion and Git
Contrast Subversion and Git

مطابق عکس بالا (که یک ورژن کنترلر متمرکز رو با گیت مقایسه کرده)، گیت تفاوت‌ها در هر نسخه ذخیره نکرده، بلکه در هر کامیت دقیقا خود فایل ذخیره شده. در حقیقت هر بار که شما یک تغییری توی فایلها ایجاد می‌کنید و کامیت می‌کنید، دو کار اتفاق میفته:

  1. اول گیت چک می‌کنه و اگر چیزی تغییر کرده باشه، اون رو فشرده می‌کنه و اون رو داخل دایرکتوری objects ذخیره میکنه و اسم اون (که معادل همون هش‌اش هست) به اسنپ‌شات اضافه میکنه و نهایتاً خود این اسنپ‌شات هم فشرده سازی میشه و به اون یک هش برای ارجاع دادن (رفرنس) اختصاص داده میشه.
  2. اگر هم فایلی تغییر نکرده باشه، گیت هش قبلی اون فایل رو توی اسنپ شات فعلی ذخیره می‌شه. تا دیگه نیازی به ذخیره مجدد اون نباشه.

خب بریم دایرکتوری objects رو با هم چک کنیم:

Examine .git/objects directory
Examine .git/objects directory

این حاصل کاری هست که گیت فقط برای اضافه کردن یک فایل انجام داده. اما ما فقط یک فایل اضافه کردیم چرا سه تا هش اینجا وجود داره؟

بررسی آبجکت‌های گیت

گیت یک فایل‌سیستم برای خودش داره، توی ادبیات اون به جای دایرکتوری گیت از یک آبجکت به اسم tree استفاده می‌کنه. داخل هر tree یا treeهای دیگه‌ قرار دارند یا یک‌سری فایل هست، دقیقا مشابه دایرکتوری. و همین‌طور برای ذخیره‌سازی فایل از یک آبجکت به اسم blob استفاده می‌کنه. گیت آبجکت‌های دیگه‌ای هم داره:

  • آبجکت commit که به یک tree اشاره می‌کنه (دایرکتوری اصلی پروژه) و نشون میده که در اون لحظه محتوای پروژه به چه صورت بوده و یک سری metadata هم داخل اون برای ذخیره سازی پارامترهایی مثل اسم commiter و زمان و اشاره‌گری به کامیت‌های قبلی استفاده میشه.
  • آبجکت tag که برای اسم‌گذاری یک کامیت خاص به کار می‌ره و معمولاً برای اسم‌گذاری releaseهای مختلف ازش استفاده میشه.

اگر بخواهیم از دید گیت به مخزن‌مون نگاه کنیم، دستور git ls-tree این امکان رو به ما میده:

Output of git ls-tree master .
Output of git ls-tree master .

خروجی بالا به ما نشون می‌ده که تا الان فقط یک فایل (blob) داشتیم. اگر پروژه‌ی ما شامل یک دایرکتوری (tree) هم بود، گیت اون رو اینجوری ذخیره می‌کرد:

What object git uses to save a directory
What object git uses to save a directory

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

Generate sha1 checksum similar to git
Generate sha1 checksum similar to git

برای کنجکاوی بیشتر می‌تونیم به دایرکتوری objects بریم و از خود گیت بپرسیم که هر آبجکت اونجا نماینگر چه چیزی هست. اما قبل از اینکه‌ این کار رو بکنیم بذارید یک جمع‌بندی از ساختار فعلی‌مون داشته باشیم. توی مخزن ما، یک دایرکتوری اصلی (test_git) داریم که توی اون فقط یک فایل خالی وجود داره و فقط هم یک‌ کامیت داریم. در نتیجه گیت باید همه‌ی اینارو الان داشته باشه:

Determine type of each git object
Determine type of each git object

وقتی لیست آبجکت‌های رو با کمک find می‌گیریم سه آبجکت اونجا حضور داره. بنظر می‌رسه که همه‌چی همون‌جوری که حدس می‌زدیم هست.

فقط ممکن هست متوجه این نکته بشید که هش ها به دو بخش تقسیم شدن، دو کاراکتر اولش اسم دایرکتوری شده و مابقی اسم اون آبجکت مورد نظر هست. دلیل این کار هم این هست که اکثر فایل‌سیستم‌ها به شما اجازه ذخیره بیش از مقداری رو نمی‌دن و اگر گیت این کار رو نمی‌کرد خیلی زود با فایل‌سیستم‌ِ (هر؟) سیستم‌عاملی به مشکل می‌خورد.

فراموش هم نمی‌کنیم که تمام فایل‌های داخل این دایرکتوری به صورت فشرده ذخیره شدن:

We shouldn't forget that there are compressed
We shouldn't forget that there are compressed

برای اینکه بتونیم محتوای این آبجکت‌ها رو ببینیم، بهترین ابزار دستور cat-file هست، که با آپشن t- امکان تشخیص نوع اون آبجکت رو به ما میده.

آخرین کنجکاوی ما بررسی ساختار یک کامیت هست. هر کامیت هم باید مشخص کنه داخل کدوم دایرکتوری (tree) قرار داره و باید مشخص باشه که چه کسی اون نویسنده و committerاش کی بوده و در آخر باید یک متن کامیت هم داشته باشه.

با کمک ابزار cat-file محتوای آبجکت commit رو بررسی می‌کنیم:

What's going on inside git commit object!
What's going on inside git commit object!

به نظر می‌رسه هر کامیت، مشخصات خودش رو می‌دونه به همراه آدرس یک tree که داخل اون فایل‌های مربوط به قرار دارند.

یک جمله‌ی اول این پست گفتیم که گیت در هر کامیت یک snapshot از فایل‌ها داره نه diff اونا. بیاید این رو هم با هم دیگه چک کنیم.

برای این‌کار هم دو کامیت به مخزن بالا اضافه می‌کنیم.

اول محتوای فایل اول‌مون رو تغییر می‌دیم:

Change is always good, as nature does it all the time!
Change is always good, as nature does it all the time!

بعدش یک فایل‌ دیگه به مخزن‌مون اضافه می‌کنیم.

حالا باید توی کامیت کامیت سوم، فایل first_file دوباره ذخیره نشده باشه یعنی توی کامیت سوم هش first_file وجود داشته باشه، ثانیاً چیزی شبیه خروجی diff نباید ببینیم بلکه محتوای خود فایل باشه. پس تا الان سه تا کامیت داریم:

List of commits in our project
List of commits in our project

پایین حاصل بررسی این دو کامیت و محتوای tree هر کدوم رو می‌بینیم:

Lets get deep into two different commit objects
Lets get deep into two different commit objects

اگر دقت کنیم می‌بینیم که هش first_file توی هر دو تا tree یکی هست و محتوای blobها هم به شرح زیر است:

Compare content of two different
Compare content of two different

آبجکت تگ

حالا که پروژه‌مون حسابی بزرگ شده، بیاید یگ تگ هم بهش بزنیم. تگ‌های راهی ساده برای اسم‌گذاری کامیت‌ها هستند و اکثر توسعه‌دهنده ها از اون برای مشخص کردن هر release استفاده می‌کنند.

Tag our project
Tag our project


به محض اجرا شدن، دستور بالا گیت یک اشاره‌گر (رفرنس) می‌سازه که داخل اون هش کامیت مورد نظر رو نگه داری می‌کنه:

همین‌طور گیت یک آبجکت جدید داخل دایرکتوری git./objects اضافه می‌کنه که این آبجکت اطلاعات لازم راجعبه اون تگ رو مثل چه کسی تگ‌اش کرده، تگ به کدوم کامیت بوده و ... رو نگه‌داری می‌کنه:

How git saves a tag object
How git saves a tag object

بنظر می‌رسه تگ‌ها مشابه برنچ‌ها هستند. دو‌تاشون یک اشاره‌گر به کامیت مورد نظر دارند. با این تفاوت که تگ‌ها بر خلاف‌ برنچ‌ها امکان جابجایی یا به روزرسانی ندارند.

جمع‌بندی

هدف از این مطلب، افزایش درک ما نسبت به روند کار گیت بود و یاد گرفتیم که گیت محتوای ما رو فشرده می‌کنه و برای ارجاع به هر چیزی از تابع هشِ sha1 استفاده می‌کنه. همین‌طور دیدیم که گیت چطور با استفاده از چهار آبجکت این کار رو انجام میده. آبجکت tree که معادل همون دایرکتوریِ و آبجکت blob که گفتیم برای ذخیره سازی محتوای فایل‌های به کار می‌ره و آبجکت commit رو دیدیم که اطلاعات هر کامیت رو نگه داری می‌کرد و نهایتاً آبجکت tag رو در آخر با هم بررسی کردیم.

بروزرسانی

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

امیدوارم که از این مطلب چیزی یاد گرفته باشید و اگر خوش‌تون اومده اون رو با دوستان و همکارای خودتون به اشتراک بذارید.

از اینجا کجا بریم؟

  • اگر علاقه‌مند هستید که بیشتر راجعبه مباحث بالا بدونید، داکیومنت‌های خود گیت بهترین مرجع توی این حوزه هستند:

Git Objects

  • اگر این برای شما سوال هست که ذخیره کردن کل فایل در مقابل Changesetها کدوم‌اش موثرتر هست، لینک پایین شروع خوبی می‌تونه باشه:

How does git store files?

  • من راجعبه گیت یک ارائه داشتم، اسلاید‌های این ارائه هم در آدرس پایین قابل دسترسی هست:

Git tutorial slides

gitgit internalstree
Pythonista, Free Software Enthusiast. GNU/Linux Master. Network Security Researcher. Son. Brother.
شاید از این پست‌ها خوشتان بیاید