ویرگول
ورودثبت نام
رضا کارگر
رضا کارگر
رضا کارگر
رضا کارگر
خواندن ۶ دقیقه·۴ روز پیش

از git push --force نترس! فقط بلد باش کی و چطور ازش استفاده کنی

(اگر فکر میکنی محتوای ویدیویی برات کارساز تره می‌تونی ویدیو این مطلب رو مشاهده کنی)

چند روز داشتم با یکی از همکارام روی یه کار کوچیک کار می‌کردم.
تغییراتم رو کامیت کردم و مثل همیشه پوش کردم.
چند دقیقه بعد یادم افتاد یه تیکه از کار جا مونده!

گفتم خب… مشکلی نیست، سریع یه rebase می‌کنم و اون تغییر رو به همون کامیت قبلی اضافه می‌کنم.
همه‌چیز خوب پیش رفت… تا رسیدم به لحظه‌ی حساس:

می‌خواستم بزنم:

git push --force

همین که انگشتم رفت روی اینتر، دیدم همکارم با چشمای گشاد داره نگاهم می‌کنه؛
یه جوری که انگار تو دلش داد می‌زد:

«نکـننننن! این خطرناکه! نزن!»

منم خندیدم و براش توضیح دادم که:

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

حتی Git یه نسخه‌ی امن‌تر هم داره که خیلی‌ها اسمش رو نشنیدن:
--force-with-lease

بیاین با یه نگاه ساده ببینیم واقعاً داستان چیه…


🎯 اصلاً چرا Force Push وجود داره؟

وقتی rebase می‌کنی یا تاریخچه رو دست‌کاری می‌کنی، Git انتظار داره چیزی که پوش می‌کنی ادامه‌ی حالت قبلی باشه.
اما rebase این سازگاری رو از بین می‌بره.

برای همین مجبور می‌شی Force بزنی

قبل اینکه وارد بررسی مثالی بشیم به تصویر زیر نگاه کنید تا با اشکال استفاده شده در تصاویر آشنا بشید:

در تصویر بالا، هرکدام از دایره های سبز رنگ نشان دهنده یک کامیت در مخزن لوکال ما هستند.

من یک سری اشاره گر هم در نظر گرفته ام که هرکدوم را در ادامه توضیح میدم، اشاره گر های HEAD و main نمایانگر این هستند که در مخزن لوکال ما برنچ main به کجا اشاره می کند، همان طور که میدانید ما یک نسخه از مخزن‌مون در یک سرور ماند github یا gitlab ممکن است داشته باشیم، اشاره گر origin/main به آخرین کامیتی که در روی سرور هست اشاره میکند و اشاره گر ref/origin/main درواقع متناظر آن اشاره گر در محیط لوکال هست و نشان‌دهنده این هست که از نظر مخزن لوکال، آخرین وضعیت مخزن بر روی سرور چگونه هست، ممکن است این دو اشاره گر به یک کامیت واحد اشاره نکننده چرا که مثلا در روی سرور تغییراتی اعمال شده است که مخزن لوکال هنوز از آنها مطلع نشده است.

پس اگر کامیتی جدیدی به اسم E ثبت کنیم وضعیت مخزن لوکال به شکل زیر در خواهد آمد:

و پس از push کردن تغییرات به سرور وضعیت مخزن لوکال به شکل زیر در خواهد آمد:

فرض کنیم تغییری توسط شخص دیگری روی سرور اعمال میشود:

و ما از این تغییرات هنوز مطمعل نیستم و تصمیم میگیریم که کامیت E را تغییر دهیم و آن را Rebase کنیم، پس از این کار کامیت E' ایجاد می‌شود:

حال اگر بخواهیم تغییراتمان را به سرور منتقل کنیم (git push) گیت اجازه این کار را به ما نمی‌دهد چرا که تغییراتی روی سرور هست که ما از آنها مطلع نیستیم،

می توانیم git fetch بنزیم تا آخرین تغییرات را داشته باشیم، پس از git fetch:

با این حال اگر git push بکنیم باز هم خطا خواهیم گرفت چرا که تاریخچه ای که در حال push کردن آن هستیم با تاریخچه ای که روی سرور وجود دارد متفاوت است پس git اجازه این کار را به ما نمیدهد و یا باید rebase بکنیم یا force push!

حال اگر git push -f بزنیم چه اتفافی خواهد اتفاد؟

طبق تصویر بالا، پس از force push ما تغییرات E و F را از روی سرور حذف می‌کنیم و دیگر این کامیت هارا در تاریخچه مان نخواهیم داشت!

این کار بسیار خطرناک است، مگر در شرایطی که میخواهیم تاریخچه ی سرور با لوکال ما یکی شود حتی اگر کامیت هایی را از دست بدیهم، که خیلی کم پیش میاد.

آیا راهی وجود دارد که اگر git force push باعث از دست دادن کامیت هایی روی سرور شود از آن مطلع شویم؟

پاسخ گیت به این سوال بله هست!

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


🔍‌ Force-With-Lease دقیقاً چیکار می‌کنه؟

در مستندات رسمی Git (بخش مربوط به git push)، گیت یک فلگ امن‌تر معرفی کرده:

git push --force-with-lease

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

به زبان ساده:
گیت می‌پرسد:

«تو می‌گی آخرین کامیت origin/main فلان است…
منم چک می‌کنم ببینم همین‌طور است یا نه.
اگر درست گفتی → اجازه Push می‌دم
اگر اشتباه گفتی → قطعاً یه نفر قبل تو تغییری داده، پس Push رو بلوکه می‌کنم.»

برای همین این روش از --force به‌مراتب امن‌تر است.


🔧 تنظیم Force-With-Lease به‌عنوان Force پیش‌فرض

اگه نمی‌خوای هر بار تایپش کنی، می‌تونی این خط را یک بار تنظیم کنی:

git config --global push.forceWithLease true

از این به بعد هر بار بزنی:

git push -f

در واقع گیت معادلش را با --force-with-lease اجرا می‌کند.


🤔 اما آیا همه‌چیز حل شد؟

اوهوممم… نه کاملاً.

گفتیم که --force-with-lease بررسی می‌کند آیا ما از آخرین وضعیت سرور خبر داریم یا نه.

اما اگر IDE شما یا یک cron job هر چند دقیقه یک‌بار خودکار git fetch بزند چی؟
در این صورت گیت همیشه فکر می‌کند:

«آره تو حالت سرور رو می‌دونی، برو Force کن.»

پس ممکنه ناخواسته کامیت‌های دیگران حذف شوند، چون گیت تصور می‌کنه «مطلع هستیم».

پس باید یک راه امن‌تر داشته باشیم.


🛡 راه‌حل: Force-If-Includes

گیت یک فلگ خیلی مطمئن‌تر دارد:

git push --force-if-includes

این روش فقط در صورتی اجازه Force می‌دهد که تاریخچه‌ی جدید ما شامل تاریخچه‌ی روی سرور باشد—یعنی مطمئن شود کامیت‌های روی سرور گم نمی‌شوند.

به زبان ساده:

  • اگر با Force شما کامیتی از سرور حذف می‌شود → گیت اجازه نمی‌دهد

  • اگر Force فقط قرار است تاریخچه‌ای جدید جایگزین کند بدون حذف‌شدن کامیت‌های دیگران → اجازه می‌دهد

این دقیقاً چیزی است که اکثر تیم‌ها واقعاً می‌خواهند.


🔧 تنظیم Force-If-Includes به‌عنوان پیش‌فرض

git config --global push.forceIfIncludes true

بعد از این، هر بار:

git push -f

در واقع «Force If Includes» اجرا می‌شود.


😀 ولی یک نکته‌ی مهم!

گاهی عمداً می‌خوای Force Push ساده (یعنی همون --force کلاسیک) بزنی—for example در یک ریپوی شخصی یا یک برنچ feature که فقط خودت روش کار می‌کنی.

اما چون Force پیش‌فرض را عوض کردیم، حتی اگر بزنی:

git push --force

باز هم Force-With-Lease یا Force-If-Includes اجرا می‌شود و Force واقعی انجام نمی‌شود!

راه‌حل:

git push --no-force

این هنگام استفاده از --force، سویچ‌های ایمنی را خاموش می‌کند و Force «واقعی» را اجرا می‌کند.


🧠 راه‌حل رسمی Git برای استفاده امن از Force-With-Lease در سیستم‌هایی که Fetch خودکار دارند

در مستندات گیت یک راه دیگر هم معرفی شده:
ساختن یک ریموت جداگانه مخصوص Force Push:

git remote add origin-push $(git config remote.origin.url) git fetch origin-push

فقط push روی آن را انجام می‌دهید:

git push --force-with-lease push-force main

چطور کار می‌کنه؟

  • ریموت اصلی (origin) همیشه fetch خودکار دارد (IDE، cron و غیره)

  • اما ریموت جدید (push-force) هیچ وقت fetch نمی‌شود

  • پس Git همیشه تاریخچه‌ی آخرین Push شما را معیار امن بودن Force-With-Lease قرار می‌دهد

این باعث می‌شود:

  • هم از Force-With-Lease استفاده کنید

  • هم fetch خودکار باعث تخریب safety آن نشود

  • و همچنان بدون حذف اتفاقی کامیت‌های دیگران کار کنید


✨ جمع‌بندی

Force Push چیزی نیست که ازش بترسیم؛ چیزی‌ست که باید درست بشناسیم.
Git گزینه‌های مختلفی مثل --force-with-lease و --force-if-includes در اختیارت می‌گذاره تا مطمئن باشی زمانی که Force می‌زنی، آگاهانه و ایمن رفتار می‌کنی.

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


اگه سؤال یا تجربه‌ای در این زمینه داری خوشحال می‌شم توی کامنت‌ها برام بنویسی.
و اگر از این نوع محتوا خوشت میاد، حتماً من رو دنبال کن تا آموزش‌های و نوشته‌های بعدی رو از دست ندی 🙌✨

گیتgit
۲
۰
رضا کارگر
رضا کارگر
شاید از این پست‌ها خوشتان بیاید