Git 🌙
Chapters ▾ 2nd Edition

5.2 گیت توزیع‌شده - مشارکت در یک پروژه

مشارکت در یک پروژه

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

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

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

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

تمام این سؤال‌ها می‌تواند چگونگی مشارکت فعال شما به یک پروژه و اینکه چه روند‌های کاری را مناسب‌تر یا در اختیارتان هست را تحت شعاع قرار دهند. ما چند جنبه از هر کدام از این موارد را در مجموعه‌ای از یوزکیس (Use Case)ها و مثال‌های از ساده به پیچیده بررسی می‌کنیم. شما باید بتوانید روند کاری خاصی که به آن احتیاج دارید را در هر کدام از این مثال‌ها بسازید.

راهنمای کامیت

پیش از اینکه شروع به دیدن یوزکیس‌ها کنیم، اینجا نکته‌ای دربارهٔ پیغام کامیت باید ذکر شود. داشتن راهنمای خوب برای کامیت ساختن و پایبندی به آن کار کردن با گیت و همکاری با دیگران را بسیار آسانتر می‌کند. پروژهٔ گیت سندی ارائه می‌کند که در آن نکات مفیدی دربارهٔ ساختن کامیت‌هایی که از آن‌ها پچ‌ها را اعمال می‌کنید وجود دارد — شما می‌توانید این سند را در سورس کد گیت در فایل Documentation/SubmittingPatches بخوانید.

اول اینکه ارائهٔ شما نباید هیچ مشکلی مرتبط با فضاهای سفید داشته باشد. گیت برای شما راه ساده‌ای برای چک کردن این موضوع فراهم کرده است — پیش از اینکه کامیت کنید git diff --check را اجرا کنید. این دستور مشکلات احتمالی فضاهای سفید را تشخیص می‌دهد و برای شما لیست می‌کند.

Output of `git diff --check`.
نمودار 57. خروجی git diff --check.

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

سپس، سعی بر آن باشد که هر کامیت دسته‌ای از تغییرات منطقاً مجزا باشد. اگر قادرید سعی کنید تغییراتان را قابل هضم کنید — یک آخر هفتهٔ کامل را دربارهٔ ۵ ایشوی مختلف کد نزنید و سپس همه را شنبه با یک کامیت غول‌آسا تحویل دهید. حتی اگر هم در طی آخر هفته کامیت نمی‌کنید، شنبه از استیج استفاده کنید تا حداقل کارتان را به یک کامیت به ازای ایشو و با یک پیغام خوب تقسیم کنید. اگر بعضی از تغییرات روی یک فایل انجام شده سعی کنید از git add --patch استفاده کنید تا به صورت بخش بخش فایل‌ها را استیج کنید (با جزئیات در Interactive Staging بررسی شده). مادامی که همهٔ تغییرات را اضافه کرده‌اید، اسنپ‌شات پروژه در نوک برنچ یکی خواهد بود، خواه ۵ کامیت کنید یا یکی. در نتیجه سعی کنید که کار را برای توسعه‌دهندگانتان که مجبور هستند تغییرات شما را بازبینی کنند، ‌آسانتر کنید.

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

آخرین چیزی که باید به خاطر داشته باشید پیغام کامیتتان است. عادت به نوشتن پیغام‌های کامیت با کیفیت استفاده و همکاری با گیت را بسیار آسانتر می‌کند. به عنوان قانونی کلی، پیغام شما باید با یک خط که بیش از ۵۰ حرف نیست و توصیف‌کنندهٔ دقیق تغییرات است شروع شود، پس از آن یک خط خالی بیاید و پس از آن توضیحات جزئی‌تر بیشتر قرار گیرد. پیغام کامیت خود را به صورت امری بنویسید: «Fix bug» (مشکل را حل کن) و نه «Fixed bug» (مشکل حل شد) یا «Fixes bug» (مشکل را حل می‌کند).

اینجا قالبی مفید از تیم پاپ آمده:

مختصری (۵۰ حرف یا کمتر) با اطلاعات کافی

توضیحات جزئی بیشتر در صورت نیاز.  حدود ۷۲ حرف یا کمتر و بیشتر در هر خط باشد.
در بعضی متن‌ها خط اول به عنوان موضوع یک ایمیل و باقی متن به عنوان بدنهٔ
نامه استفاده می‌شود.  خط خالی که خلاصه را از بدنه جدا می‌کند حیاتی است (
مگر اینکه کلاً بدنه را حذف کنید)؛ ابزارهایی مانند ریبیس درصورت ترکیب کردن
این دو با مشکل مواجه می‌شوند.

پیغام کامیت خود را در حالت امری بنویسید: «مشکل را حل کن» و نه
«مشکل حل شد» یا «مشکل را حل می‌کند.»  این عرف با پیغام‌هایی که توسط
دستوراتی مانند گیت مرج و گیت ریورت ساخته می‌شود تطابق دارد.

بندهای بعدی هر کدام پس از یک خط خالی می‌آیند.

- بولت زدن هم در این قالب پشتیبانی می‌شود.

- معمولاً یک خط‌تیره یا ستاره برای بولت استفاده می‌شود که با یک
  فاصله همراه است و بین بولت‌ها خط خالی می‌آید. اما عرف‌ها در این مورد
  کمی با یکدیگر تفاوت دارند.

- از تورفتگی آویزان استفاده کنید.

در صورتی که تمام پیغام‌های کامیت‌های شما از این ساختار پیروی کنند، خیلی چیزها برای شما و توسعه‌دهندگانی که با آنها همکاری می‌کنید آسانتر می‌شود. پروژهٔ گیت پیغام‌های کامیت خوش قالبی دارد — git log --no-merges را در پروژه امتحان کنید تا ببنید یک تاریخچهٔ کامیت خوش قالب چه شکلی است.

یادداشت
کاری که می‌گوییم را انجام دهید، نه کاری که ما می‌کنیم.

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

خلاصه اینکه کاری که می‌گوییم را انجام دهید، نه کاری که ما می‌کنیم.

تیم خصوصی کوچک

ساده‌ترین چینشی که به احتمال زیادی به آن بر خواهید خورد یک پروژهٔ خصوصی کوچک با یک یا دو توسعه‌دهندهٔ دیگر است. «خصوصی» محتوا است، به این معنا که متن-بسته است — توسط دنیای بیرون قابل دسترس نیست. شما و دیگر توسعه‌دهندگان، همه دسترسی پوش به مخزن را دارند.

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

# John's Machine
$ git clone john@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'Remove invalid default value'
[master 738ee87] Remove invalid default value
 1 files changed, 1 insertions(+), 1 deletions(-)

دومین توسعه‌دهنده، جسیکا هم همین کار را می‌کند — مخزن را کلون می‌کند و تغییری را کامیت می‌کند:

# Jessica's Machine
$ git clone jessica@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim TODO
$ git commit -am 'Add reset task'
[master fbff5bc] Add reset task
 1 files changed, 1 insertions(+), 0 deletions(-)

حال جسیکا کار خود را به سرور پوش می‌کند که به درستی انجام می‌شود:

# Jessica's Machine
$ git push origin master
...
To jessica@githost:simplegit.git
   1edee6b..fbff5bc  master -> master

آخرین خط خروجی بالا پیغام بازگشتی مفیدی را از عملیات پوش نشان می‌دهد. قالب ساده <oldref>..<newref> fromref -> toref است که در آن oldref یعنی مرجع قدیمی، newref یعنی مرجع جدید، fromref نام مرجع محلی است که پوش می‌شود و toref نام مرجع ریموت است که بروزرسانی می‌شود. در مبحث پایین هم خروجی متفاوتی را خواهید دید، بنابراین داشتن یک ایدهٔ پایه از مفهوم این به درک شما از وضعیت‌های متفاوت مخزن کمک می‌کند. جزئیات بیشتر در مستند git-push در دسترس هستند.

به ادامهٔ مثال می‌رویم. کمی بعد جان کمی تغییرات انجام می‌دهد، آنها را در مخزن محلی خود کامیت می‌کند و سپس سعی در پوش کردن روی همان سرور می‌کند:

# John's Machine
$ git push origin master
To john@githost:simplegit.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'

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

در وهلهٔ اول جان کارهای جسیکا را فچ می‌کند (این کار فقط کار جسیکا را فچ می‌کند و در کار جان آنرا مرج نمی‌کند):

$ git fetch origin
...
From john@githost:simplegit
 + 049d078...fbff5bc master     -> origin/master

در این نقطه، مخزن محلی جان شبیه این است:

John’s divergent history.
نمودار 58. تاریخچهٔ دوشاخهٔ جان.

حال جان می‌تواند کار جسیکا را که فچ کرده بود با کار محلی خودش مرج کند:

$ git merge origin/master
Merge made by the 'recursive' strategy.
 TODO |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

مادامی که مرج محلی به درستی صورت بگیرد، تاریخچهٔ بروز جان به این شکل شبیه خواهد بود:

John’s repository after merging `origin/master`.
نمودار 59. مخزن جان بعد از مرج کردن origin/master.

اینجا جان ممکن است این کد جدید را تست کند تا مطمئن باشد که کار جسیکا به هیچ نحوی روی کار او تأثیری نذاشته و تا زمانی که همه چیز به نظر مناسب می‌آید او می‌تواند کار مرج شدهٔ جدید را به سرور پوش کند:

$ git push origin master
...
To john@githost:simplegit.git
   fbff5bc..72bbc59  master -> master

در آخر تاریخچهٔ کامیت جان به این شکل خواهد بود:

John’s history after pushing to the `origin` server.
نمودار 60. تاریخچهٔ جان پس از پوش کردن به سرور origin.

در همین حین، جسیکا یک برنچ موضوعی جدید به نام issue54 ساخته و سه کامیت روی آن برنچ گرفته است. او هنوز کارهای جان را فچ نکرده است، پس تاریخچهٔ کامیت‌های او شبیه به این است:

Jessica’s topic branch.
نمودار 61. برنچ موضوعی جسیکا.

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

# Jessica's Machine
$ git fetch origin
...
From jessica@githost:simplegit
   fbff5bc..72bbc59  master     -> origin/master

این عمل تغییراتی را که جان در این حین انجام داده است را می‌گیرد. تاریخچهٔ جسیکا اکنون شبیه به این است:

Jessica’s history after fetching John’s changes.
نمودار 62. تاریخچهٔ جسیکا پس از فچ کردن تغییرات جان.

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

$ git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 16:01:27 2009 -0700

   Remove invalid default value

سینتکس issue54..origin/master یک لاگ فیلتر است که از گیت می‌خواهد که فقط کامیت‌هایی را نشان دهد که در برنچ دوم (در این مورد origin/master) موجودند و در برنچ اول (در این مورد issue54) نیستند. دربارهٔ این ساختار و سینکس در Commit Ranges با جزئیات توضیح می‌دهیم.

از خروجی بالا متوجه می‌شویم که یک کامیت وجود دارد که جان آنرا ساخته است و جسیکا آنرا در کار محلی خود مرج نکرده است. اگر او origin/master را مرج کند، آن کامیت کار محلی او را تغییر خواهد داد.

حال جسیکا می‌تواند برنچ موضوعی خود را به master مرج کند، کار جان (origin/master) را به برنچ master خودش مرج کند. سپس آنها را دوباره به سرور پوش کند.

ابتدا (حین کامیت داشتن همهٔ تغییراتش روی برنچ موضوعی issue54) جسیکا به برنچ master خود باز می‌گردد تا مقدمات یکپارچه‌سازی را انجام دهد:

$ git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.

جسیکا می‌تواند هر کدام از برنچ‌های origin/master یا issue54 را ابتدا مرج کند — هر دوی آنها بالادست هستند در نتیجه ترتیب مهم نیست. اسنپ‌شات نهایی یکسان خواهد بود،‌ مستقل از ترتیبی که او انتخاب کند. تنها تاریخچه تفاوت خواهد کرد. او تصمیم می‌گیرد که برنچ issue54 را ابتدا مرج کند:

$ git merge issue54
Updating fbff5bc..4af4298
Fast forward
 README           |    1 +
 lib/simplegit.rb |    6 +++++-
 2 files changed, 6 insertions(+), 1 deletions(-)

مشکلی پیش نمی‌آید؛ همانطور که می‌بینید یک مرج fast-forward ساده بود. جسیکا حال فرآیند مرج محلی خود را با مرج کارهای قبل‌تر جان که فچ کرده بود و در برنچ origin/master است تمام می‌کند:

$ git merge origin/master
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

همه چیز با حالت تمیز خاتمه می‌یابد (تداخلی پیش نمی‌آید) و تایخچهٔ جسیکا شبیه به این است:

Jessica’s history after merging John’s changes.
نمودار 63. تاریخچهٔ جسیکا پس از مرج کردن تغییرات جان.

حال origin/master از برنچ master جسیکا قابل دسترسی است، پس او باید بتواند با موفقیت به آن پوش کند (بر فرض اینکه جان تغییرات بیشتری را در این حین پوش نکرده باشد):

$ git push origin master
...
To jessica@githost:simplegit.git
   72bbc59..8059c15  master -> master

هر توسعه‌دهنده چندین بار کامیت کرده و کارهای دیگران را با موفقیت مرج کرده است.

Jessica’s history after pushing all changes back to the server.
نمودار 64. تاریخچهٔ جسیکا پس از پوش کردن تمام تغییرات به سرور.

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

General sequence of events for a simple multiple-developer Git workflow.
نمودار 65. ترتیب کلی رویدادها برای یک روند کاری چند توسعه‌دهنده‌ای ساده گیت.

تیم‌های خصوصی مدیریت‌شده

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

فرض کنیم که جان و جسیکا هر دو روی یک ویژگی (نام آنرا «featureA» بگذاریم) کار می‌کنند، مادامی که جسیکا و یک توسعه‌دهندهٔ سوم، جوزی، روی یک ویژگی دوم هم کار می‌کنند (بگوییم «featureB»). در این حالت، کمپانی از یک نوع روند کاری مدیر-یکپارچه‌سازی (Integration-Manager) استفاده می‌کند که در آن کار انجام شده توسط گروه‌های مختلف و مجزا فقط توسط گروه خاصی از مهندسین تعبیه و یکپارچه‌سازی می‌شود و برنچ master مخزن فقط توسط آن گروه از مهندسین قابل بروزرسانی است. در این سناریو همهٔ کار در برنچ‌های تیمی انجام می‌شود و توسط یکپارچه‌سازها بعدها کنار هم گذاشته می‌شود.

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

# Jessica's Machine
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ vim lib/simplegit.rb
$ git commit -am 'Add limit to log function'
[featureA 3300904] Add limit to log function
 1 files changed, 1 insertions(+), 1 deletions(-)

در این نقطه لازم است که کارش را با جان به اشتراک بگذارد پس کامیت‌های برنچ featureA خود را به سمت سرور پوش می‌کند. جسیکا دسترسی پوش به برنچ master ندارد — فقط یکپارچه‌سازها دارند — پس نیاز است که او به برنچ دیگری پوش کند تا با جان همکاری کند:

$ git push -u origin featureA
...
To jessica@githost:simplegit.git
 * [new branch]      featureA -> featureA

جسیکا به جان ایمیل می‌زند و به او می‌گوید که کاری را در برنچی با نام featureA پوش کرده است و او می‌تواند اکنون آنرا ملاحظه کند. مادامی که منتظر بازخورد جان است، جسیکا تصمیم می‌گیرد که کار روی featureB را با جوزی شروع کند. برای شروع به کار او یک برنچ جدید می‌سازد و آنرا روی master سرور پایه‌گذاری می‌کند:

# Jessica's Machine
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch 'featureB'

حال جسیکا چند کامیت روی featureB برنچ می‌گیرد:

$ vim lib/simplegit.rb
$ git commit -am 'Make ls-tree function recursive'
[featureB e5b0fdc] Make ls-tree function recursive
 1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'Add ls-files'
[featureB 8512791] Add ls-files
 1 files changed, 5 insertions(+), 0 deletions(-)

مخزن جسیکا الآن به شبیه به این شکل است:

Jessica’s initial commit history.
نمودار 66. تاریخچهٔ اولیهٔ کامیت جسیکا.

او آماده است تا کار خود را پوش کند، اما ایمیلی از جوزی دریافت می‌کند که کمی کار اولیه دربارهٔ ویژگی «featureB» از قبل روی سرور با نام برنچ featureBee پوش شده است. پیش از اینکه جسیکا بتواند کار خود را روی سرور پوش کند، باید کار خود با آن تغییرات مرج کند. ابتدا جسیکا تغییرات جوزی را با git fetch می‌گیرد:

$ git fetch origin
...
From jessica@githost:simplegit
 * [new branch]      featureBee -> origin/featureBee

با فرض اینکه جسیکا هنوز روی برنچ featureB چک‌اوت است، اکنون او می‌تواند کار جوزی را در آن برنچ با git merge مرج کند:

$ git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
 lib/simplegit.rb |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

اکنون جسیکا می‌خواهد که تمام کارهای «featureB» که مرج کرده را به سمت سرور پوش کند،‌ اما نمی‌خواهد صرفاً روی برنچ featureB خودش پوش کند. از آنجایی که جوزی از قبل برنچ بالادست featureBee را ساخته است جسیکا می‌خواهد که روی آن به جای featureB پوش کند، که به وسیلهٔ دستورات زیر این کار را می‌کند:

$ git push -u origin featureB:featureBee
...
To jessica@githost:simplegit.git
   fba9af8..cd685d1  featureB -> featureBee

به این refspec می‌گویند. برای بحث جزئی‌تر دربارهٔ _refspec_های گیت و کارهای دیگری که می توانید با آنها انجام دهید به The Refspec مراجعه کنید. همچنین به فلگ -u توجه کنید؛ این مختصری برای --set-upstream است که برنچ‌ها را برای پوش و پول آسانتر در آینده تنظیم می‌کند.

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

$ git fetch origin
...
From jessica@githost:simplegit
   3300904..aad881d  featureA   -> origin/featureA

جسیکا می‌تواند لاگ کارهای جدید جان را با مقایسهٔ محتوای تازه فچ شدهٔ برنچ featureA با کپی محلی خودش از همان برنچ مشاهده کند:

$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 19:57:33 2009 -0700

    Increase log output to 30 from 25

اگر جسیکا از خروجی که می‌گیرد راضی است می‌تواند کار جدید جان را در برنچ محلی featureA محلی خود به شکل زیر مرج کند:

$ git checkout featureA
Switched to branch 'featureA'
$ git merge origin/featureA
Updating 3300904..aad881d
Fast forward
 lib/simplegit.rb |   10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)

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

$ git commit -am 'Add small tweak to merged content'
[featureA 774b3ed] Add small tweak to merged content
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git push
...
To jessica@githost:simplegit.git
   3300904..774b3ed  featureA -> featureA

تاریخچهٔ کامیت جسیکا اکنون باید شبیه به این باشد:

Jessica’s history after committing on a feature branch.
نمودار 67. تاریخچهٔ جسیکا پس از کامیت روی یک برنچ feature.

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

Jessica’s history after merging both her topic branches.
نمودار 68. تاریخچهٔ جسیکا پس از مرج شدن هر دو برنچ‌های موضوعی او.

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

Basic sequence of this managed-team workflow.
نمودار 69. ترتیب پایهٔ روند کاری این تیم مدیریت‌شده.

پروژهٔ عمومی فورک شده

مشارکت در یک پروژهٔ عمومی کمی متفاوت است. چرا که شما دسترسی بروزرسانی مستقیم برنچ‌های پروژه را ندارید و باید کار را به نحو دیگری به نگهدارنده‌ها برسانید. مثال اول مشارکت با فورک‌سازی میزبانان گیت را توصیف می‌کند که از فورک‌سازی ساده پشتیبانی می‌کنند. بسیاری از سایت‌های میزبانی این ویژگی را پشتیبانی می‌کنند (شامل گیت‌هاب،‌ گیت‌لب،‌ repo.or.cz، و غیره) و بسیاری از نگهدارندگان پروژه‌ها انتظار این نوع از همکاری را دارند. بخش بعدی به پروژه‌هایی می‌پردازد که ترجیح می‌دهند پچ‌های مشارکت‌شده را از طریق ایمیل دریافت کنند.

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

$ git clone <url>
$ cd project
$ git checkout -b featureA
  ... work ...
$ git commit
  ... work ...
$ git commit
یادداشت

ممکن است بخواهید از rebase -i برای اسکوآش کردن کار خود به یک کامیت یا تغییر ترتیب کارها در کامیت‌ها استفاده کنید تا اعمال پچ را برای نگهدارنده آسانتر کنید — به Rewriting History برای اطلاعات بیشتر دربارهٔ بازنویسی تاریخچه و ریبیس تعاملی مراجعه کنید.

هنگامی که کار برنچ تمام شده است و آماده‌اید تا آنرا با نگهدارنده به اشتراک بگذارید به صفحهٔ پروژهٔ اصلی بروید و روی دکمهٔ «Fork» کلیک کنید و فورک قابل نوشتن خود را از پروژه بسازید. سپس لازم دارید تا آدرس URL این مخزن را به عنوان یک ریموت جدید مخزن محلی خود اضافه کنید؛ در این مثال به آن myfork می‌گوییم:

$ git remote add myfork <url>

پس از آن باید کار جدید خود را به این مخزن پوش کنید. پوش کردن برنچ موضوعی که روی آن کار می‌کنید به مخزن فورک شده‌تان بسیار آسانتر از مرج کردن کار خود به برنچ master و پوش کردن آن است. علت این است که اگر کارتان تأیید یا چری-پیک نشود، مجبور نمی‌شوید که برنچ master خود را به قبل از مرج بازگردانید (عملیات چری پیک-گیت با جزئیات بیشتر در روند کاری ریبیس و چری-پیک بررسی شده است). اگر نگهدارنده کار شما را merge، rebase یا cherry-pick کند، شما باز هم مرج شدهٔ آنرا به نحوی از مخزن او دریافت خواهید کرد.

در هر حال، می‌توانید کار خود را به این شکل پوش کنید:

$ git push -u myfork featureA

هنگامی که کار شما به فورک مخزن پوش شد، لازم است که نگهدارندهٔ اصل پروژه را مطلع کنید که کاری کرده‌اید که دوست دارید او ادغامش کند. غالباً به این حرکت درخواست پول (Pull Request) گفته می‌شود و شما معمولاً چنین درخواستی را یا با وبسایت انجام می‌دهید — گیت‌هاب اکنون سازوکار «پول ریکوئست» خودش را دارد که به آن در GitHub می‌پردازیم — یا می‌توانید دستور git request-pull را اجرا و خروجی حاصله را به طور دستی به نگهدارندهٔ پروژه ایمیل کنید.

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

$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
Jessica Smith (1):
        Create new function

are available in the git repository at:

  git://githost/simplegit.git featureA

Jessica Smith (2):
      Add limit to log function
      Increase log output to 30 from 25

 lib/simplegit.rb |   10 +++++++++-
 1 files changed, 9 insertions(+), 1 deletions(-)

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

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

$ git checkout -b featureB origin/master
  ... work ...
$ git commit
$ git push myfork featureB
$ git request-pull origin/master myfork
  ... email generated request pull to maintainer ...
$ git fetch origin

اکنون هر کدام از موضوعات شما درون یک سیلو — مشابه با صف پچ — است که می‌توانید بازنویسی، ریبیس یا ویرایش کنید بدون اینکه موضوعات با یکدیگر تداخل پیدا کنند و یا به یکدیگر وابسته باشند،‌ به این صورت:

Initial commit history with `featureB` work.
نمودار 70. تاریخچهٔ اولیه کامیت با کار featureB.

فرض کنیم که نگهدارندهٔ پروژه یک برنچ از تعدادی وصلهٔ دیگر پول کرده و برنچ اول شما را امتحان کرده است اما دیگر به طور تمیز قابل اعمال نیست. در این حالت شا می‌توانید که آن برنچ را به نوک origin/master ریبیس و تداخلات را برای نگهدارنده حل کرده و دگربار تغییرات خود را ارائه کنید:

$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA

این کار تاریخچهٔ شما را به نحوی بازنویسی می‌کند که اکنون مشابه تاریخچهٔ کامیت پس از کار featureA. بشود.

Commit history after `featureA` work.
نمودار 71. تاریخچهٔ کامیت پس از کار featureA.

از جهت اینکه شما برنچ را ریبیس کرده‌اید، باید -f را به دستور پوش خود بدهید تا بتوانید برنچ featureA سرور را با کامیتی که فرزند آن نیست بازنویسی کنید. همچنین به جای آن می‌توانید که این کار جدید را به برنچ جدید روی سرور (احتمالاً featureAv2 نام) پوش کنید.

بیایید نگاهی به یک سناریو که احتمال بیشتری دارد بیاندازیم: نگهدارنده به کار شما در برنچ دوم نگاه کرده و از مفهوم کلی آن راضی است اما دوست دارد که تغییراتی در جزئیات پیاده‌سازی آن اعمال کنید. علاوه‌بر آن، این فرصت را پیدا می‌کنید تا کار خود را بر نوک برنچ master حاضر پایه‌گذاری کنید. یک برنچ جدید می‌سازید که بر پایهٔ برنچ origin/master است، تغییرات featureB را آنجا اسکوآش، تداخلات را حل، جزئیات پیاده‌سازی را اعمال و آنرا به عنوان یک برنچ جدید پوش می‌کنید.

$ git checkout -b featureBv2 origin/master
$ git merge --squash featureB
  ... change implementation ...
$ git commit
$ git push myfork featureBv2

آپشن --squash تمام کار روی برنچ مرج شده را می‌گیرد و آنرا به یک دسته تغییرات تبدیل می‌کند که حالتی برای مخزن ایجاد می‌کند که یک مرج واقعی ایجاد می‌کرد، بدون ساختن یک مرج کامیت واقعی. این به این معناست که کامیت آیندهٔ شما یک والد خواهد داشت و به شما این اجازه را خواهد داد تا تمام تغییرات خود را از برنچی دیگر اضافه کنید و پس از آن قبل از کامیت جدید ساختن تغییراتی به آنها اعمال کنید. همچنین آپشن --no-commit می‌تواند برای به تعویق انداختن کامیت در حالتی که مرج معمولی انجام می‌دهید مفید واقع شود.

در این نقطه می‌توانید نگهدارنده را مطلع کنید که تغییرات درخواست شده را اعمال کرده‌اید و می‌تواند آنها را در برنچ featureBv2 مشاهده کند.

Commit history after `featureBv2` work.
نمودار 72. تاریخچهٔ کامیت پس از کار featureBv2.

پروژه‌های عمومی روی ایمیل

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

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

$ git checkout -b topicA
  ... work ...
$ git commit
  ... work ...
$ git commit

حال شما دو کامیت دارید و می‌خواهید آنها را به لیست صندوق ارسال کنید. از git format-patch برای ساختن فایل‌های در قالب mbox استفاده می‌کنید تا بتوانید آنها را به صندوق ایمیل کنید — این دستور هر کامیت را به ایمیلی تبدیل می‌کند که خط اول پیغام کامیت موضوع آن و در بدنهٔ ایمیل جزئیات بعلاوهٔ پچی است که در کامیت معرفی شده است. نکتهٔ خوب آن این است که اعمال پچی که از طریق format-patch ساخته شده تمام اطلاعات کامیت را به درستی نگهداری می‌کند.

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-increase-log-output-to-30-from-25.patch

دستور format-patch نام پچ فایل‌هایی که می‌سازد را چاپ می‌کند. آپشن -M به گیت می‌گوید که دنبال بازنامگذاری‌ها بگردد. در آخر فایل‌ها اینچنین خواهند شد:

$ cat 0001-add-limit-to-log-function.patch
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

---
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 76f47bc..f9815f1 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -14,7 +14,7 @@ class SimpleGit
   end

   def log(treeish = 'master')
-    command("git log #{treeish}")
+    command("git log -n 20 #{treeish}")
   end

   def ls_tree(treeish = 'master')
--
2.1.0

همچنین شما می‌توانید این پچ فایل‌ها را ویرایش کنید تا اطلاعات بیشتری برای ایمیل لیست اضافه کنید که نمی‌خواهید در پیغام کامیتتان باشند. اگر می‌خواهید می‌توانید متنی بین خطوط --- و ابتدای پچ قرار دهید (خط diff --git)، توسعه‌دهندگان می‌توانند آنرا بخوانند، اما آن محتوا توسط فرآیند پچ کردن نادیده گرفته می‌شود.

برای ارسال این لیست نامه‌ها، یا می‌توانید محتوای فایل‌ها را به برنامهٔ ایمیلتان الحاق کنید یا آنرا با یک برنامهٔ خط فرمان ارسال کنید. الحاق کردن متن معمولاً منجر به مشکلات قالب‌بندی می‌شود، مخصوصاً با کلاینت‌های «هوشمندتر» که خطوط جدید و دیگر فضاهای سفید را به درستی نگه نمی‌دارند. خوشبختانه گیت ابزاری برای فرستادن اصولی پچ‌ها از طریق IMAP دارد که ممکن است استفاده از آن برای شما آسانتر باشد. ما نحوه ارسال پچ‌ها با جیمیل را نشان خواهیم داد، جیمیلی که شناخته‌شده‌ترین ایمیل ایجنتی است که ما می‌دانیم؛ شما می‌توانید دستورالعمل‌های جزئی‌تر را برای چندی برنامهٔ ایمیل دیگر در آخر فایل Documentation/SubmittingPatches پیشتر ذکرشده در سورس کد گیت بخوانید.

ابتدا لازم است که بخش imap را در فایل ~/.gitconfig تنظیم کنید. شما می‌توانید هر مقدار را جداگانه با مجموعه‌ای از دستورات git config اجرا کنید یا به طور دستی آنها را اضافه کنید، در هر صورت فایل کانفیگتان در آخر باید به شبیه به این باشد:

[imap]
  folder = "[Gmail]/Drafts"
  host = imaps://imap.gmail.com
  user = user@gmail.com
  pass = YX]8g76G_2^sFbd
  port = 993
  sslverify = false

اگر سرور IMAP شما از SSL استفاده نمی‌کند، دو خط آخر احتمالاً‌احتیاج نیستند و مقدار هاست به جای imaps://‍ مقدار imap:// خواهد بود. وقتی که تنظیم شد می‌توانید از git imap-send استفاده کنید تا دستهٔ پچ‌ها را در پوشهٔ پیش‌نویس‌های سرور IMAP مشخص‌شده قرار دهید.

$ cat *.patch |git imap-send
Resolving imap.gmail.com... ok
Connecting to [74.125.142.109]:993... ok
Logging in...
sending 2 messages
100% (2/2) done

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

همچنین می‌توانید پچ‌ها را از طریق یک سرور SMTP ارسال کنید. مانند قبل می‌توانید به طور جداگانه با دسته‌ای از دستورات git config هر مقدار را تنظیم کنید یا می‌توانید به طور دستی بخش sendemail را به فایل ~/.gitconfig خود بیافزایید:

[sendemail]
  smtpencryption = tls
  smtpserver = smtp.gmail.com
  smtpuser = user@gmail.com
  smtpserverport = 587

بعد از اینکه تمام شد می‌توانید از git send-email استفاده کنید تا پیغام‌های خود را ارسال کنید:

$ git send-email *.patch
0001-add-limit-to-log-function.patch
0002-increase-log-output-to-30-from-25.patch
Who should the emails appear to be from? [Jessica Smith <jessica@example.com>]
Emails will be sent from: Jessica Smith <jessica@example.com>
Who should the emails be sent to? jessica@example.com
Message-ID to be used as In-Reply-To for the first email? y

سپس، گیت برای هر پچی که ارسال می‌کنید دسته‌ای از لاگ‌ها را خروجی خواهد داد که اینچنین خواهند:

(mbox) Adding cc: Jessica Smith <jessica@example.com> from
  \line 'From: Jessica Smith <jessica@example.com>'
OK. Log says:
Sendmail: /usr/sbin/sendmail -i jessica@example.com
From: Jessica Smith <jessica@example.com>
To: jessica@example.com
Subject: [PATCH 1/2] Add limit to log function
Date: Sat, 30 May 2009 13:29:15 -0700
Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com>
X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty
In-Reply-To: <y>
References: <y>

Result: OK

خلاصه

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

scroll-to-top