Merge در Git چگونه کار میکند؟ Fast-forward، سه طرفه و Commit ادغام
مکانیک git merge: جد مشترک، fast-forward، ادغام سه طرفه و merge commit — چه زمانی تاریخچه خطی میماند و چه زمانی گره ادغام ساخته میشود؛ از Pro Git.
بنیانگذار و مهندس محصول

Merge پاسخ به این مسئله است: دو خط توسعه از یک جد مشترک جدا شدهاند و حالا باید یک تاریخچهٔ واحد قابل استفاده بسازیم. برخلاف کپی دستی فایلها، Git با پیدا کردن ancestor مشترک و ترکیب تغییرات، سعی میکند نتیجه را خودکار بسازد. وقتی نتواند، Conflict میدهد (۲۰۷) — نه اینکه بیصدا یکی را overwrite کند.
مقالهٔ ۰۷۷ معرفی مفهومی داشت؛ اینجا مکانیک: fast-forward در برابر merge commit، ادغام سه طرفه، و پیامد هر کدام برای تاریخچه و PR.

پاسخ کوتاه
git merge تغییرات یک Branch را وارد Branch جاری میکند. اگر Branch جاری جد مستقیم دیگری باشد، اغلب fast-forward رخ میدهد: فقط اشارهگر جلو میرود و Commit جدیدی لازم نیست. اگر هر دو طرف جلو رفته باشند، Git سه طرفه Merge میکند (پایه = جد مشترک، دو نوک) و معمولاً یک merge commit با دو والد میسازد مگر استراتژی/گزینهٔ دیگری بخواهید.
Merge تاریخچه را حفظ میکند که «این دو خط اینجا به هم رسیدند»؛ این آگاهی برای دیباگ گاهی از خطی بودن صرف باارزشتر است.
پیشنیاز ذهنی: جد مشترک
Git از روی گراف Commit نزدیکترین جد مشترک (merge base) را پیدا میکند. تفاوتهای هر سمت نسبت به آن پایه محاسبه میشود. اگر هر دو سمت یک ناحیهٔ یکسان را به شکل ناسازگار عوض کرده باشند، Conflict.
bash
git switch main git merge feature/x git log --oneline --graph --decorate -n 15
حالت ۱: Fast-forward
فرض: main روی C1 است؛ feature از C1 به C2 و C3 رسیده و main جلو نرفته. Merge feature به main میتواند اشارهگر main را به C3 حرکت دهد. تاریخچه خطی میماند؛ merge commit ساخته نمیشود.
- مزیت: ساده و خوانا.
- محدودیت: رد پای «اینجا فیچر ادغام شد» بهصورت Commit جدا نیست.
- کنترل: --no-ff مجبور به ساخت merge commit میکند حتی وقتی fast-forward ممکن است — بعضی تیمها برای ردیابی PR این را میخواهند.
حالت ۲: ادغام سه طرفه و merge commit
اگر main هم Commitهای جدید داشته باشد، fast-forward ممکن نیست. Git تغییرات دو سمت را نسبت به پایه ترکیب میکند. نتیجهٔ موفق یک Commit ادغام با دو والد است: یکی والدین main قبلی، یکی نوک feature.
استراتژی پیشفرض در عمل رایج ort/recursive برای دو سر است. موارد خاص (بیش از دو Branch) کمتر در روزمره لازم است.
جدول: انتخابهای Merge از دید تاریخچه
| روش | تاریخچه | چه زمانی رایج است |
|---|---|---|
| Fast-forward | خطی | Branch کوتاه، main ثابت مانده |
| Merge commit (--no-ff) | گره ادغام واضح | ردیابی PR / فیچر |
| Squash merge (روی هاست) | یک Commit روی هدف | تاریخچهٔ main خلوت |
| Rebase سپس merge FF | خطیشده | قبل از ادغام روی Branch خودتان |
Squash روی GitHub یک Commit واحد از کل PR میسازد؛ از نظر Git محلی، جزئیات متفاوت است ولی هدف محصولی «main تمیز» است. مقایسهٔ عمیقتر با Rebase در ۲۱۱.
Merge محلی در برابر دکمهٔ Merge در Pull Request
روی GitHub سه حالت رایج: merge commit، squash، rebase-and-merge. هر کدام تاریخچهٔ main را differently شکل میدهند. قبل از انتخاب پیشفرض مخزن، با تیم توافق کنید؛ عوض کردن مداوم عادت Review را به هم میزند.
مستندات GitHub About pull request merges این گزینهها را شرح میدهد. مهم: Rebase-and-merge روی هاست با git rebase محلی یکی نیست در جزئیات UX، ولی ایدهٔ خطیسازی مشترک است.
آمادهسازی قبل از Merge
- Working tree تمیز یا Stash؛ Merge روی تغییرات کثیف همپوشان ممکن است رد شود.
- تست و CI سبز روی PR.
- بهروز بودن نسبی با پایه (main) برای کاهش Conflict.
- خواندن diff نهایی؛ Merge خودکار ≠ درست بودن محصول.
اشتباههای رایج
- Merge کردن بدون دیدن --graph و فهمیدن fast-forward یا نه.
- حل سرسری Conflict و Commit ادغام با پیام خالی.
- Merge کردن Branch اشتباه به main بهخاطر switch فراموششده.
- فرض اینکه Merge همیشه باید Rebase باشد یا برعکس — معیار تیم.
خواندن نتیجه با graph
بعد از هر ادغام غیرپیشپاافتاده، یک نگاه به گراف از ده پیام گنگ در چت مفیدتر است:
bash
git log --oneline --graph --decorate --all -n 20
Fast-forward را بهصورت خط صاف میبینید؛ merge commit را بهصورت گره با دو والد. این عادت هزینهٔ «پس الان روی کدام پایه هستیم؟» را کم میکند.
استراتژی و گزینههای کاربردی
- git merge --ff-only: فقط اگر fast-forward ممکن باشد؛ وگرنه شکست — مفید برای سیاست خطی سخت.
- git merge --no-ff: همیشه merge commit — ردیابی فیچر.
- git merge --abort: خروج از ادغام ناتمام.
- پیام Commit ادغام را خالی نگذارید اگر دلیل غیرعادی دارد (مثلاً revert نسبی).
جزئیات استراتژیهای نادر (octopus و مانند آن) برای روزمره لازم نیست؛ اول توپولوژی دو سر را مسلط شوید.
Merge و CI: تعریف «سبز»
Merge موفق در Git فقط یعنی درخت فایل بدون Conflict ساخته شد. سبز بودن تست، امنیت وابستگی، و تأیید محصول لایههای بعدیاند. دکمهٔ Merge در PR باید پشت required checks باشد تا «ادغام متنی» با «قابل انتشار» یکی گرفته نشود.
Merge base را خودتان ببینید
bash
git merge-base main feature/x git log --oneline $(git merge-base main feature/x)..feature/x
دیدن بازهٔ Commitهایی که واقعاً وارد میشوند، قبل از Merge، غافلگیری Review را کم میکند. اگر بازه بیش از حد بزرگ است، قبل از ادغام به main روی شکستن PR فکر کنید.
Revert یک merge commit
برگرداندن merge commit با git revert -m نیاز به دانستن والد اصلی دارد. این عملیات پیشرفتهتر از Revert ساده است؛ برای حادثهٔ Production گاهی لازم میشود. جزئیات undo در ۲۱۳؛ اینجا فقط بدانید merge commit ردپای قابل رجوع میسازد که یکی از دلایل ترجیح --no-ff در بعضی تیمهاست.
وقتی fast-forward نمیخواهید
بعضی تیمها عمداً --no-ff میزنند تا هر فیچر یک گره ادغام داشته باشد و بتوان با git log --merges یا ابزار Release یادداشت ساخت. هزینه: تاریخچه کمی شلوغتر. اگر squash را روی هاست انتخاب کنید، همان ردیابی را به صفحهٔ PR میسپارید نه به گراف Commit.
انتخاب بین اینها باید مکتوب باشد؛ تغییر هفتهبههفته فقط ابزار را عوض میکند نه کیفیت را.
Merge از دید Incident
در حادثه، سؤال «کدام Merge این را آورد؟» با merge commit یا با لینک PR پاسخ داده میشود. اگر همه چیز squash شده و پیامها مبهم باشند، پاسخ طولانیتر میشود. بنابراین حتی وقتی squash میکنید، پیام نهایی و لینک PR را جدی بگیرید — جزئی از مکانیک انسانی Merge است.
سوالات متداول
آیا merge commit زشت است؟
خیر؛ اطلاعات توپولوژی میدهد. اگر تیم تاریخچهٔ کاملاً خطی میخواهد، سیاست FF-only یا rebase را صریح کنید.
Abort چگونه است؟
اگر Merge ناتمام است: git merge --abort (در صورت مجاز بودن وضعیت). بعد از Commit ادغام، برگشت معمولاً با revert merge یا reset محتاطانه است — روی مشترکات خطرناک.
Merge از Remote؟
ابتدا Fetch، سپس merge origin/main (یا Pull). Pull فقط میانبر است؛ فهم جداگانهٔ Fetch و Merge کنترل بیشتری میدهد.
خلاصه
Merge با جد مشترک کار میکند: یا اشارهگر را fast-forward میکند یا نتیجهٔ سه طرفه را در merge commit مینشاند. انتخاب FF، --no-ff، squash یا مسیر rebase به اولویت خوانایی، ردیابی PR و عادت تیم بستگی دارد — مطلق «بهترین» وجود ندارد.
وقتی ترکیب خودکار شکست بخورد، سراغ ۲۰۷ بروید؛ برای جایگزینی خطیسازی سراغ ۲۱۰ و ۲۱۱.
منابع و مراجع
- Pro Git — Basic Merging — https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging
- git-merge documentation — https://git-scm.com/docs/git-merge
- GitHub Docs — About pull request merges — https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges
- Pro Git — Advanced Merging — https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging
- git-merge-base documentation — https://git-scm.com/docs/git-merge-base
نویسنده
سهیل ابراهیمپور بنیانگذار FutureForge است. روی طراحی محصول، معماری و استقرار نرمافزار سفارشی کار میکند.
یادداشتهای مرتبط
اگر در انتخاب معماری یا مسیر توسعه مطمئن نیستید، میتوانید درباره پروژه صحبت کنیم.
مسئله و محدودیت را بنویسید. اگر تطابق داشته باشیم، برای گفتگو هماهنگ میکنیم.




