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.
Figure 8. یک کامیت و درختش

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

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

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

Note

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

A branch and its commit history.
Figure 10. یک برنچ و تاریخچهٔ کامیت‌هایش

ساختن یک برنچ جدید

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

$ git branch testing

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

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

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

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

به سادگی هر چه تمام شما می‌توانید با اجرای دستور 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.
Figure 13. هد به برنچ فعلی اشاره می‌کند

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Divergent history.
Figure 16. تاریخچه دوشاخه شده

علاوه بر آن، با دستور 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 قدیمی است که شامل کپی کردن تمام فایل‌های پروژه به یک پوشه ثانوی می‌باشد. این می‌تواند چندین ثانیه یا حتی دقیقه، بسته به سایز پروژه، طول بکشد؛ درحالی که در گیت این فرآیند همیشه آنی است. همچنین به علت اینکه هنگامی که کامیت می‌کنیم والد را هم در کامیت ثبت می‌کنیم، پیدا کردن یک پایهٔ ادغام برای مرج کردن به صورت خودکار برای ما انجام می‌شود و به طور کل به سهولت انجام می‌پذیرد. معمولاً مزایا توسعه‌دهندگان را به ساختن و استفاده از برنچ‌ها مشتاق می‌کند.

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

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

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