Chapters ▾ 2nd Edition

5.2 گیت توزیع‌شده (Distributed git) - مشارکت در یک پروژه (Contributing to a Project)

مشارکت در یک پروژه (Contributing to a Project)

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

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

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

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

تمام این سوالات می‌توانند بر نحوه مؤثر مشارکت شما در پروژه و جریان‌های کاری مورد ترجیح یا قابل دسترس برای شما تأثیر بگذارند. ما جنبه‌هایی از هر یک را در مجموعه‌ای از موارد کاربردی بررسی خواهیم کرد، از ساده به پیچیده؛ شما باید بتوانید با استفاده از این مثال‌ها جریان‌های کاری خاص مورد نیاز خود را در عمل بسازید.

راهنمایی‌های کامیت (Commit Guidelines)

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

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

Output of `git diff --check`
نمودار 56. Output of git diff --check

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

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

این روش همچنین برگشت یا حذف یکی از مجموعه تغییرات را در آینده آسان‌تر می‌کند. بازنویسی تاریخچه (Rewriting History) چند ترفند مفید گیت برای بازنویسی تاریخچه و آماده‌سازی تعاملی فایل‌ها را شرح می‌دهد — از این ابزارها برای ایجاد یک تاریخچه تمیز و قابل فهم قبل از ارسال کار به دیگران استفاده کنید.

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

در اینجا قالبی است که می‌توانید دنبال کنید، که ما کمی آن را از نسخه اصلی در که در ابتدا توسط Tim Pope نوشته شده اقتباس کرده‌ایم:

خلاصه‌ای کوتاه با حرف بزرگ (۵۰ کاراکتر یا کمتر)

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

پیام کامیت خود را به صورت امری بنویسید: «Fix bug» نه «Fixed bug» یا «Fixes bug». این قرارداد با پیام‌های کامیت تولید شده توسط دستورات مثل git merge و git revert همخوانی دارد.

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

- نکات گلوله‌ای هم پذیرفته شده‌اند

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

- از تورفتگی معلق استفاده کنید
 اگر تمام پیام‌های کامیت شما از این الگو پیروی کنند، کار برای شما و توسعه‌دهندگانی که با آنها همکاری می‌کنید بسیار آسان‌تر خواهد بود.
پروژه Git پیام‌های کامیتی به‌خوبی قالب‌بندی‌شده‌ای دارد—برای دیدن نحوه نمایش تاریخچه پروژه با قالب‌بندی مناسب، دستور `git log --no-merges` را در آن اجرا کنید.
یادداشت
Do as we say, not as we do.

برای اختصار، بسیاری از مثال‌های این کتاب پیام‌های کامیت به این زیبایی ندارند؛ در عوض، ما صرفاً از گزینه -m در دستور git commit استفاده می‌کنیم.

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

تیم کوچک خصوصی (Private Small Team)

ساده‌ترین تنظیمی که احتمالاً با آن مواجه می‌شوید، پروژه‌ای خصوصی با یک یا دو توسعه‌دهنده دیگر است. در اینجا منظور از «خصوصی» یعنی کد منبع بسته است—برای جهان بیرون قابل دسترسی نیست. شما و دیگر توسعه‌دهندگان دسترسی برای ارسال تغییرات (push) به مخزن را دارید.

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

# 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(-)

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

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

خط آخر خروجی بالا پیام بازگشتی مفیدی از عملیات push را نشان می‌دهد. قالب کلی آن به صورت <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'

در این حالت، ارسال (push) جان به دلیل ارسال قبلی جسیکا با شکست مواجه می‌شود. این موضوع به‌ویژه اگر به Subversion عادت دارید مهم است، چون مشاهده می‌کنید که دو توسعه‌دهنده روی فایل مشابهی کار نکرده‌اند. اگرچه در Subversion، اگر فایل‌های متفاوتی ویرایش شوند، ادغام به‌صورت خودکار روی سرور انجام می‌شود، اما در Git ابتدا باید کامیت‌ها را به‌صورت محلی ادغام کنید. به عبارت دیگر، جان باید ابتدا تغییرات جسیکا را دریافت (fetch) و آنها را در مخزن محلی خودش ادغام کند، سپس اجازه ارسال خواهد داشت.

اولین قدم این است که جان کار جسیکا را دریافت می‌کند (این فقط دریافت است و هنوز ادغام انجام نشده):

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

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

John’s divergent history
نمودار 57. John’s divergent history

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

$ 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`
نمودار 58. John’s repository after merging origin/master

در این مرحله، جان ممکن است بخواهد کد جدید را تست کند تا مطمئن شود که کارهای جسیکا تأثیری روی کارهای خودش ندارد و اگر همه چیز خوب بود، نهایتاً می‌تواند کار ادغام‌شده جدید را به سرور ارسال کند:

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

در نهایت، تاریخچه کامیت‌های جان به این صورت خواهد بود:

John’s history after pushing to the `origin` server
نمودار 59. John’s history after pushing to the origin server

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

Jessica’s topic branch
نمودار 60. Jessica’s topic branch

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

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

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

Jessica’s history after fetching John’s changes
نمودار 61. Jessica’s history after fetching John’s changes

جسیکا فکر می‌کند شاخه موضوعی‌اش آماده است، اما می‌خواهد بداند کدام بخش از کارهای دریافت‌شده جان را باید با کار خودش ادغام کند تا بتواند تغییرات را ارسال کند. او دستور 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(-)

مشکلی پیش نمی‌آید؛ همانطور که می‌بینید این یک ادغام ساده و سریع بود. جسیکا اکنون فرایند ادغام محلی را با ادغام کارهای قبلاً دریافت‌شده جان که در شاخه 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
نمودار 62. Jessica’s history after merging John’s changes

اکنون شاخه 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
نمودار 63. Jessica’s history after pushing all changes back to the server

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

General sequence of events for a simple multiple-developer Git workflow
نمودار 64. General sequence of events for a simple multiple-developer Git workflow

تیم خصوصی مدیریت‌شده (Private Managed Team)

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

فرض کنید جان و جسیکا روی یک ویژگی با هم کار می‌کنند (به آن “featureA” می‌گوییم)، در حالی که جسیکا و توسعه‌دهنده سومی به نام جوزی روی ویژگی دوم (مثلاً “featureB”) کار می‌کنند. در این حالت، شرکت از نوعی روند کاری به نام مدیر ادغام استفاده می‌کند که کار گروه‌های فردی تنها توسط مهندسان خاصی ادغام می‌شود و شاخه 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
نمودار 65. Jessica’s initial commit history

او آماده است که تغییراتش را به سرور ارسال کند، اما ایمیلی از جوزی دریافت می‌کند که شاخه‌ای با مقداری کار اولیه روی “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 را ایجاد کرده است، جسیکا می‌خواهد به همان شاخه پوش کند، که این کار را با دستور زیر انجام می‌دهد:

$ 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
نمودار 66. Jessica’s history after committing on a feature branch

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

Jessica’s history after merging both her topic branches
نمودار 67. Jessica’s history after merging both her topic branches

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

Basic sequence of this managed-team workflow
نمودار 68. Basic sequence of this managed-team workflow

پروژه‌ی عمومی فورک شده (Forked Public Project)

مشارکت در پروژه‌های عمومی کمی متفاوت است. چون شما اجازه‌ی به‌روزرسانی مستقیم شاخه‌ها روی پروژه را ندارید، باید کار خود را به مدیران پروژه به روش دیگری برسانید. این مثال اول، مشارکت از طریق فورک کردن روی میزبان‌های گیت که فورک آسان را پشتیبانی می‌کنند، را شرح می‌دهد. بسیاری از سایت‌های میزبانی این ویژگی را دارند (از جمله GitHub، BitBucket، 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 خود ادغام کرده و آن را پوش کنید. دلیل این موضوع این است که اگر کار شما پذیرفته نشود یا به‌صورت cherry-pick انتخاب شود، نیازی به عقب بردن شاخه‌ی master خود ندارید (عملیات cherry-pick در گیت با جزئیات بیشتر در روندهای کاری بازپایه‌گذاری و انتخاب گزینشی (Rebasing and Cherry-Picking Workflows) توضیح داده شده). اگر مدیران پروژه کار شما را merge، rebase یا cherry-pick کنند، در نهایت از طریق pull گرفتن از مخزن آن‌ها دوباره آن را دریافت خواهید کرد.

در هر صورت، می‌توانید کار خود را با دستور زیر پوش کنید:

$ git push -u myfork featureA

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

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

$ 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:

  https://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 را دنبال کند و کارتان را در شاخه‌های موضوعی انجام دهید که در صورت رد شدن بتوانید به آسانی آنها را حذف کنید. جدا کردن موضوعات کاری در شاخه‌های جداگانه همچنین کار بازپایه‌گذاری (rebase) را آسان‌تر می‌کند، اگر در این فاصله نوک مخزن اصلی تغییر کرده باشد و کامیت‌های شما به صورت تمیز اعمال نشوند. برای مثال، اگر بخواهید موضوع دومی را به پروژه ارائه دهید، روی همان شاخه موضوعی که تازه ارسال کرده‌اید کار نکنید — از شاخه 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
نمودار 69. Initial commit history with featureB work

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

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

این کار تاریخچه شما را طوری بازنویسی می‌کند که شبیه Commit history after featureA work شود.

Commit history after `featureA` work
نمودار 70. Commit history after featureA work

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

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

$ 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
نمودار 71. Commit history after featureBv2 work

پروژه عمومی از طریق ایمیل (Public Project over Email)

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

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

$ 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 برای قرار دادن سری پچ‌ها در پوشه Drafts سرور 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

در این مرحله، باید بتوانید به پوشه Drafts خود بروید، فیلد گیرنده (To) را به فهرست پستی که پچ را به آن ارسال می‌کنید تغییر دهید، احتمالاً نفر مسئول یا نگهدارنده بخش را CC کنید و ایمیل را ارسال نمایید.

همچنین می‌توانید پچ‌ها را از طریق سرور 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
نکته

برای دریافت کمک در تنظیم سیستم و ایمیل، نکات و ترفندهای بیشتر، و محیط آزمایشی برای ارسال پچ آزمایشی از طریق ایمیل، به https://git-send-email.io مراجعه کنید.

خلاصه (Summary)

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

در ادامه، خواهید دید چگونه سمت دیگر ماجرا را مدیریت کنید: نگهداری یک پروژه گیت. یاد می‌گیرید چگونه یک دیکتاتور خیرخواه یا مدیر ادغام باشید.

scroll-to-top