Git
Chapters ▾ 2nd Edition

3.2 شاخه‌سازی در گیت - شاخه‌سازی و ادغام مقدماتی

شاخه‌سازی و ادغام مقدماتی

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

  1. کمی کار روی یک پروژه وبسایت کنید.

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

  3. روی آن برنچ کمی کار کنید.

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

  1. به برنچ پروداکشن خود بروید.

  2. یک برنچ برای اضافه کردن هات‌فیکس به پروژه بسازید.

  3. پس از اینکه تست شد برنچ هات‌فیکس را مرج کنید و تغییرات را به برنچ پروداکشن پوش کنید.

  4. به یوزراستوری قبلی که روی آن کار می‌کردید باز گردید و به ادامهٔ کار بپردازید.

شاخه‌سازی مقدماتی

در ابتدا فرض کنیم که شما روی پروژه‌ای کار می‌کنید و از قبل تعدادی کامیت روی برنچ master دارید.

A simple commit history.
نمودار 18. یک تاریخچهٔ کامیت ساده

شما تصمیم گرفته‌اید که روی ایشو #53 در سیستم پیگیری-مشکلی (Issue-Tracking System) که کمپانی شما از آن استفاده می‌کند کار کنید. برای ساختن یک برنچ جدید و تعویض برنچ در آن واحد می‌توانید دستور git checkout را با کلید -b اجرا کنید:

$ git checkout -b iss53
Switched to a new branch "iss53"

این دستور کوتاه شده خطوط زیر است:

$ git branch iss53
$ git checkout iss53
Creating a new branch pointer.
نمودار 19. ساختن یک نشانگر برنچ جدید

شما روی سایت خود کار می‌کنید و چند کامیت می‌کنید. انجام چنین کاری برنچ iss53 را به جلو منتقل می‌کند، به این دلیل که شما آنرا چک‌اوت کرده‌اید (یعنی HEAD شما به آن اشاره دارد):

$ vim index.html
$ git commit -a -m 'Create new footer [issue 53]'
The `iss53` branch has moved forward with your work.
نمودار 20. برنچ iss53 با کار شما به جلو رفته است

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

اگرچه قبل از اینکه آن کار را انجام دهید باید دقت کنید که اگر پوشه کاری یا استیج شما تغییرات کامیت‌نشده‌ای دارد که باعث ایجاد تداخل (Conflict) در برنچی که می‌خواهید به آن چک‌اوت کنید می‌شود، گیت به شما اجازهٔ تعویض برنچ نمی‌دهد. همیشه بهتر است قبل از تعویض برنچ، پوشه کاری داشته باشید. راه‌های متفاوتی برای انجام این تمیزکاری وجود دارد (من جمله استش (Stash) و امند کردن کامیت) که در Stashing and Cleaning به آن خواهیم پرداخت. فعلاً بیاید فرض کنیم که همه تغییرات کامیت شده‌اند، بنابراین می‌توانید به برنچ master خود انتقال پیدا کنید:

$ git checkout master
Switched to branch 'master'

تا اینجای کار پوشه کاری پروژهٔ شما دقیقاً مانند قبل از زمانی است که شروع به کار روی ایشو #53 کردید و می‌توانید روی هات‌فیکس خود تمرکز کنید. این نکته مهمی برای به خاطر سپردن است: هنگامی که برنچ را تعویض می‌کنید، گیت پوشه کاری شما را بازنشانی می‌کند تا دقیقاً به آن شکلی شود که آخرین بار روی آن برنچ کامیت کردید. گیت به طور خودکار فایل‌ها را کم، زیاد یا ویرایش می‌کند تا مطمئن شود که کپی فعال شما عیناً مطابق آخرین کامیت روی برنچ است.

در ادامه شما باید به هات‌فیکس خود بپردازید. بیاید یک برنچ hotfix بسازیم و تا زمان تمام شدنش روی آن کار کنیم:

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'Fix broken email address'
[hotfix 1fb7853] Fix broken email address
 1 file changed, 2 insertions(+)
Hotfix branch based on `master`.
نمودار 21. برنچ هات‌فیکس در ادامهٔ master

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

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

شما متوجه عبارت «fast-forward» در آن مرج می‌شوید. به این دلیل که کامیت C4 توسط برنچ hotfix مورد اشاره قرار گرفته که دقیقاً جلوی کامیت C2 است که شما روی آن کار می‌کنید، گیت به سادگی نشانگر را به جلو هل می‌دهد. به بیان دیگر وقتی سعی می‌کنید یک کامیت را با کامیت دیگری که می‌توان از طریق یپمودن مسیر کامیت اول به آن رسید، مرج کنید، گیت نشانگر را به جلو می‌کشد چون دوشاخگی در مسیر وجود ندارد که گیت بخواهد آنرا ادغام کند — به این کار «fast-forward» می‌گویند.

اکنون تغییرات شما در اسنپ‌شات کامیتی است که برنچ master به آن اشاره می‌کند و شما می‌توانید فیکس خود را ارائه کنید.

`master` is fast-forwarded to `hotfix`.
نمودار 22. برنچ master به hotfix فست-فوروارد شد.

پس از اینکه فیکس فوق مهمتان ارائه شد، آماده‌اید تا به کاری که پیش از اینکه به شما اطلاع داده شود بازگردید. هرچند ابتدا شما برنچ hotfix را پاک خواهید کرد، چرا که دیگر آنرا احتیاج ندارید — برنچ master به همانجا اشاره دارد. شما می‌توانید با آپشن -d دستور git branch آنرا پاک کنید:

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

حال می‌توانید به برنچ در حال تکمیل شدن قبلی که دربارهٔ ایشو #53 بود بازگردید و به کارکردن روی آن ادامه دهید:

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'Finish the new footer [issue 53]'
[iss53 ad82d7a] Finish the new footer [issue 53]
1 file changed, 1 insertion(+)
Work continues on `iss53`.
نمودار 23. کار روی iss53 ادامه پیدا می‌کند

اهمیتی ندارد که کاری که روی برنچ hotfix خود به انجام رسانیده‌اید در فایل‌های برنچ iss53 موجود نیست. اگر احتیاج دارید تا آنرا اعمال کنید، می‌توانید با اجرای git merge master برنچ master را در برنچ iss53 مرج کنید، یا می‌توانید برای تعبیه‌سازی تغییرات صبر کنید تا زمانی که تصمیم بگیرید که برنچ iss53 را به برنچ master پول کنید.

ادغام مقدماتی

فرض کنید به این نتیجه رسیده‌اید که کار روی ایشوی #53 تمام شده و آماده است تا با برنچ master مرج شود. برای اینکه این کار را انجام دهید، شما برنچ iss53 را درون برنچ master می‌ریزید و ادغام می‌کنید، بسیار شبیه به قبل، زمانی که برنچ hotfix را مرج کردید. تمام کاری که باید بکنید این است که برنچی که می‌خواهید تغییرات را به آن بریزید را چک‌اوت کنید و دستور git merge را اجرا کنید:

$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

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

Three snapshots used in a typical merge.
نمودار 24. سه اسنپ‌شات مورد استفاده در یک مرج معمولی

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

A merge commit.
نمودار 25. یک مرج کامیت

حال که کار شما ادغام شده است و شما دیگر نیازی به برنچ iss53 ندارید. شما می‌توانید ایشو را در سیستم پیگیری-مشکلتان ببندید و برنچ را پاک کنید:

$ git branch -d iss53

تداخلات ادغام پایه

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

$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

گیت به طور خودکار یک مرج کامیت جدید نساخته است. فرآیند را متوقف کرده است تا شما تداخل را حل کنید. هرگاه می‌خواهید ببینید که چه فایل‌هایی در یک تداخل مرج، مرج‌نشده باقی مانده‌اند، می‌توانید دستور git status را اجرا کنید:

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

هر چیزی که تداخل مرج دارد و حل نشده باشد به عنوان «مرج‌نشده» (Unmerged) لیست می‌شود. گیت چند نشانگر استاندارد تفکیک-تداخل را به فایل‌هایی که در آنها تداخل هست اضافه می‌کند تا بتوانید آن‌ها را به طور دستی باز کرده و تداخل‌ها را حل کنید. فایل شما بخشی خواهد داشت که شبیه زیر خواهد بود:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

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

<div id="footer">
please contact us at email.support@github.com
</div>

این راه حل کمی از هر بخش را در خود دارد و خطوط >>>>>>>، ======= و <<<<<<< هم کاملاً پاک شده‌اند. بعد از اینکه هر کدام از این بخش‌ها را در هر فایل متداخل را ویرایش کردید، git add را روی هر فایل اجرا کنید تا آنرا به عنوان حل شده علامت‌گذاری کنید. در گیت، استیج‌کردن فایل آنرا به عنوان حل شده علامت‌گذاری می‌کند.

اگر می‌خواهید از ابزاری گرافیکی برای حل این مسائل استفاده کنید، می‌توانید git mergetool را اجرا کنید که یک ابزار مرج بصری مناسب اجرا می‌کند و شما را در تداخل‌ها همراهی می‌کند:

$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

اگر می‌خواهید از ابزار مرجی به جز ابزار پیش‌فرض استفاده کنید (در این مثال گیت opendiff را انتخاب کرده چرا که دستور روی یک مک اجرا شده)، می‌توانید لیست تمام ابزارهای پشتیبانی شده را در بالا پس از «one of the following tools» مشاهده کنید. کافیست نام ابزاری که ترجیح می‌دهید استفاده کنید وارد کنید.

یادداشت

اگر به ابزار پیشرفته‌تری برای حل تداخل‌های خاص دارید، ما به بحث مرج‌کردن در Advanced Merging بیشتر می‌پردازیم.

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

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

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

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#	.git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#	modified:   index.html
#

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