جریان کاری gitflow از زبان خالق آن

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

توضیح نویسنده:

این مدل در سال ۲۰۱۰ طراحی شد، زمانی که تازه گیت به‌وجود آمده بود، حالا بیش از ۱۰ سال از آن زمان گذشته است. در این ۱۰ سال، git-flow (مدل شاخه‌بندی که در این مقاله توضیح داده شده) در بسیاری از تیم‌های نرم‌افزاری به شدت محبوب شده است تا جایی که مردم شروع به برخورد با آن به‌عنوان یک استاندارد کرده‌اند — اما متاسفانه همچنین به‌عنوان یک دگما یا نوش‌دارو.

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

این نرم‌افزارها همان چیزی نیستند که من هنگام نوشتن پست وبلاگ ۱۰ سال پیش در نظر داشتم. اگر تیم شما در حال انجام تحویل پیوسته نرم‌افزار است، پیشنهاد می‌کنم یک روند کاری ساده‌تر (مانند GitHub flow) را به جای تلاش برای اعمال git-flow در تیم خود انتخاب کنید.

اما اگر در حال ساخت نرم‌افزاری هستید که صراحتا نسخه‌بندی شده است یا اگر نیاز به پشتیبانی از نسخه‌های مختلف نرم‌افزار در محیط دارید، آنگاه git-flow ممکن است همانقدر برای تیم شما مناسب باشد که در ۱۰ سال گذشته برای دیگران بوده است. در این صورت، لطفاً ادامه مطلب را بخوانید.

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

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

مدل gitflow

چرا Git؟

برای بررسی جامع مزایا و معایب Git نسبت به سیستم‌های کنترل نسخه متمرکز، می‌توانید در اینترنت جستجو کنید. بحث‌های داغ بسیاری در این زمینه وجود دارد. به‌عنوان یک توسعه‌دهنده، Git را برتر از سایر ابزارهای موجود می‌دانم. Git واقعاً نحوه تفکر توسعه‌دهندگان درباره ادغام و انشعاب را تغییر داده است. در دنیای کلاسیک CVS/Subversion که از آن آمده‌ام، همیشه ادغام/انشعاب کمی ترسناک به نظر می‌رسید (“مراقب کانفلیکت‌های ادغام باشید، نیش می‌زنند!”) و کاری بود که فقط گهگاهی انجام می‌دادیم.

اما با Git، این کارها بسیار ساده و ارزان شده‌اند و به‌عنوان یکی از بخش‌های اصلی فرآیند کاری روزانه شما در نظر گرفته می‌شوند. برای مثال، در کتاب‌های مربوط به CVS/Subversion، موضوع انشعاب و ادغام معمولاً در فصول پایانی (برای کاربران پیشرفته) بررسی می‌شود، درحالی‌که در هر کتاب Git، این موضوع در فصل سوم (مقدمات) پوشش داده شده است.

به دلیل این سادگی و تکرار، دیگر انشعاب و ادغام چیزی نیست که از آن بترسید. ابزارهای کنترل نسخه قرار است بیشتر از هر چیز دیگری در انشعاب/ادغام به شما کمک کنند.

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

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

تنظیم مخزن (repository) که استفاده می‌کنیم و با این مدل انشعاب به‌خوبی کار می‌کند، شامل یک مخزن مرکزی “حقیقت” است. توجه داشته باشید که این مخزن تنها به‌عنوان مخزن مرکزی در نظر گرفته می‌شود (زیرا Git یک DVCS است و از نظر فنی چیزی به نام مخزن مرکزی وجود ندارد). ما به این مخزن به‌عنوان origin اشاره می‌کنیم، زیرا این نام برای همه کاربران Git آشنا است.

هر توسعه‌دهنده تغییرات را از origin دریافت و به آن ارسال می‌کند. اما علاوه بر این ارتباطات متمرکز دریافت-ارسال، هر توسعه‌دهنده می‌تواند تغییرات را از دیگر همکاران نیز دریافت کند تا تیم‌های کوچکتری تشکیل دهد. برای مثال، این کار می‌تواند زمانی مفید باشد که بخواهید با دو یا چند توسعه‌دهنده دیگر روی یک ویژگی جدید بزرگ کار کنید، قبل از اینکه تغییرات در حال انجام را زودهنگام به origin ارسال کنید. در شکل بالا، تیم‌های کوچک‌تری مانند آلیس (Alice) و باب (Bob)، آلیس و دیوید (David)، و کلر (Clair) و دیوید وجود دارند.

از نظر فنی، این تنها به این معناست که آلیس یک Git remote تعریف کرده که به مخزن باب اشاره می‌کند، و بالعکس.

شاخه‌های اصلی در gitflow

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

  • master
  • develop

شاخه‌های اصلی در gitflow

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

ما origin/master را به‌عنوان شاخه اصلی در نظر می‌گیریم که کد منبع در HEAD آن همیشه حالت آماده تولید را نشان می‌دهد.

ما origin/develop را به‌عنوان شاخه اصلی در نظر می‌گیریم که کد منبع در HEAD آن همیشه تغییرات توسعه‌ای اخیر برای انتشار بعدی را منعکس می‌کند. برخی آن را شاخه “یکپارچه‌سازی” می‌نامند. این همان جایی است که هرگونه ساخت خودکار شبانه از آن ساخته می‌شود.

زمانی که کد منبع در شاخه develop به یک نقطه پایدار رسید و آماده انتشار شد، تمام تغییرات باید به نوعی به master ادغام شده و سپس با یک شماره انتشار برچسب‌گذاری شود. نحوه انجام این کار در ادامه بحث خواهد شد.

بنابراین، هر بار که تغییرات به master ادغام می‌شوند، به تعریف ما این یک انتشار تولید جدید است. ما در این مورد بسیار سخت‌گیر هستیم، به‌طوری‌که از نظر تئوری می‌توانیم از یک اسکریپت hook در Git استفاده کنیم تا هر بار که یک commit روی master انجام می‌شود، به‌طور خودکار نرم‌افزار را ساخته و در سرورهای تولید خود منتشر کنیم.

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

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

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

  • شاخه‌های ویژگی (Feature branches)
  • شاخه‌های انتشار (Release branches)
  • شاخه‌های اصلاح فوری (Hotfix branches)

هر یک از این شاخه‌ها هدف خاصی دارند و به قوانین سخت‌گیرانه‌ای وابسته هستند که مشخص می‌کند کدام شاخه‌ها می‌توانند شاخه مبدأ باشند و کدام شاخه‌ها باید هدف ادغام آنها باشند. در ادامه به بررسی آنها خواهیم پرداخت.

این شاخه‌ها به‌هیچ‌وجه از نظر فنی “خاص” نیستند. انواع شاخه‌ها بر اساس نحوه استفاده ما دسته‌بندی شده‌اند. آنها در واقع شاخه‌های ساده Git هستند.

شاخه‌های ویژگی (Feature Branches)

می‌توانند از شاخه زیر منشعب شوند:

  • develop

باید به شاخه زیر بازگردند:

  • develop

قواعد نام‌گذاری شاخه:

  • هر چیزی به جز master، develop، release-* یا hotfix-*

شاخه‌های ویژگی در gitlow

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

معمولاً شاخه‌های ویژگی فقط در مخازن توسعه‌دهندگان وجود دارند و نه در 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هایی که با هم یک ویژگی را اضافه کرده‌اند را گروه‌بندی می‌کند. مقایسه کنید:

در حالت دوم، از تاریخچه Git نمی‌توان تشخیص داد که کدام commitها با هم یک ویژگی را پیاده‌سازی کرده‌اند—باید تمام پیام‌های ثبت شده را به صورت دستی بخوانید. بازگرداندن یک ویژگی کامل (یعنی یک گروه از commitها)، در حالت دوم کاری بسیار دشوار است، در حالی که اگر از پرچم --no-ff استفاده شده باشد، این کار به راحتی انجام می‌شود.

بله، این کار چند شیء commit (خالی) بیشتر ایجاد می‌کند، اما مزایای آن به‌مراتب بیشتر از هزینه‌اش است

شاخه‌های انتشار (Release Branches)

می‌توانند از شاخه زیر منشعب شوند:

  • develop

باید به شاخه‌های زیر بازگردند:

  • develop و master

قواعد نام‌گذاری شاخه:

  • release-*

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

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

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

ایجاد یک شاخه انتشار

شاخه‌های انتشار از شاخه توسعه (develop) ایجاد می‌شوند. برای مثال، فرض کنید نسخه 1.1.5 نسخه فعلی تولید است و یک انتشار بزرگ در پیش داریم. وضعیت شاخه توسعه برای “انتشار بعدی” آماده است و تصمیم گرفته‌ایم که این نسخه، 1.2 باشد (نه 1.1.6 یا 2.0). بنابراین، یک شاخه جدید ایجاد می‌کنیم و نام آن را بر اساس شماره نسخه جدید می‌گذاریم:

$ 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 یک اسکریپت شل خیالی است که برخی فایل‌ها را در کپی کاری تغییر می‌دهد تا نسخه جدید را منعکس کند. (البته این تغییر می‌تواند به صورت دستی انجام شود. نکته اصلی این است که فایل‌هایی تغییر می‌کنند.) سپس شماره نسخه جدید کامیت (commit) می‌شود.

این شاخه جدید ممکن است مدتی وجود داشته باشد تا زمان انتشار نهایی فرا برسد. در این مدت، رفع اشکالات ممکن است در این شاخه انجام شود (نه در شاخه توسعه). اضافه کردن ویژگی‌های جدید بزرگ در این شاخه به شدت ممنوع است. این ویژگی‌ها باید به شاخه توسعه ادغام شده و بنابراین تا انتشار بزرگ بعدی منتظر بمانند.

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

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

دو مرحله اول در گیت:

$ 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  

اکنون انتشار انجام شده و برای مراجعه آینده برچسب‌گذاری شده است.

نکته: ممکن است بخواهید از فلگ‌های -s یا -u <key> برای امضای رمزنگاری‌شده برچسب خود استفاده کنید.

برای حفظ تغییرات انجام‌شده در شاخه انتشار، باید آن‌ها را به شاخه توسعه بازگردانیم. در گیت:

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

این مرحله ممکن است به تعارض ادغام (merge conflict) منجر شود (احتمالاً، زیرا شماره نسخه را تغییر داده‌ایم). در صورت بروز تعارض، آن را رفع کنید و کامیت کنید.

اکنون کار به طور کامل تمام شده است و شاخه انتشار می‌تواند حذف شود، زیرا دیگر به آن نیازی نداریم:

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

شاخه‌های اصلاح فوری (Hotfix Branches)

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

  • master

باید به شاخه‌های زیر ادغام شوند:

  • develop و master

الگوی نام‌گذاری شاخه:

  • hotfix-*

شاخه‌های اصلاح فوری در gitflow

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

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

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

ایجاد شاخه اصلاح فوری

شاخه‌های اصلاح فوری از شاخه اصلی (master) ایجاد می‌شوند. برای مثال، فرض کنید نسخه 1.2 نسخه فعلی تولید است که به دلیل یک اشکال جدی در حال اجرا بوده و مشکلاتی ایجاد کرده است. اما تغییرات در شاخه توسعه (develop) هنوز ناپایدار هستند. در این حالت، می‌توان یک شاخه اصلاح فوری ایجاد کرد و شروع به رفع مشکل کرد:

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

پایان دادن به یک شاخه اصلاح فوری

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

ابتدا، شاخه اصلی را به‌روزرسانی کرده و نسخه را برچسب‌گذاری کنید:

$ 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  

نکته: ممکن است بخواهید از فلگ‌های -s یا -u <key> برای امضای رمزنگاری‌شده برچسب خود استفاده کنید.

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

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

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

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

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

خلاصه

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

یک نسخه باکیفیت از این تصویر به صورت PDF ارائه شده است. آن را چاپ کرده و برای مراجعه سریع در هر زمان، روی دیوار نصب کنید.

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