git branching model

چرا گیت؟

جنگ شدیدی بین توسعه دهندگان نرم افزار در مورد نرم افزارهای کنترل ورژن برقرار است. به عنوان یک برنامه نویس من Git را به همه آنها ترجیح میدهم. Git روش برنامه نویسان را در مورد ادغام و شاخه بندی تغییر داد. در دنیای کلاسیک نرم افزاری های کنترل ورژن (CVS/Subversion) که من از آن می آیم ادغام و شاخه بندی ( merging/branching) کار ترسناکی محسوب میشود و چیزی نیست که بخواهید هر روز انجام دهید.

اما با گیت این اعمال با گیت بسیار ساده و ارزان خواهند بود و واقعا به عنوان فرآیند روزانه کاری شما بدل خواهند شد. در کتابهای CVS , Subversion شاخه بندی و ادغام در فصل های آخر کتاب بحث میشود اما در هرکتاب گیت بحث در این مورد از فصل سوم شروع میشود.

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

غیر متمرکز اما متمرکز

ما از یک مخزن به عنوان مخزن اصلی استفاده خواهیم کرد. خاطر نشان میکنیم که این مخزن صرفا اصلی در نظرگرفته میشود ( زیرا گیت غیر متمرکز است و چیزی بنام مخزن اصلی و مرکزی در آن وجود ندارد). به این مخزن با نام origin رجوع خواهیم کرد زیرا این نام برای همه کاربران Git آشناست.

center-decenter

هریک از برنامه نویسان به مخزن مرکزی ارسال یا دریافت میکنند اما در پس این ارتباط ارسال-دریافت (pull-push) متمرکز هر برنامه نویس میتواند تغییرات را از سایر برنامه نویسان درخواست  کند تا زیر تیمهای کوچکتر تشکیل شوند. برای مثال؛ خیلی مفید خواهد بود که با یک یا دو برنامه نویس روی یک ویژگی جدید نرم افزار کارکنیم قبل از اینکه کار در حال اجرای خود را بصورت دایمی به مخزن مرکزی ارسال نماییم. در شکل بالا سه زیر تیم الیس-باب و الیس-دیوید و کلیر-دیوید وجود دارد.

مخازن اصلی

مخزن مرکزی دو شاخه اصلی با عمر نامحدود را نگه میدارد:

  • master
  • develop

شاخه master در مخزن origin برای کاربر گیت آشناست. به موازات شاخه master شاخه دیگری بنام develop وجود دارد.

شاخه origin/master را به عنوان شاخه اصلی که کد HEAD بعنوان سورس کد نهایی و قابل انتشار دربردارد؛ در نظر میگیریم.

به همین ترتیب شاخه origin/develop در برگیرنده آخرین تغییرات برنامه برای انتشار در نسخه بعدی می باشد. برخی این مخزن را مخزن تجمیع (integeration branch) می نامند. این همان مخزنی است که هر بیلد شبانه اتوماتیک (automatic nightly build) از آن ساخته میشود.

master-develop

وقتی که سورس کد در شاخه develop به نقطه پایداری میرسد و برای انتشار آماده میگردد تمام تغییرات باید دوباره با مخزن master ادغام گردد و با یک شماره نشر (release number) برچسب گذاری (tag) شود. جزییات این کار در ادامه بحث خواهد شد.

بنابراین هر بار که تغییرات با master ادغام میشود بنا به تعریف نسخه جدیدی از محصول داریم. ما میتوانیم از یک اسکریپت Git برای ساخت اتوماتیک و ارسال محصول نهایی به سرورهای در حال کار خود (production) درهر بار ارسال به شاخه master استفاده نماییم.

شاخه های پشتیبان

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

انواع شاخه های مختلفی که استفاده میکنیم عبارتند از:

  • شاخه های ویژگیها
  • شاخه های انتشار
  • شاخه های اشکالزدایی (Hotfix)

هر یک از این شاخه ها هدف مشخصی دارند و قواعد محکمی در مورد اینکه میتوانند از کدام شاخه ها جدا شوند و یا با کدام شاخه ها ادغام شوند وجود دارند. در ادامه این شاخه ها را بررسی خواهیم کرد.

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

شاخه های ویژگی
شاخه های ویژگی (Feature) میتوانند از شاخه develop مشتق شوند و باید در نهایت با شاخه develop ادغام گردند. بصورت قراردادی نام این شاخه هرچیزی غیر از master, develop, release-* و یا hotfix-* میتواند باشد.
feature branches
شاخه های ویژگی ( گاهی اوقات شاخه های عنوان نامیده میشوند) برای توسعه امکانات و ویژگی های آتی و یا یک نسخه انتشار  در آینده به کار میروند. در هنگام شروع توسعه، ممکن است نسخه هدفی که این ویژگی قرار است در آن به کار گرفته شود مشخص نباشد. در واقع این شاخه تا زمانی که یک ویژگی جدید در حال توسعه باشد وجود خواهد داشت.
شاخه های ویژگی عموما فقط در مخزن developer موجود هستند و نه در مخزن origin.

ایجاد یک مخزن ویژگی

در هنگام شروع برای کار روی یک امکانات جدید، از شاخه develop شاخه جدیدی را مشتق میکنیم.
$ git checkout -b myfeature develop
Switched to a new branch "myfeature"

به کاربردن یک ویژگی کامل شده در develop

امکانات کامل شده میتوانند دوباره با شاخه develop ادغام شوند تا در نسخه های آینده مورد استفاده قرار گیرند.

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff myfeature
Updating ea1b82a..05e9557
(Summary of changes)
$ git branch -d myfeature
Deleted branch myfeature (was 05e9557).
$ git push origin develop

عبارت --no-ff باعث میشود که ادغام همیشه یک شی commit جدید ایجاد کند، حتی اگر ادغام بتواند با یک fast-forward شکل بگیرد. این امر باعث میشود اطلاعات تاریخ یک شاخه ویژگی از دست نرود و تمام commit هایی که یک ویژگی را شکل میدهند را یکجا جمع کند. مقایسه کنید :

merge without fast forward

در مورد سمت راست، از روی تاریخچه گیت نمیتوان دید که کدام کامیتها یک ویژگی را شکل داده اند ( البته شما میتوانید تک تک پیام های لاگ را مطالعه کنید!).

این درست است که این کار کامیتهای خالی بیشتری به وجود می آورد ولی آنچه به دست می آورید از آنچه از دست میدهید بیشتر است.

اگر راهی پیدا کردید که پارامتر --no--ff پارامتر پیشفرض گیت شود حتما آن را انجام دهید.

شاخه های انتشار

مشتق میشود از : develop

باید ادغام  شود با : develop و master

قاعده نام گذاری : release-*

شاخه های انتشار (release branches)  برای کمک به انتشار یک نسخه جدید کاری از پروژه هستند. میتوان در آنها از اشکالزادیی های ریز و آماده سازی متادیتا ( شماره ورژن و تاریخ بیلد و .. ) برای انتشار استفاده کرد. با انجام همه این کارها در یک شاخه انتشار، شاخه develop برای دریافت ویژگیهای نسخه انتشار بزرگ بعدی آماده میشود.

لحظه کلیدی برای ایجاد شاخه releas‌e از یک شاخه develop وقتی است که شاخه develop به وضعیت مورد انتظار برای یک نسخه جدید رسیده است. تمام ویژگی هایی که برای نشر مورد هدف قرار گرفته اند باید با شاخه develop ادغام شوند. ویژگی هایی که برای نسخه های بعدی در نظر گرفته شده اند باید تا بعد از مشتق شدن شاخه release منتظر بمانند.

دقیقا در شروع یک شاخه انتشار نسخه آینده یک شماره ورژن میگیرد نه زودتر. تا آن لحظه نسخه develop بازتابی از تغییرات برای نسخه بعدی می باشد. اما اینکه نسخه بعدی که باید 0.3 باشد یا 0.1 معلوم نیست تا زمانی که شاخه release شروع شود. این تصمیم در شروع شاخه release و براساس قواعد پروژه روی شماره گذاری ورژنها گرفته میشود.

ایجاد یک شاخه realese

شاخه های release از شاخه develop ساخته میشوند. برای مثال فرض کنید نسخه 1.1.5 نسخه در حال اجرای نرم افزار باشد و ما یک نسخه بزرگ جدید در راه داشته باشیم. وضعیت develop آماده برای release بعدی می باشد و ما تصمیم گرفته ایم که این نسخه 1.2 باشد. بنابراین ما یک شاخه از develop مشتق میکنیم و به آن نامی میدهیم که ورژن را نشان دهد:

$ git checkout -b release-1.2 develop
Switched to a new branch "release-1.2"
$ ./bump-version.sh 1.2
Files modified successfully, version bumped to 1.2.
$ git commit -a -m "Bumped version number to 1.2"
[release-1.2 74d9424] Bumped version number to 1.2
1 files changed, 1 insertions(+), 1 deletions(-)

بلافاصله بعد از ایجاد شاخه جدید و سوییچ کردن به آن شماره ورژن را تغییر میدهیم. یک اسکریپت شل bump-version.sh وجود دارد که میتواند این کار را برای ما انجام دهد ( البته میتوان این کار را بطور دستی انجام داد). بعد از تغییر شماره ورژن در تمام فایلها کامیت انجام میدهیم.

شاخه جدید ممکن است تا زمانی که نسخه نشر بیرون کشیده شود وجود داشته باشد. در طول این مدت اشکالزدایی های جزیی ممکن است روی این شاخه انجام پذیرد. افزودن ویژگیهای بزرگ روی این شاخه توصیه نمیشود. آنها باید با شاخه develop ادغام شوند بنابراین تا نسخه بزرگ بعدی صبرکنید.

پایان یک شاخه release

وقتی که یک شاخه release به وضعیتی میرسد که برای یک نشر واقعی آمده باشد، باید اقداماتی انجام گردد. در ابتدا شاخه release با شاخه master ادغام میشود ( از آنجایی که هر کامیت در master یک نسخه محسوب میشود). سپس این کامیت روی master باید برچسب گذاری شود تا در آینده بتوان به آن ارجاع کرد. سرانجام تغییرات انجام شده در شاخه release باید با شاخه develop ادغام شوند تا انتشارهای بعدی نیز شامل رفع اشکالات باشند.

دو مرحله اول در Git:

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2

حالا انتشار انجام شده و برای ارجاع در آینده برچسب گذاری شده است. از پارامترهای -u و -s میتوانید برای امضای دیجیتالی برچسبهای خود استفاده کنید.

برای اینکه تغییرات انجام شده در شاخه release را داشته باشیم، باید دوباره آن را با شاخه develop ادغام کنیم :

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)

این مرحله ممکن است با تداخل (conflict) مواجه شود. اگر این طور شد آن را برطرف و سپس کامیت نمایید.

حالا کار ما تمام شده و از آنجا که به شاخه release دیگر نیازی نداریم آن را حذف میکنیم:

$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).

شاخه های اشکال زدایی (Hotfix)

میتواند مشتق شود از : master

باید دوباره ادغام شود با : develop و master

قاعده نامگذاری: hotfix-*

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

hotfix branches

ضرورت این کار در این است که کار اعضای تیم ممکن است روی شاخه develop ادامه داشته باشد در حالیکه شخص دیگری در حال ترمیم سریع خطای نسخه در حال اجراست.

ایجاد یک شاخه hotfix

شاخه های hotfix از شاخه master بوجود می آیند. برای مثال اگر نسخه در حال اجرا نسخه 1.2 باشد که یک باگ باعث ایجاد اشکالاتی در آن شده و تغییرات در نسخه develop هنوز پایدار نیستند ما میتوانیم از یک شاخه hotfix برای شروع رفع ایراد استفاده کنیم :

$ git checkout -b hotfix-1.2.1 master
Switched to a new branch "hotfix-1.2.1"
$ ./bump-version.sh 1.2.1
Files modified successfully, version bumped to 1.2.1.
$ git commit -a -m "Bumped version number to 1.2.1"
[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1
1 files changed, 1 insertions(+), 1 deletions(-)

تغییر شماره ورژن بعد از مشتق کردن شاخه را از یاد نبرید!

سپس بعد از رفع ایراد، تغییرات انجام شده را کامیت کنید.

$ git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)

پایان یک شاخه hotfix

بعد از اتمام اشکالزدایی، تغییرات باید با شاخه master ادغام شوند؛ اما همچنین باید با شاخه develop نیز ادغام گردند تا از اصلاحات انجام شده در نسخه بعدی اطمینان حاصل شود. این کار دقیقا مثل اتمام شاخه های release انجام میشود.

ابتدا شاخه master را بروز میکنیم و نسخه را نامگذاری میکنیم.

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2.1

سپس اصلاح انجام شده را در develop نیز اعمال میکنیم :

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)

یک استثنا برای این قاعده وقتی است که یک شاخه release در حال حاضر وجود دارد و اصلاحات انجام شده باید بجای شاخه develop با شاخه release ادغام شوند. ادغام باگفیکس با شاخه release در نهایت هنگام اتمام شاخه release منجر به ادغام با شاخه develop خواهد شد.

و در نهایت شاخه موقتی ایجاد شده را حذف میکنیم :

$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).

جمع بندی

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

منبع :a successful git branching model