Chapters ▾ 2nd Edition

5.3 گیت توزیع‌شده (Distributed git) - نگهداری یک پروژه (Maintaining a Project)

نگهداری یک پروژه (Maintaining a Project)

علاوه بر دانستن نحوه مشارکت مؤثر در یک پروژه، احتمالاً باید بدانید چگونه آن را نگهداری کنید. این کار می‌تواند شامل پذیرش و اعمال اصلاحاتی باشد که از طریق format-patch ایجاد شده و به شما ایمیل شده‌اند، یا ادغام تغییرات در شاخه‌های راه دور برای مخازنی که به عنوان ریموت به پروژه خود اضافه کرده‌اید. چه شما مسئول نگهداری یک مخزن رسمی باشید و چه بخواهید با تأیید یا تصویب اصلاحات کمک کنید، باید بدانید چگونه کار را به گونه‌ای بپذیرید که برای سایر مشارکت‌کنندگان واضح باشد و در درازمدت برای شما پایدار بماند.

کار در شاخه‌های موضوعی (Working in Topic Branches)

وقتی قصد دارید کاری جدید را ادغام کنید، معمولاً بهتر است آن را در یک شاخه موضوعی امتحان کنید — شاخه‌ای موقتی که به طور خاص برای آزمایش آن کار جدید ایجاد می‌شود. به این ترتیب، می‌توانید به راحتی یک اصلاحیه را به صورت جداگانه تنظیم کنید و اگر کار نمی‌کند آن را کنار بگذارید تا وقتی که وقت داشته باشید دوباره به آن برگردید. اگر نام ساده‌ای برای شاخه بر اساس موضوع کاری که می‌خواهید امتحان کنید انتخاب کنید، مانند ruby_client یا چیزی مشابه که توصیفی باشد، به راحتی می‌توانید آن را به خاطر بسپارید اگر مجبور شوید برای مدتی آن را رها کنید و بعداً برگردید. نگهدارنده پروژه Git معمولاً این شاخه‌ها را نیز با نام‌گذاری فضا (namespace) مشخص می‌کند — مثلاً sc/ruby_client که در آن sc مخفف نام شخصی است که کار را ارائه داده است. همان‌طور که به یاد دارید، می‌توانید شاخه را بر اساس شاخه master خود اینگونه ایجاد کنید:

$ git branch sc/ruby_client master

یا اگر می‌خواهید بلافاصله به آن سوئیچ کنید، می‌توانید از گزینه checkout -b استفاده کنید:

$ git checkout -b sc/ruby_client master

حالا آماده‌اید که کار ارسالی را که دریافت کرده‌اید به این شاخه موضوعی اضافه کنید و تصمیم بگیرید که آیا می‌خواهید آن را به شاخه‌های بلندمدت خود ادغام کنید یا خیر.

اعمال اصلاحات از طریق ایمیل (Applying Patches from Email)

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

اعمال یک پچ با apply (Applying a Patch with apply)

اگر اصلاحیه را از کسی دریافت کردید که آن را با git diff یا نسخه‌ای از دستور یونیکس diff ایجاد کرده است (که توصیه نمی‌شود؛ بخش بعدی را ببینید)، می‌توانید آن را با فرمان git apply اعمال کنید. فرض کنیم اصلاحیه را در مسیر /tmp/patch-ruby-client.patch ذخیره کرده‌اید، می‌توانید به این صورت اعمالش کنید:

$ git apply /tmp/patch-ruby-client.patch

این کار فایل‌ها را در دایرکتوری کاری شما تغییر می‌دهد. این تقریباً مشابه اجرای دستور patch -p1 برای اعمال اصلاحیه است، با این تفاوت که git apply محتاط‌تر است و تطابق‌های مبهم کمتری را می‌پذیرد. همچنین اگر افزودن، حذف یا تغییر نام فایل‌ها در فرمت git diff توصیف شده باشد، آن را مدیریت می‌کند، کاری که patch انجام نمی‌دهد. در نهایت، git apply یک مدل "همه را اعمال کن یا همه را لغو کن" است، یعنی یا همه اصلاحات اعمال می‌شوند یا هیچ‌کدام، در حالی که patch ممکن است اصلاحات را به صورت ناقص اعمال کند و دایرکتوری کاری شما را در وضعیت نامناسبی قرار دهد. در مجموع، git apply بسیار محافظه‌کارانه‌تر از patch است. این فرمان برای شما کامیت ایجاد نمی‌کند — بعد از اجرای آن باید تغییرات را به صورت دستی استیج و کامیت کنید.

همچنین می‌توانید از git apply استفاده کنید تا ببینید آیا اصلاحیه به صورت تمیز اعمال می‌شود یا خیر، قبل از اینکه واقعاً آن را اعمال کنید — می‌توانید با گزینه --check این کار را انجام دهید:

$ git apply --check 0001-see-if-this-helps-the-gem.patch
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply

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

اعمال پچ با am (Applying a Patch with am)

اگر مشارکت‌کننده از کاربران گیت باشد و به اندازه کافی حرفه‌ای باشد که از فرمان format-patch برای تولید پچ خود استفاده کند، کار شما آسان‌تر خواهد بود چون پچ شامل اطلاعات نویسنده و پیام کامیت برای شما است. اگر می‌توانید، مشارکت‌کنندگان خود را تشویق کنید تا به جای استفاده از diff، برای تولید پچ‌ها از format-patch استفاده کنند. شما معمولاً فقط برای پچ‌های قدیمی و موارد مشابه نیاز دارید از git apply استفاده کنید.

برای اعمال پچی که با format-patch تولید شده است، از git am استفاده می‌کنید (نام فرمان am به این دلیل است که برای «اعمال مجموعه‌ای از پچ‌ها از یک صندوق پستی» به کار می‌رود). از نظر فنی، git am برای خواندن فایل mbox طراحی شده است، که یک فرمت ساده و متنی برای ذخیره‌سازی یک یا چند ایمیل در یک فایل متنی است. این فرمت چیزی شبیه به این است:

From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] Add limit to log function

Limit log functionality to the first 20

این ابتدای خروجی فرمان git format-patch است که در بخش قبل دیدید؛ همچنین نمایانگر یک فرمت ایمیل معتبر mbox است. اگر کسی پچ را به درستی از طریق git send-email برای شما ایمیل کرده باشد و شما آن را در قالب mbox دانلود کنید، می‌توانید به git am اشاره کنید که آن فایل mbox را بخواند و شروع به اعمال همه پچ‌های موجود در آن کند. اگر از یک کلاینت ایمیل استفاده می‌کنید که می‌تواند چند ایمیل را در قالب mbox ذخیره کند، می‌توانید کل سری پچ‌ها را در یک فایل ذخیره کرده و سپس با git am آنها را یکی‌یکی اعمال کنید.

با این حال، اگر کسی فایل پچی که با git format-patch تولید شده را در یک سیستم تیکتینگ یا مشابه آن بارگذاری کرده باشد، می‌توانید آن فایل را به صورت محلی ذخیره کرده و سپس آن فایل ذخیره شده روی دیسک را به git am بدهید تا اعمال شود:

$ git am 0001-limit-log-function.patch
Applying: Add limit to log function

می‌بینید که پچ به‌صورت تمیز اعمال شده و به طور خودکار کامیت جدیدی برای شما ساخته است. اطلاعات نویسنده از سربرگ‌های From و Date ایمیل گرفته شده و پیام کامیت نیز از Subject و متن ایمیل (قبل از پچ) استخراج می‌شود. برای مثال، اگر این پچ از نمونه mbox بالا اعمال شده باشد، کامیت تولید شده چیزی شبیه به این خواهد بود:

$ git log --pretty=fuller -1
commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0
Author:     Jessica Smith <jessica@example.com>
AuthorDate: Sun Apr 6 10:17:23 2008 -0700
Commit:     Scott Chacon <schacon@gmail.com>
CommitDate: Thu Apr 9 09:19:06 2009 -0700

   Add limit to log function

   Limit log functionality to the first 20

اطلاعات Commit فردی را نشان می‌دهد که پچ را اعمال کرده و زمان اعمال آن را، اطلاعات Author فردی را که پچ را در اصل ایجاد کرده و زمان ایجاد آن را نشان می‌دهد.

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

$ git am 0001-see-if-this-helps-the-gem.patch
Applying: See if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Patch failed at 0001.
When you have resolved this problem run "git am --resolved".
If you would prefer to skip this patch, instead run "git am --skip".
To restore the original branch and stop patching run "git am --abort".

این فرمان، مارکرهای تداخل را در فایل‌های دارای مشکل قرار می‌دهد، مشابه زمانی که در یک عملیات ادغام یا بازپایه‌گذاری (rebase) به تداخل برخورد می‌کنید. شما این مشکل را به همان روش حل می‌کنید — فایل را ویرایش کرده و تداخل را برطرف می‌کنید، فایل جدید را استیج می‌کنید، و سپس دستور git am --resolved را اجرا می‌کنید تا به پچ بعدی بروید:

$ (fix the file)
$ git add ticgit.gemspec
$ git am --resolved
Applying: See if this helps the gem

اگر می‌خواهید گیت کمی هوشمندانه‌تر تلاش کند تا تداخل را حل کند، می‌توانید گزینه -3 را به آن بدهید که باعث می‌شود گیت یک ادغام سه‌طرفه انجام دهد. این گزینه به طور پیش‌فرض فعال نیست چون در صورتی که کامیتی که پچ گفته بر اساس آن ساخته شده در مخزن شما نباشد، کار نمی‌کند. اگر آن کامیت را دارید — یعنی اگر پچ بر اساس یک کامیت عمومی ساخته شده — گزینه -3 معمولاً در اعمال پچ‌های دارای تداخل هوشمندانه‌تر عمل می‌کند:

$ git am -3 0001-see-if-this-helps-the-gem.patch
Applying: See if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.

در این حالت، بدون گزینه -3 پچ به عنوان تداخل در نظر گرفته می‌شد. اما چون گزینه -3 استفاده شده، پچ به‌صورت تمیز اعمال شده است.

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

$ git am -3 -i mbox
Commit Body is:
--------------------------
See if this helps the gem
--------------------------
Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all

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

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

بررسی شاخه‌های ریموت (Checking Out Remote Branches)

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

برای مثال، اگر جسیکا ایمیلی برای شما بفرستد و بگوید که یک ویژگی عالی جدید در شاخه ruby-client مخزن خودش دارد، می‌توانید با اضافه کردن ریموت و چک‌اوت آن شاخه به صورت محلی آن را تست کنید:

$ git remote add jessica https://github.com/jessica/myproject.git
$ git fetch jessica
$ git checkout -b rubyclient jessica/ruby-client
اگر او بعداً دوباره برای شما ایمیل بزند و شاخه‌ای دیگر با ویژگی دیگری عالی ارسال کند، می‌توانید مستقیماً با استفاده از `fetch` و سپس `checkout` آن را دریافت کنید چون قبلاً تنظیم ریموت را انجام داده‌اید.

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

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

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

$ git pull https://github.com/onetimeguy/project
From https://github.com/onetimeguy/project
 * branch            HEAD       -> FETCH_HEAD
Merge made by the 'recursive' strategy.

تشخیص تغییرات ایجاد شده (Determining What Is Introduced)

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

معمولاً مفید است که تمام کامیت‌هایی که در این شاخه هستند ولی در شاخه master شما نیستند را بررسی کنید. می‌توانید با افزودن گزینه --not قبل از نام شاخه، کامیت‌های موجود در شاخه master را حذف کنید. این کار همان کاری را انجام می‌دهد که فرمت master..contrib که قبلاً استفاده کردیم انجام می‌داد. برای مثال، اگر همکارتان دو پچ برای شما فرستاده و شما شاخه‌ای به نام contrib ساخته و آن‌ها را روی آن اعمال کرده‌اید، می‌توانید این دستور را اجرا کنید:

$ git log contrib --not master
commit 5b6235bd297351589efc4d73316f0a68d484f118
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Oct 24 09:53:59 2008 -0700

    See if this helps the gem

commit 7482e0d16d04bea79d0dba8988cc78df655f16a0
Author: Scott Chacon <schacon@gmail.com>
Date:   Mon Oct 22 19:38:36 2008 -0700

    Update gemspec to hopefully work better

برای دیدن تغییراتی که هر کامیت معرفی می‌کند، به یاد داشته باشید که می‌توانید گزینه -p را به git log بدهید تا تفاوت‌های مربوط به هر کامیت به خروجی اضافه شود.

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

$ git diff master

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

اگر master یک جد مستقیم شاخه موضوعی شما باشد، این مشکل نیست؛ اما اگر دو تاریخچه متفاوت شده باشند، diff طوری به نظر می‌رسد که شما دارید همه چیز جدید شاخه موضوعی را اضافه می‌کنید و همه چیز منحصر به فرد شاخه master را حذف می‌کنید.

آنچه واقعاً می‌خواهید ببینید، تغییراتی است که به شاخه موضوعی اضافه شده است — کاری که اگر این شاخه را با master ادغام کنید وارد می‌کنید. این کار را با این روش انجام می‌دهید که گیت آخرین کامیت شاخه موضوعی شما را با اولین جد مشترک آن با شاخه master مقایسه کند.

از نظر فنی، می‌توانید با یافتن صریح جد مشترک و سپس اجرای diff روی آن این کار را انجام دهید:

$ git merge-base contrib master
36c7dba2c95e6bbb78dfa822519ecfec6e1ca649
$ git diff 36c7db

یا به صورت خلاصه‌تر:

$ git diff $(git merge-base contrib master)

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

$ git diff master...contrib

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

ادغام کارهای ارسالی (Integrating Contributed Work)

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

روش‌های ادغام (Merging Workflows)

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

برای مثال، اگر مخزنی داشته باشیم که کار در دو شاخه به نام‌های ruby_client و php_client انجام شده و وضعیت آن مانند History with several topic branches باشد، و ابتدا ruby_client سپس php_client را ادغام کنیم، تاریخچه‌ی شما به شکل After a topic branch merge خواهد بود.

History with several topic branches
نمودار 72. History with several topic branches
After a topic branch merge
نمودار 73. After a topic branch merge

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

اگر پروژه‌ی مهم‌تری دارید، ممکن است بخواهید از چرخه‌ی ادغام دو مرحله‌ای استفاده کنید. در این حالت، دو شاخه‌ی بلندمدت دارید، master و develop، که در آن تعیین شده است فقط وقتی نسخه‌ی بسیار پایداری منتشر می‌شود، master به‌روزرسانی شود و تمام کدهای جدید در شاخه‌ی develop ادغام شوند. شما هر دو شاخه را به طور منظم به مخزن عمومی ارسال می‌کنید. هر بار که شاخه‌ی موضوعی جدیدی برای ادغام دارید (Before a topic branch merge)، آن را به develop ادغام می‌کنید (After a topic branch merge). سپس، وقتی یک نسخه منتشر می‌کنید، شاخه‌ی master را به محل شاخه‌ی develop که حالا پایدار است، به‌صورت fast-forward می‌برید (After a project release).

Before a topic branch merge
نمودار 74. Before a topic branch merge
After a topic branch merge
نمودار 75. After a topic branch merge
After a project release
نمودار 76. After a project release

به این ترتیب، وقتی افراد مخزن پروژه‌ی شما را کلون می‌کنند، می‌توانند یا شاخه‌ی master را چک‌اوت کنند تا آخرین نسخه‌ی پایدار را بسازند و به‌راحتی به‌روز بمانند، یا شاخه‌ی develop را که محتویات پیشرفته‌تر و جدیدتری دارد، بررسی کنند. شما می‌توانید این مفهوم را با داشتن یک شاخه‌ی integrate که تمام کارها در آنجا ادغام می‌شود، گسترش دهید. سپس وقتی کد روی آن شاخه پایدار و تست‌ها را پاس کرد، آن را به شاخه‌ی develop ادغام می‌کنید؛ و وقتی آن شاخه برای مدتی پایدار بود، شاخه‌ی master خود را fast-forward می‌کنید.

روندهای کاری ادغام بزرگ (Large-Merging Workflows)

پروژه‌ی Git چهار شاخه‌ی بلندمدت دارد: master، next، و seen (قبلاً "pu" به معنای به‌روزرسانی‌های پیشنهادی) برای کارهای جدید، و maint برای پشتیبانی و نگهداری. وقتی کار جدیدی توسط مشارکت‌کنندگان ارائه می‌شود، در شاخه‌های موضوعی در مخزن نگهدارنده جمع‌آوری می‌شود، مشابه آنچه شرح داده شد (نگاه کنید به Managing a complex series of parallel contributed topic branches). در این مرحله، موضوع‌ها ارزیابی می‌شوند تا مشخص شود آیا امن و آماده‌ی استفاده هستند یا نیاز به کار بیشتری دارند. اگر امن باشند، به شاخه‌ی next ادغام می‌شوند و آن شاخه به مخزن عمومی ارسال می‌شود تا همه بتوانند موضوع‌ها را به صورت یکپارچه امتحان کنند.

Managing a complex series of parallel contributed topic branches
نمودار 77. Managing a complex series of parallel contributed topic branches

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

Merging contributed topic branches into long-term integration branches
نمودار 78. Merging contributed topic branches into long-term integration branches

وقتی شاخه‌ی موضوعی نهایتاً به master ادغام شد، از مخزن حذف می‌شود. پروژه‌ی Git همچنین یک شاخه‌ی maint دارد که از آخرین نسخه فورک شده تا در صورت نیاز به انتشار نگهداری، اصلاحات به عقب منتقل شوند. بنابراین، وقتی مخزن Git را کلون می‌کنید، چهار شاخه دارید که می‌توانید برای ارزیابی پروژه در مراحل مختلف توسعه آن‌ها را بررسی کنید، بسته به اینکه چقدر پیشرفته بودن برایتان مهم است یا چگونه می‌خواهید مشارکت کنید؛ و نگهدارنده هم یک گردش‌کار ساختاریافته دارد تا به آن‌ها در بررسی مشارکت‌های جدید کمک کند. گردش‌کار پروژه‌ی Git تخصصی است. برای درک دقیق‌تر می‌توانید راهنمای نگهدارنده‌ی Git را در Git Maintainer’s guide. مطالعه کنید

روندهای کاری بازپایه‌گذاری و انتخاب گزینشی (Rebasing and Cherry-Picking Workflows)

نگهدارندگان دیگر ترجیح می‌دهند کارهای ارائه شده را روی شاخه‌ی master خود بازبیس یا چری‌پیک کنند، به جای ادغام مستقیم، تا تاریخچه‌ای تقریباً خطی داشته باشند. وقتی کاری در شاخه‌ی موضوعی دارید و تصمیم گرفته‌اید آن را ادغام کنید، به آن شاخه می‌روید و فرمان بازبیس را اجرا می‌کنید تا تغییرات را روی شاخه‌ی فعلی master (یا develop و غیره) بازسازی کنید. اگر این کار خوب پیش رفت، می‌توانید شاخه‌ی master را fast-forward کنید و تاریخچه‌ی پروژه به صورت خطی باقی می‌ماند.

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

Example history before a cherry-pick
نمودار 79. Example history before a cherry-pick

اگر می‌خواهید کامیت e43a6 را به شاخه‌ی master خود وارد کنید، می‌توانید این دستور را اجرا کنید:

$ git cherry-pick e43a6
Finished one cherry-pick.
[master]: created a0a41a9: "More friendly message when locking the index fails."
 3 files changed, 17 insertions(+), 3 deletions(-)

این دستور همان تغییر اعمال شده در e43a6 را وارد می‌کند، اما یک مقدار جدید SHA-1 برای کامیت دریافت خواهید کرد، چون تاریخ اعمال آن متفاوت است. اکنون تاریخچه‌ی شما به این شکل خواهد بود:

History after cherry-picking a commit on a topic branch
نمودار 80. History after cherry-picking a commit on a topic branch

حالا می‌توانید شاخه موضوعی خود را حذف کنید و کامیت‌هایی که نمی‌خواستید وارد شوند را کنار بگذارید.

بازاستفاده خودکار از حل تضادها (Rerere)

اگر زیاد عملیات ادغام (merge) و بازپایه‌گذاری (rebase) انجام می‌دهید یا شاخه‌ی موضوعی بلندمدتی را نگهداری می‌کنید، گیت ویژگی‌ای به نام “rerere” دارد که می‌تواند به شما کمک کند.

Rerere مخفف “reuse recorded resolution” است — روشی برای کوتاه‌کردن فرآیند حل تعارضات به صورت دستی. وقتی rerere فعال باشد، گیت مجموعه‌ای از تصاویر قبل و بعد از ادغام‌های موفق را نگه می‌دارد، و اگر متوجه شود تعارضی مشابه تعارضی که قبلاً حل کرده‌اید رخ داده، به جای اینکه دوباره از شما بخواهد آن را حل کنید، به‌طور خودکار همان حل قبلی را به کار می‌برد.

این ویژگی دو بخش دارد: یک تنظیم پیکربندی و یک دستور. تنظیم پیکربندی rerere.enabled است، که به اندازه‌ای مفید است که بهتر است در تنظیمات سراسری (global) قرار بگیرد:

$ git config --global rerere.enabled true

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

اگر لازم باشد، می‌توانید با استفاده از دستور git rerere با کش rerere تعامل داشته باشید. وقتی این دستور به تنهایی اجرا شود، گیت پایگاه داده حل تعارض‌های خود را بررسی می‌کند و سعی می‌کند با تعارض‌های فعلی مطابقت پیدا کند و آن‌ها را حل کند (اگر rerere.enabled روی true تنظیم شده باشد این کار به صورت خودکار انجام می‌شود). همچنین زیردستورات مختلفی وجود دارد تا ببینید چه مواردی ثبت خواهد شد، حل تعارض خاصی را از کش پاک کنید و یا کل کش را خالی نمایید. ما در بخش بازاستفاده خودکار از حل تضادها (Rerere) به جزئیات بیشتری درباره rerere خواهیم پرداخت.

برچسب‌گذاری نسخه‌های خود (Tagging Your Releases)

وقتی تصمیم می‌گیرید یک نسخه منتشر کنید، احتمالاً می‌خواهید یک برچسب (tag) اختصاص دهید تا بتوانید آن نسخه را در هر زمانی در آینده دوباره ایجاد کنید. می‌توانید برچسب جدیدی ایجاد کنید، همانطور که در مقدمات گیت (git basics chapter) توضیح داده شده است. اگر تصمیم دارید برچسب را به عنوان نگهدارنده امضا کنید، برچسب‌گذاری ممکن است چیزی شبیه به این باشد:

$ git tag -s v1.5 -m 'my signed 1.5 tag'
You need a passphrase to unlock the secret key for
user: "Scott Chacon <schacon@gmail.com>"
1024-bit DSA key, ID F721C45A, created 2009-02-09

اگر برچسب‌های خود را امضا کنید، ممکن است با مشکل توزیع کلید عمومی PGP که برای امضای برچسب‌ها استفاده می‌شود مواجه شوید. نگهدارنده پروژه گیت این مشکل را با قرار دادن کلید عمومی خود به صورت یک blob در مخزن و سپس اضافه کردن یک برچسب که مستقیماً به آن محتوا اشاره می‌کند، حل کرده است. برای این کار، می‌توانید کلیدی که می‌خواهید را با اجرای دستور gpg --list-keys پیدا کنید:

$ gpg --list-keys
/Users/schacon/.gnupg/pubring.gpg
---------------------------------
pub   1024D/F721C45A 2009-02-09 [expires: 2010-02-09]
uid                  Scott Chacon <schacon@gmail.com>
sub   2048g/45D02282 2009-02-09 [expires: 2010-02-09]

سپس می‌توانید کلید را با صادر کردن آن و عبور دادن خروجی از طریق git hash-object مستقیماً در پایگاه داده گیت وارد کنید، که یک blob جدید با این محتویات در گیت می‌نویسد و مقدار SHA-1 آن blob را به شما برمی‌گرداند:

$ gpg -a --export F721C45A | git hash-object -w --stdin
659ef797d181633c87ec71ac3f9ba29fe5775b92

حالا که محتویات کلید شما در گیت است، می‌توانید برچسبی بسازید که مستقیماً به آن اشاره کند با مشخص کردن مقدار SHA-1 جدیدی که دستور hash-object به شما داده است:

$ git tag -a maintainer-pgp-pub 659ef797d181633c87ec71ac3f9ba29fe5775b92

اگر دستور git push --tags را اجرا کنید، برچسب maintainer-pgp-pub با همه به اشتراک گذاشته خواهد شد. اگر کسی بخواهد برچسبی را تأیید کند، می‌تواند کلید PGP شما را با استخراج مستقیم blob از پایگاه داده و وارد کردن آن در GPG به صورت مستقیم وارد کند:

$ git show maintainer-pgp-pub | gpg --import

آن‌ها می‌توانند از آن کلید برای تأیید تمام برچسب‌های امضا شده شما استفاده کنند. همچنین، اگر دستورالعمل‌هایی در پیام برچسب درج کنید، اجرای git show <tag> به کاربر نهایی اجازه می‌دهد دستورالعمل‌های خاص‌تری درباره تأیید برچسب دریافت کند.

تولید شماره ساخت (Generating a Build Number)

چون گیت شماره‌هایی که به صورت پیوسته افزایش یابند مانند 'v123' یا معادل آن برای هر کامیت ندارد، اگر بخواهید نامی قابل خواندن برای انسان همراه با کامیت داشته باشید، می‌توانید دستور git describe را روی آن کامیت اجرا کنید. در پاسخ، گیت رشته‌ای تولید می‌کند که شامل نام آخرین برچسبی است که قبل از آن کامیت وجود داشته، به همراه تعداد کامیت‌هایی که از آن برچسب گذشته است، و در نهایت بخشی از مقدار SHA-1 کامیتی که توصیف می‌شود (با پیشوند حرف "g" که مخفف Git است):

$ git describe master
v1.6.2-rc1-20-g8c5b85c
 به این ترتیب، می‌توانید یک تصویر فوری (snapshot) یا نسخه‌ای بسازید و آن را به گونه‌ای نام‌گذاری کنید که برای دیگران قابل فهم باشد.
در واقع، اگر گیت را از کد منبعی که از مخزن گیت کلون شده بسازید، دستور `git --version` چیزی شبیه به این به شما می‌دهد.
اگر شما یک کامیت را که مستقیماً تگ زده‌اید توضیح دهید، فقط نام تگ را نشان می‌دهد.

به طور پیش‌فرض، دستور git describe به تگ‌های حاشیه‌نویسی شده (annotated tags) نیاز دارد (تگ‌هایی که با فلگ‌های -a یا -s ساخته شده‌اند)؛ اگر می‌خواهید از تگ‌های سبک‌وزن (lightweight tags) یا غیرحاشیه‌نویسی شده نیز استفاده کنید، گزینه --tags را به دستور اضافه کنید. شما همچنین می‌توانید این رشته را به عنوان هدف دستور git checkout یا git show استفاده کنید، اگرچه این روش به مقدار کوتاه‌شده SHA-1 در انتها وابسته است، پس ممکن است همیشه معتبر نباشد. برای مثال، هسته لینوکس اخیراً تعداد کاراکترهای SHA-1 را از ۸ به ۱۰ افزایش داده است تا یکتایی اشیاء SHA-1 را تضمین کند، بنابراین نام‌های خروجی قدیمی git describe نامعتبر شده‌اند.

آماده‌سازی نسخه انتشار (Preparing a Release)

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

$ git archive master --prefix='project/' | gzip > `git describe master`.tar.gz
$ ls *.tar.gz
v1.6.2-rc1-20-g8c5b85c.tar.gz

اگر کسی آن فایل tar را باز کند، آخرین تصویر پروژه شما را در دایرکتوری‌ای به نام project دریافت می‌کند. شما همچنین می‌توانید به روشی مشابه یک آرشیو zip ایجاد کنید، اما با افزودن گزینه --format=zip به دستور git archive:

$ git archive master --prefix='project/' --format=zip > `git describe master`.zip

اکنون شما یک فایل tar و یک آرشیو zip زیبا از نسخه پروژه‌تان دارید که می‌توانید آن را در وب‌سایت خود بارگذاری کنید یا برای دیگران ایمیل کنید.

گزارش کوتاه (The Shortlog)

وقت آن است که به فهرست ایمیل افرادی که می‌خواهند بدانند در پروژه شما چه خبر است اطلاع دهید. یک روش خوب برای به دست آوردن سریع نوعی گزارش تغییرات (changelog) از آنچه از آخرین انتشار یا ایمیل شما به پروژه اضافه شده، استفاده از دستور git shortlog است. این دستور خلاصه‌ای از تمام کامیت‌ها در بازه‌ای که به آن می‌دهید ارائه می‌دهد؛ برای مثال، دستور زیر خلاصه‌ای از تمام کامیت‌ها از زمان آخرین انتشار شما، اگر آخرین انتشار شما با نام v1.0.1 بوده باشد، به شما می‌دهد:

$ git shortlog --no-merges master --not v1.0.1
Chris Wanstrath (6):
      Add support for annotated tags to Grit::Tag
      Add packed-refs annotated tag support.
      Add Grit::Commit#to_patch
      Update version and History.txt
      Remove stray `puts`
      Make ls_tree ignore nils

Tom Preston-Werner (4):
      fix dates in history
      dynamic version method
      Version bump to 1.0.2
      Regenerated gemspec for version 1.0.2

شما یک خلاصه مرتب و تمیز از تمام کامیت‌ها از v1.0.1 به بعد، به تفکیک نویسنده، دریافت می‌کنید که می‌توانید آن را به فهرست ایمیل خود ارسال کنید.

scroll-to-top