Git
Chapters ▾ 2nd Edition

3.1 شاخه‌سازی در گیت - شاخه‌ها در یک کلمه

تقریباً همهٔ VCSها نوعی پشتیبانی از شاخه‌سازی دارند. شاخه یا شعبه‌سازی (Branching) به معنی این است که شما از مسیر اصلی توسعه جدا شده و به ادامهٔ کار، بدون خرابی به بار آوردن در مسیر اصلی بپردازید. در بسیاری از VCSها این فرآیندی کم‌وبیش پرهزینه است. اغلب شما را مجبور می‌کند که کپی جدیدی از پوشه سورس کدتان بگیرید که در پروژه‌های عظیم وقت زیادی را می‌گیرد.

برخی مدل شاخه‌سازی گیت را «خفن‌‌ترین ویژگی» گیت می‌داند و به طور قطع این ویژگی جایگاه خاصی به گیت در جامعه VCS داده است. چرا این ویژگی اینچنین خاص است؟ طریقه شاخه‌سازی در گیت بسیار سبک‌وزن است به گونه‌ای که آنرا تقریباً بلادرنگ می‌کند، همچنین رفت‌وآمد بین شاخه‌ها هم معمولاً به همان اندازه سریع است. بی‌تشابه به اکثر دیگر VCSها، گیت روند کاری را به شاخه‌شدن و مرج شدن سوق می‌دهد، حتی به اندازه‌ای که چند بار در روز این کار انجام شود. فهم درست و خبره شدن در این قابلیت به شما ابزار یکتا و قدرتمندی می‌دهد و می‌تواند به کلی نحوه توسعه‌دادن کارتان را تغییر دهد.

شاخه‌ها در یک کلمه

برای اینکه به راستی نحوهٔ شاخه‌سازی گیت را درک کنید، باید یک قدم به عقب برگردیم و نحوهٔ ثبت اطلاعات گیت را مطالعه کنیم.

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

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

برای ملموس کردن این موضوع، فرض را بر این بگذاریم که پوشه‌ای حاوی سه فایل داریم و شما همهٔ آنها را استیج و کامیت می‌کنید. استیج کردن فایل‌های برای هر کدام از آنها یک چک‌سام (هش SHA-1 که درشروع به کار ذکر کردیم) محاسبه می‌کند، آن نسخه از فایل را در مخزن گیت ذخیره می‌کند، (گیت از این فایل‌های با نام بلاب یاد می‌کند) و آن چک‌سام را به استیج اضافه می‌کند:

$ git add README test.rb LICENSE
$ git commit -m 'Initial commit'

وقتی کامیتی را با اجرای git commit می‌سازید، گیت همهٔ زیرپوشه‌ها (در مثال فقط روت پروژه) را چک‌سام می‌کند و آن‌ها را به عنوان یک آبجکت درخت در مخزن گیت ذخیره می‌کند. سپس گیت یک کامیت آبجکت که متادیتا و نشانگری که به روت درخت پروژه دارد می‌سازد تا بتواند اسنپ‌شات پروژه را هنگامی که نیاز بود بازسازی کند.

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

A commit and its tree.
نمودار 9. یک کامیت و درختش

اگر تغییری ایجاد کنید و دوباره کامیت بگیرید، کامیت بعدی نشانگری را به کامیتی که دقیقاً قبل از آن آمده در خود ذخیره می‌کند.

Commits and their parents.
نمودار 10. کامیت‌ها و والدینشان

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

یادداشت

برنچ «master» در گیت برنچ خاصی نیست. این برنچ دقیقاً مشابه هر برنچ دیگری است. تنها دلیلی که تقریباً همهٔ مخزن‌ها یک برنچ با این نام دارند این است که دستور git init به طور پیش‌فرض آنرا می‌سازد و بیشتر مردم هم زحمت تغییر این نام را به خود نمی‌دهند.

A branch and its commit history.
نمودار 11. یک برنچ و تاریخچهٔ کامیت‌های آن

ساختن یک شاخه جدید

وقتی یک برنچ جدید می‌سازید چه اتفاقی می‌افتد؟ انجام این کار یک نشانگر جدید برای شما می‌سازد تا آنرا به این سو و آن سو انتقال دهید. فرض بر این بگذاریم که می‌خواهید برنچ جدیدی با نام testing بسازید. شما این کار را با دستور git branch انجام می‌دهید:

$ git branch testing

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

Two branches pointing into the same series of commits.
نمودار 12. دو برنچ که به یک دسته از کامیت‌ها اشاره می‌کنند

چگونه گیت می‌داند که روی چه برنچی کار می‌کنید؟ گیت نشانگر خاصی به نام HEAD (هد) را در خود دارد. به خاطر داشته باشید این مفهوم تفاوت زیادی با مفهوم HEAD در دیگر VCSها، مانند ساب‌ورژن یا CVS، دارد که ممکن است از پیشتر به یاد داشته باشید. در گیت، هد نشانگری است که به برنچ محلی که روی آن هستید اشاره می‌کند. در مثالی که روی آن کار می‌کنیم شما هنوز روی master هستید. دستور git branch فقط یک برنچ جدید ساخت — به برنچ جدید نقل مکان نکرد.

HEAD pointing to a branch.
نمودار 13. هد که به یک برنچ اشاره می‌کند

به سادگی هر چه تمام شما می‌توانید با اجرای دستور git log، که به شما نشان می‌دهد چه برنچی به کجا اشاره دارد، این موضوع را بررسی کنید. این آپشن --decorate خوانده می‌شود.

$ git log --oneline --decorate
f30ab (HEAD -> master, testing) Add feature #32 - ability to add new formats to the central interface
34ac2 Fix bug #1328 - stack overflow under certain conditions
98ca9 Initial commit

شما می‌توانید برنچ‌های master و testing را که دقیقاً کنار کامیت f30ab هستند ببینید.

تعویض شاخه‌ها

برای تعویض یا جابه‌جایی به یک برنچ از پیش ساخته شده دستور git checkout را اجرا می‌کنید. حال بیایید به برنچ جدید testing برویم:

$ git checkout testing

این کار HEAD را جابه‌جا می‌کند تا به برنچ testing اشاره کند.

HEAD points to the current branch.
نمودار 14. هد به برنچ فعلی اشاره می‌کند

تأثیر این کار در چه بود؟ خب، بیایید یک کامیت دیگر انجام دهیم:

$ vim test.rb
$ git commit -a -m 'made a change'
The HEAD branch moves forward when a commit is made.
نمودار 15. هنگامی که کامیتی ساخته می‌شود برنچ هد به جلو می‌رود

این جالب است چرا که الآن برنچ testing به جلو رفت در حالی که برنچ master هنوز به کامیتی، که سابقاً روی آن git checkout که برای تعویض برنچ‌ها استفاده شد، اشاره می‌کند. بیایید دوباره به برنچ master بازگردیم:

$ git checkout master
یادداشت
git log همیشه، همهٔ برنچ‌ها را نمایش نمی‌دهد.

اگر الآن git log را اجرا می‌کردید ممکن بود از اینکه برنچ «testing»ـی که همین الآن ساختید کجا رفت متعجب شوید، چرا که قاعدتاً نباید در خروجی نمایش داده شود.

برنچ مذکور غیب نشده است؛ صرفاً گیت گمان نمی‌کند که شما علاقه‌ای به دانستن اطلاعات آن برنچ دارید و سعی می‌کند مطالبی که به نظرش برای شما مفید است را به شما نشان دهد. به بیان دیگر، در حالت پیش‌فرض، git log فقط تاریخچهٔ کامیت‌های زیر برنچی که شما چک‌اوت کرده‌اید را نمایش می‌دهد.

برای نمایش تاریخچهٔ کامیت‌های برنچ مورد نظر باید به صراحت آن را نام ببرید: git log testing. برای نمایش تمام برنچ‌ها --all را به دستور git log خود بی‌افزایید.

HEAD moves when you checkout.
نمودار 16. وقتی چک‌اوت می‌کنید هد جابه‌جا می‌شود

آن دستور دو کار انجام داد. نشانگر هد را بازگرداند تا به برنچ master اشاره کند و فایل‌هایی که در پوشه کاری شما بودند را به اسنپ‌شاتی که master به آن اشاره می‌کرد بازگردانی کرد. این همچنین به این معنا است که تغییراتی که از این نقطه به بعد اعمال کنید از نسخه‌های قدیمی‌تر پروژه جدا خواهد ماند. در ساده‌ترین تعریف، کاری که در برنچ testing کردید را خنثی می‌کند تا بتوانید راه دیگری را در پیش بگیرید.

یادداشت
تعویض برنچ فایل‌های درون پوشه کاری را تغییر می‌دهد

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

بیایید چند تغییر اعمال کنیم و دوباره کامیت بگیریم:

$ vim test.rb
$ git commit -a -m 'made other changes'

اکنون تاریخچه پروژه شما دوشاخه شده است (به تاریخچه دوشاخه شده مراجعه کنید). شما یک برنچ ساختید و به آن انتقال پیدا کردید، کمی روی آن کار کردید و سپس به برنچ اصلی خود بازگشتید و آنجا کمی کار متفاوت انجام دادید. هر دوی آن تغییرات به صورت ایزوله در برنچ‌های خودشان موجوداند: شما می‌توانید بین برنچ‌ها جابه‌جا شوید و هرگاه آماده بودید آنها را با هم مرج کنید. و جالبتر اینکه همهٔ این کارها را صرفاً با دستورهای branch، checkout و commit انجام دادید.

Divergent history.
نمودار 17. تاریخچه دوشاخه شده

علاوه بر آن، با دستور git log می‌توانید این اطلاعات را ببینید. اگر git log --oneline --decorate --graph --all را اجرا کنید، برنامه تاریخچهٔ کامیت‌های شما را نمایش می‌دهد، نشان می‌دهد که نشانگرهای برنچ‌هایتان کجاست و چگونه تاریخچهٔ شما دو شاخه شده است.

$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) Made other changes
| * 87ab2 (testing) Made a change
|/
* f30ab Add feature #32 - ability to add new formats to the central interface
* 34ac2 Fix bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project

از این جهت که برنچ در گیت صرفاً یک فایل سادهٔ محتوی یک چک‌سام SHA-1 چهل حرفی کامیتی است که به آن اشاره می‌کند، ساختن و از بین بردن برنچ‌ها کم هزینه است. ساختن یک برنچ جدید به سادگی و سرعت نوشتن ۴۱ بایت اطلاعات درون یک فایل است (۴۰ حرف و یک خط جدید).

این قضیه بسیار متفاوت با نحوهٔ برنچ‌سازی بیشتر ابزارهای VCS قدیمی است که شامل کپی کردن تمام فایل‌های پروژه به یک پوشه ثانوی می‌باشد. این می‌تواند چندین ثانیه یا حتی دقیقه، بسته به سایز پروژه، طول بکشد؛ درحالی که در گیت این فرآیند همیشه آنی است. همچنین به علت اینکه هنگامی که کامیت می‌کنیم والد را هم در کامیت ثبت می‌کنیم، پیدا کردن یک پایهٔ ادغام برای مرج کردن به صورت خودکار برای ما انجام می‌شود و به طور کل انجام آن بسیار آسان است. این مزایا توسعه‌دهندگان را به ساختن و استفاده بیشتر از برنچ‌ها مشتاق می‌کند.

بیایید به اینکه چرا شما نیز باید این کار را کنید نگاهی بیاندازیم.

یادداشت
ساختن یک برنچ جدید و انتقال به آن در آن واحد

خیلی اوقات پیش می‌آید که یک برنچ جدید بسازید و بخواهید در آن واحد به آن انتقال یابید — این کار در یک عملیات با git checkout -b <newbranchname> قابل انجام است.

scroll-to-top