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

شاخه بندی در گیت برای تیم دو تا پنج نفره خیلی ساده خلاصه می شه: یه شاخه اصلی (main) که همیشه سالمه، برای هر کار یه شاخه کوتاه عمر، یه Pull Request کوچک که یک نفر نگاهش می کنه، و بعدش ادغام و پاک کردن شاخه. بیشتر دردسرهای ادغام از شاخه های چند هفته ای و PR های غول پیکر میاد، نه از این که روال پیچیده نداشتیم. توی این راهنما قدم به قدم، از تنظیم مخزن تا حل تداخل و اصلاح اضطراری، یه روال یک صفحه ای می سازیم.
فرض کن یه فروشگاه اینترنتی داری و سه نفر روی کدش کار می کنن. یکی صفحه پرداخت رو عوض می کنه، یکی فیلتر محصولات رو، یکی هم به روزرسانی یه کتابخونه رو انجام می ده. اگه هر کس سه هفته توی شاخه خودش بمونه و آخر ماه همه چیز رو یه جا ادغام کنن، یه روز کامل از وقت تیم صرف حل تداخل می شه و وسطش هم یه باگ تازه می سازن. اون یه روز، یعنی هزینه واقعی حقوق سه نفر. روال سبک، دقیقاً برای جلوگیری از همین خرجه.
شاخه بندی در گیت: سه قاعده که کل روال روشون ایستاده
main رو مثل سالن یه رستوران در نظر بگیر. مشتری فقط غذای آماده رو می بینه. هر آشپز روی میز خودش (یه شاخه) کار می کنه و غذا رو وقتی به سالن می فرسته که چشیده و تأیید شده باشه. هیچ کس وسط پخت، قابلمه رو روی میز مشتری نمی ذاره.
با این تشبیه، سه قاعده دارم:
قاعده اول: main همیشه قابل استقراره. یعنی هر لحظه که کسی از main نسخه بگیره و روی سرور بذاره، سایت باید درست کار کنه. اگه استقرار رو خودکار کرده باشی، این قاعده حتی مهم تر می شه؛ بعداً بهش برمی گردیم.
قاعده دوم: هر کار یه شاخه داره، نه هر آدم یه شاخه همیشگی. شاخه ای به اسم ali-dev که ماه ها زنده می مونه، کم کم یه نسخه موازی از پروژه می شه. شاخه باید برای یه کار مشخص ساخته بشه و با تموم شدن اون کار بمیره.
قاعده سوم: عمر شاخه به ساعت یا چند روز حساب می شه، نه هفته. دلیلش ساده است: هر روزی که شاخه تو از main جدا می مونه، بقیه تیم چند تغییر دیگه توی main می ریزن. تداخل (conflict) از همین فاصله می آد. شاخه کوتاه، فاصله کم و تداخل کوچیک و قابل فهم.
چرا Git Flow کامل برای تیم کوچک زیادیه
Git Flow شاخه های develop، release و hotfix رو کنار هم می ذاره. این ساختار برای تیمی معنا داره که چند نسخه رو هم زمان نگه می داره و مثلاً باید به نسخه ۲ باگ فیکس بده، در حالی که نسخه ۳ رو توسعه می ده. اغلب تیم های کوچک که یه سایت یا یه اپلیکیشن با یه نسخه زنده دارن، این نیاز رو ندارن. شاخه اضافه یعنی ادغام اضافه، و ادغام اضافه یعنی جای بیشتر برای اشتباه.
استثنا: اگه اپلیکیشن موبایل داری که کاربرها نسخه های قدیمی رو مدت ها نگه می دارن و باید روی چند نسخه هم زمان باگ فیکس بدی، یه شاخه release ساده می تونه به دردت بخوره. ولی اول ببین واقعاً به این نیاز رسیدی یا فقط شنیدی که «حرفه ای ها این طور کار می کنن».
آماده سازی مخزن: دو ربع ساعت که سال ها صرفه جویی می کنه
این بخش رو فقط یه بار انجام می دی و بعدش خود ابزار جلوی خیلی از اشتباه ها رو می گیره.
محافظت از main
اولین کار، بستن در مستقیمه. توی تنظیمات مخزن (توی GitHub بخش Branch protection rules یا Rulesets، توی GitLab بخش Protected branches، توی Gitea هم بخش Branches با گزینه Protected) این ها رو فعال کن:
- push مستقیم به
mainممنوع بشه و فقط از طریق Pull Request (در GitLab اسمش Merge Request) بشه تغییر داد. - حداقل یک تأیید از یه نفر دیگه لازم باشه.
- اگه تست خودکار داری، سبز بودنش شرط ادغام باشه.
اسم گزینه ها بین سرویس ها و حتی بین نسخه ها کمی فرق می کنه، پس دنبال مفهومش بگرد نه عین عبارت. اگه تیمت به خاطر شرایط دسترسی به سرویس های خارجی، گیت رو روی سرور خودش اجرا می کنه (مثلاً با Gitea یا GitLab)، همین قابلیت ها اونجا هم هست.
یه نکته صریح: خودت هم که مدیر پروژه ای، این قانون رو برای خودت هم فعال کن. بیشترین push مستقیم خراب کن ها همون «فقط یه خط بود» های مدیرها هستن.
قرارداد نام گذاری شاخه
یه الگوی ساده کافیه:
feature/shopping-cartبرای قابلیت تازهfix/payment-errorبرای رفع باگchore/update-libraryبرای کارهای فنی که ظاهر محصول رو عوض نمی کنن
اسم شاخه رو انگلیسی و بدون فاصله بنویس. با اسم فارسی، بعضی ابزارها، اسکریپت ها و سرویس های CI توی نمایش یا آدرس دهی دردسر درست می کنن. اسم شاخه باید بگه کار چیه، نه کار کیه. fix/payment-error برای همه روشنه، reza-test2 فقط برای رضا.
فایل های کنار مخزن
یه .gitignore درست داشته باش تا فایل های محیطی، رمزها، پوشه node_modules یا فایل های ساخته شده وارد مخزن نشن. فایل .env رو هیچ وقت کامیت نکن، چون بعد از ورودش به تاریخچه، پاک کردنش دردسرساز می شه.
توی README پنج خط بنویس که همین روال رو بگه: از main شاخه بساز، کوچیک نگهش دار، PR بزن، یه نفر تأیید کنه، شاخه رو پاک کن. دفترچه چند صفحه ای کسی نمی خونه، پنج خط رو می خونن.
برای پیام کامیت هم استاندارد سنگین لازم نیست. یه جمله روشن بسه: «اعتبارسنجی شماره موبایل در فرم ثبت نام اضافه شد» یا «Fix phone validation in signup form». فقط یکدست باش و پیام هایی مثل «تغییرات» و «درست شد» ننویس. شش ماه بعد که دنبال این می گردی چرا یه خط عوض شده، همین پیام ها نجاتت می دن.
چرخه روزانه: از شاخه تازه تا push
گام به گام با دستورها
هر کار جدید با این ترتیب شروع می شه:
git switch main
git pull
git switch -c feature/shopping-cart
اول main رو به روز می کنی، بعد از روی اون شاخه می سازی. اگه این مرحله رو رد کنی و از یه main قدیمی شروع کنی، از همون لحظه اول با تداخل شروع کردی.
بعد کار می کنی و کامیت های کوچیک می زنی. کامیت کوچیک یعنی یه تغییر قابل توضیح، نه ۴۰ فایل با هم:
git add .
git commit -m "Add cart summary component"
git push -u origin feature/shopping-cart
همون روز push کن، حتی اگه کار تموم نشده. دو دلیل داره. اول، پشتیبانه: اگه لپ تاپت خراب بشه یا دسترسی به اینترنت بین المللی وسط روز قطع و وصل بشه، کار یه روزت فقط روی یه دیسک نمونده. دوم، دیده شدنه: همکارت می بینه تو روی چی کار می کنی و اگه همون فایل رو دست زده، زودتر هماهنگ می کنین.
هر چند ساعت یه بار با main همگام شو
اگه شاخه ات چند روز عمر داره، هر روز (یا هر چند ساعت اگه تیم شلوغه) تغییرات main رو بیار توی شاخه ات:
git fetch origin
git merge origin/main
یا اگه تیمت با rebase راحته:
git fetch origin
git rebase origin/main
هر دو درستن. مهم اینه که کل تیم یکی رو انتخاب کنه. ترکیب سلیقه ای باعث می شه تاریخچه شلوغ بشه و هر کس چیزی رو که انتظار نداره ببینه. اگه تازه شروع کردی و کسی rebase رو خوب بلد نیست، merge امن تره، چون تاریخچه رو بازنویسی نمی کنه. اگه rebase می کنی و شاخه ات قبلاً push شده، بعدش باید با git push --force-with-lease بفرستی، که از --force ساده امن تره چون اگه کس دیگه ای روی همون شاخه چیزی ریخته باشه، جلوت رو می گیره. و یادت باشه: rebase فقط روی شاخه ای که خودت تنها روش کار می کنی.
کار نیمه تموم و کارهای بزرگ
گاهی کار واقعاً بزرگه، مثلاً بازنویسی کامل فرایند پرداخت. راه حل شاخه سه هفته ای نیست. کار رو به تکه های مستقل بشکن که هر کدوم جدا ادغام بشه و main رو نشکنه. مثلاً اول جدول های جدید پایگاه داده، بعد کد پشت صحنه، بعد رابط کاربری.
اگه تکه ای آماده ادغامه ولی نباید به کاربر نشون داده بشه، یه پرچم ویژگی (feature flag) ساده بذار. حتی یه متغیر تنظیمات مثل ENABLE_NEW_CHECKOUT=false کافیه. کد داخل main می ره، ولی تا وقتی پرچم خاموشه، کاربر چیزی نمی بینه. این روش خیلی بهتر از یه شاخه عظیمه که هیچ کس جرأت نمی کنه بهش دست بزنه.

Pull Request کوچیک، بازبینی سریع
یه PR خوب چی داره
PR خوب یه کار رو انجام می ده و راحت خونده می شه. یه قاعده تقریبی که کمک می کنه: اگه بازبین نتونه توی ده بیست دقیقه PR رو بخونه، احتمالاً بزرگه. عدد دقیقی نیست، ولی جهت رو نشون می ده.
توی توضیح PR سه چیز بنویس:
- چی عوض شد؟ یه یا دو جمله.
- چرا؟ مثلاً «کاربرها بعد از خطا توی پرداخت، سبد رو از دست می دادن».
- چطور تست کنم؟ مثلاً «با کد تخفیف TEST یه سفارش بساز و ببین جمع کل درسته».
اگه تغییر ظاهری داره، یه اسکرین شات بذار. بازبین نباید برای فهمیدن این که تغییر چیه، کد رو روی سیستم خودش اجرا کنه.
بازبینی: دقیقاً دنبال چی بگردیم
توی تیم کوچیک، بازبینی دقیق و بی رحمانه همه چیز لازم نیست. روی سه سؤال تمرکز کن:
- آیا این تغییر کار قبلی رو می شکنه؟
- آیا چیز حساسی (رمز، کلید API، آدرس داخلی) توش هست؟
- آیا نفر بعدی می تونه کد رو بفهمه؟
سبک نوشتن و جای فاصله ها رو به ابزار خودکار (linter) بسپار، نه به بحث انسانی. توی تیم های کوچیک، بحث سر سلیقه بیشتر از باگ انرژی می خوره.
برای تغییرات پرریسک، مثل منطق پرداخت، تنظیمات سرور یا دسترسی ها، بازبینی رو جدی تر بگیر. یه خط اشتباه توی قانون های فایروال یا تنظیمات درگاه پرداخت می تونه از یه باگ ظاهری خیلی گران تر تموم بشه. و بازبین نباید دیر جواب بده: قرار بذارین PR ها توی همون روز دیده بشن، وگرنه شاخه ها دوباره بلند می شن.
ادغام: merge commit، squash یا rebase؟
وقتی PR تأیید شد، سه راه برای ادغام هست:
| روش | چه می کنه | چه وقت مناسبه |
|---|---|---|
| Merge commit | همه کامیت ها رو با یه کامیت ادغام نگه می داره | وقتی تاریخچه دقیق شاخه برات مهمه |
| Squash and merge | همه کامیت های PR رو یکی می کنه | تیم کوچیک با PR های کوچیک، تاریخچه تمیز |
| Rebase and merge | کامیت ها رو بدون کامیت ادغام روی main می چینه | تیمی که با rebase راحته و خطی بودن تاریخچه می خواد |
پیشنهاد من برای شروع، Squash and merge هست. هر PR می شه یه کامیت روی main با یه پیام روشن. وقتی بعداً بخوای تغییری رو برگردونی، فقط یه کامیت رو برمی گردونی. معایبش هم هست: ریزتاریخچه کامیت ها از دست می ره، پس اگه توی یه PR چند کار جدا کردی، تاریخچه تمیز نمی مونه. برای همین PR کوچیک این قدر مهمه.
بعد از ادغام، همون لحظه شاخه رو پاک کن. گیت سرویس ها گزینه «پاک کردن خودکار شاخه بعد از ادغام» دارن؛ روشنش کن. روی سیستم خودت هم هر از گاهی این رو بزن:
git fetch --prune
git branch -d feature/shopping-cart
شاخه های مرده یه جور بی نظمی ان که کم کم همه رو گیج می کنه: کدوم زنده است؟ کدوم رو می شه پاک کرد؟
استقرار از روی main: چرا قاعده اول مهمه
اگه استقرار دستی انجام می دی (فایل ها رو با FTP می فرستی یا روی سرور git pull می زنی)، قاعده «main همیشه سالم» فقط یه توصیه است. ولی اگه استقرار خودکار باشه، قاعده جدی می شه: هر چیزی که وارد main بشه، می ره روی سایت. اگه این مسیر رو می خوای بسازی، مقاله استقرار خودکار سایت با CI/CD؛ از push تا سرور بدون دست کاری رو ببین.
یه نکته برای کسب و کارهای ایرانی: ساعت های اوج ترافیک رو بشناس. اگه فروشگاهت شب ها یا موقع کمپین های فروش بیشترین بازدید رو داره، ادغام و استقرار رو توی اون ساعت ها انجام نده، حتی اگه همه چیز سبزه. استقرار قبل از اوج، یعنی صبح یا اوایل بعدازظهر، بهت فرصت می ده اگه چیزی خراب شد، قبل از شلوغی درستش کنی.
تداخل که پیش اومد: خونسرد باش و دو طرف رو بخون
تداخل خطا نیست؛ فقط گیت می گه «دو نفر یه خط رو عوض کردن و من نمی دونم کدوم درسته». وقتی merge یا rebase تداخل داشته باشه، فایل های درگیر رو با git status می بینی. داخلشون چیزی شبیه این هست:
<<<<<<< HEAD
$total = $price * $quantity;
=======
$total = ($price - $discount) * $quantity;
>>>>>>> origin/main
بالای ======= تغییر خودته و پایینش تغییر main. سه قدم:
- هر دو طرف رو بخون. اینجا تغییر
mainتخفیف رو اضافه کرده. هیچ کدوم تنها درست نیست. - نتیجه نهایی رو دستی بنویس و خط های
<<<<<<<،=======و>>>>>>>رو پاک کن. - تست کن و ادامه بده:
git add path/to/file
git merge --continue
اگه از rebase استفاده می کنی، آخرش git rebase --continue می زنی. اگه وسط کار گیج شدی و می خوای از اول شروع کنی، git merge --abort یا git rebase --abort تو رو به حالت قبل برمی گردونه. این دستور شبکه ایمنیه، بدون ترس ازش استفاده کن.
اشتباه رایج: انتخاب کورکورانه «نسخه من» یا «نسخه اون». این کار تغییر یه همکار رو بی صدا پاک می کنه و ماه ها بعد پیداش می شه. اگه دلیل تغییر طرف مقابل رو نمی فهمی، از خودش بپرس. دو دقیقه حرف زدن از دو ساعت دنبال باگ گشتن ارزون تره.
یه ترفند عملی هم هست: اگه می دونی دو نفر روی یه فایل کار می کنن، قبل از شروع یه جمله هماهنگ کنید. حتی بهترین ابزار هم جای حرف زدن رو نمی گیره.
اصلاح اضطراری: وقتی سایت زنده خرابه
فرض کن بعد از استقرار، خطایی توی صفحه پرداخت پیدا شد. اول از همه: ترسی که باعث می شه مستقیم روی main بزنی، همون چیزیه که بدتر می کنه. روال همونه، فقط سریع تر:
- از
mainیه شاخهfix/بساز. - کوچیک ترین تغییر ممکن رو بده که مشکل رو حل کنه. این وقت، وقت تمیزکاری نیست.
- یه PR باز کن، یه نفر سریع نگاه کنه، ادغام کن.
- استقرار بده و درست شدن رو روی سایت واقعی تأیید کن.
اگه مشکل رو نمی شه سریع فهمید، گزینه امن تر برگردوندنه (revert):
git revert <hash-of-the-commit>
این دستور یه کامیت تازه می سازه که اثر کامیت خراب رو خنثی می کنه، بدون این که تاریخچه رو پاک کنه. اگه ادغام با merge commit بوده، باید git revert -m 1 <hash> بزنی. با squash، کار ساده تره چون فقط یه کامیت معمولیه.
یه هشدار: revert کد رو برمی گردونه، نه داده رو. اگه تغییر خراب، جدول ها یا رکوردهای پایگاه داده رو هم عوض کرده، برگردوندن کد کافی نیست. برای همین نسخه پشتیبان مرتب بخش جدایی از ایمنی کاره؛ توی بکاپ خودکار سرور مجازی؛ چه چیزی، کجا و هر چند وقت یک بار؟ دقیق تر گفتیم چی رو کجا و هر چند وقت بگیری.
بعد از رفع مشکل، یه کار کوچیک ولی مهم: دو خط بنویس که چی شد و چرا رد شد. این یادداشت اگه بعداً تکرار بشه، وقت زیادی صرفه جویی می کنه.

اشتباه های رایجی که می بینیم
- شاخه های سه هفته ای. اگه کار بزرگ شده، بشکنش یا پرچم ویژگی بذار.
- PR های ۴۰ فایلی. هیچ کس اون ها رو واقعاً نمی خونه؛ بازبینی می شه یه «تأیید» بی معنی.
- تأیید بدون خوندن. اگه فقط دکمه رو می زنین، بهتره قانون تأیید رو بردارین و صادقانه بگین بازبینی نداریم. این از ظاهرسازی بهتره.
- ادغام وقتی تست قرمزه. «بعداً درستش می کنیم» یعنی
mainسالم نیست و قاعده اول شکسته. - تغییر تاریخچه شاخه مشترک.
rebaseوpush --forceروی شاخه ای که چند نفر روش کار می کنن، کار همکارها رو خراب می کنه. - ذخیره رمز و کلید توی مخزن. حتی اگه مخزن خصوصیه. بعداً که کد رو به پیمانکار یا تیم دیگه می دی، همه چیز با اون می ره.
- روال برای همه یکسان نیست. اگه یه نفر استثنا باشه، قانون وجود نداره.
سوالات متداول
برای تیم دو نفره هم باید Pull Request بزنیم؟
بله، به شرط این که سبک نگهش داری. حتی دو نفره، PR یه نگاه دوم می ده و یه رد مکتوب از اون که چی عوض شده. اگه تیم خیلی کوچیکه، می تونی قانون «حداقل یک تأیید» رو نگه داری و فقط بازبینی رو سریع انجام بدی.
اگه تنها کار می کنم، شاخه بندی لازمه؟
لازم نیست، ولی مفیده. شاخه برای کار تنهایی هم به تو اجازه می ده یه آزمایش رو بدون ریسک امتحان کنی و اگه خوب نشد، دور بندازی. حداقل main رو سالم نگه دار و برای کارهای بزرگ شاخه بساز.
بین merge و rebase کدوم رو انتخاب کنم؟
اگه تیم با rebase راحت نیست، merge امن تره چون تاریخچه رو بازنویسی نمی کنه. اگه همه با rebase آشنان و تاریخچه خطی می خوان، اون هم اشکالی نداره. مهم ترین نکته اینه که کل تیم یه روش رو انتخاب کنه و روی شاخه های مشترک rebase نزنه.
یه شاخه چند روز می تونه عمر کنه؟
قانون دقیق نیست، ولی اگه شاخه ات بیشتر از چند روز کاری باز مونده، علامت خطره. یا کار رو به تکه های کوچیک تر بشکن، یا هر روز با main همگامش کن، یا برای تکه های ناتموم از پرچم ویژگی استفاده کن.
اگه اشتباهی توی main ادغام کردیم چی کار کنیم؟
سریع ترین و امن ترین راه git revert هست که اثر اون کامیت رو خنثی می کنه بدون پاک کردن تاریخچه. بعد از اون، مشکل رو توی یه شاخه جدا درست کن و دوباره PR بزن. اگه تغییر روی داده هم اثر گذاشته، به نسخه پشتیبان دیتابیس هم نیاز داری.
جمع بندی: روالی که نیازی به بحث ندارد
اگه بخوای همه چیز رو توی چند خط ببری روی دیوار: main همیشه سالمه، هر کار یه شاخه کوتاه داره، PR کوچیکه، یک نفر نگاهش می کنه، با squash ادغام می شه و شاخه همون لحظه پاک می شه. وقتی چیزی خراب شد، به جای عجله، از همین مسیر کوتاه تر عبور کن.
این روال هزینه ای نداره، ولی چیزی رو که واقعاً گرونه، یعنی وقت تیم و اعتماد مشتری، حفظ می کنه. اگه می خوای بعدش خود استقرار هم بدون دست کاری انجام بشه، می تونی از خدمات DevOps ما هم کمک بگیری یا فقط مقاله استقرار خودکار رو بخونی و خودت پیش بری. شروعش هم ساده است: امروز محافظت از main رو روشن کن و یه PR کوچیک بزن.


