Chapters ▾ 2nd Edition

3.1 انشعاب‌گیری در گیت (Git Branching) - شاخه‌ها در یک نگاه (Branches in a Nutshell)

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

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

شاخه‌ها در یک نگاه (Branches in a Nutshell)

برای درک واقعی نحوه‌ی شاخه‌بندی در گیت، باید یک قدم به عقب برداریم و بررسی کنیم گیت داده‌های خود را چگونه ذخیره می‌کند.

همانطور که ممکن است از گیت چیست؟ (What is Git?) به یاد داشته باشید، گیت داده‌ها را به صورت یک سری تغییرات یا تفاوت‌ها ذخیره نمی‌کند، بلکه به صورت یک سری تصویر لحظه‌ای ذخیره می‌کند.

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

برای تجسم این موضوع، فرض کنید دایرکتوری‌ای دارید که شامل سه فایل است و همه را آماده کرده و کامیت می‌کنید. آماده کردن فایل‌ها یک مقدار چکسام برای هر فایل محاسبه می‌کند (هش SHA-1 که در گیت چیست؟ (What is Git?) ذکر شد)، آن نسخه از فایل را در مخزن گیت ذخیره می‌کند (گیت به آنها blob می‌گوید) و آن چکسام را به منطقه آماده‌سازی اضافه می‌کند:

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

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

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

A commit and its tree
نمودار 9. A commit and its tree

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

Commits and their parents
نمودار 10. Commits and their parents

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

یادداشت

شاخه “master” در گیت شاخه‌ای ویژه نیست. این دقیقاً مانند هر شاخه دیگری است. تنها دلیل اینکه تقریباً هر مخزنی یکی دارد این است که دستور git init آن را به طور پیش‌فرض ایجاد می‌کند و بیشتر افراد زحمت تغییر آن را به خود نمی‌دهند.

A branch and its commit history
نمودار 11. A branch and its commit history

ایجاد یک شاخه جدید (Creating a New Branch)

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

$ git branch testing

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

Two branches pointing into the same series of commits
نمودار 12. Two branches pointing into the same series of commits

چگونه گیت می‌داند شما الان روی کدام شاخه هستید؟ گیت یک اشاره‌گر ویژه به نام HEAD نگه می‌دارد. توجه داشته باشید که این بسیار متفاوت از مفهوم HEAD در سایر سیستم‌های کنترل نسخه مانند Subversion یا CVS است. در گیت، این اشاره‌گری به شاخه محلی است که در حال حاضر روی آن هستید. در این مورد، شما هنوز روی master هستید. دستور git branch فقط یک شاخه جدید ایجاد کرد — ولی به آن شاخه سوئیچ نکرد.

HEAD pointing to a branch
نمودار 13. HEAD pointing to a branch

شما می‌توانید این موضوع را با اجرای دستور ساده 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 قرار دارند.

تغییر شاخه (Switching Branches)

برای تغییر به یک شاخه موجود، دستور git checkout را اجرا کنید. بیایید به شاخه جدید testing سوئیچ کنیم:

$ git checkout testing

این باعث می‌شود HEAD به شاخه testing اشاره کند.

HEAD points to the current branch
نمودار 14. HEAD points to the current branch

اهمیت این چیست؟ خب، بیایید یک کامیت دیگر بزنیم:

$ vim test.rb
$ git commit -a -m 'Make a change'
The HEAD branch moves forward when a commit is made
نمودار 15. The HEAD branch moves forward when a commit is made

این جالب است، چون اکنون شاخه testing جلو رفته اما شاخه master هنوز به کامیتی اشاره دارد که هنگام اجرای git checkout روی آن بودید. بیایید برگردیم به شاخه master:

$ git checkout master
یادداشت
git log doesn’t show all the branches all the time

اگر همین الان دستور git log را اجرا کنید، ممکن است تعجب کنید که شاخه “testing” که تازه ساختید کجا رفته است، چون در خروجی نمایش داده نمی‌شود.

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

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

HEAD moves when you checkout
نمودار 16. HEAD moves when you checkout

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

یادداشت
Switching branches changes files in your working directory

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

بیایید چند تغییر ایجاد کنیم و دوباره کامیت بزنیم:

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

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

Divergent history
نمودار 17. Divergent history

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

$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) Make other changes
| * 87ab2 (testing) Make 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

| چون یک شاخه در گیت در واقع یک فایل ساده است که مقدار چکسام 40 حرفی SHA-1 کامیتی که به آن اشاره دارد را نگه می‌دارد، ایجاد و حذف شاخه بسیار کم هزینه است. ساختن یک شاخه جدید همانقدر سریع و ساده است که 41 بایت (40 کاراکتر و یک خط جدید) را در یک فایل بنویسیم.

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

بیایید ببینیم چرا باید این کار را انجام دهید

یادداشت
Creating a new branch and switching to it at the same time

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

یادداشت

از نسخه 2.23 گیت به بعد می‌توانید از git switch به جای git checkout استفاده کنید برای:

  • سوئیچ به شاخه موجود: git switch testing-branch.

  • ساختن یک شاخه جدید و سوئیچ به آن: git switch -c new-branch. گزینه -c مخفف create است، همچنین می‌توانید گزینه کامل آن یعنی: --create استفاده کنید.

  • بازگشت به شاخه‌ای که قبلاً روی آن بودید: git switch -.

scroll-to-top