برخی اوقات به خاطرِ combine نادرستِ تغییرات در فرایندِ merge برنامه‌مون با مشکل مواجه می‌شه. برای رفع این مشکل مجبوریم که اون mergeای که انجام دادیم رو undo کنیم و از نو عملِ merge رو انجام بدیم.

فرض کنیم merge commitای با آی‌دیِ f634b2a رو داریم و می‌خواهیم اون رو undo کنیم. یک گزینه اینه که این کامیت رو حذف کنیم که انگار از قبل هم اونجا نبوده. در این حالت در واقع داریم historyمون رو rewrite (بازنویسی) می‌کنیم.

بازنویسی تاریخچه زمانی خوبه که این تاریخچه بصورتِ محلی در repository ماست. ولی اگه این کامیت رو با دیگر افرادِ تیم به اشتراک گذاشته باشیم و یا کامیت‌هامون رو در remote repository قرار داده باشیم، در این شرایط نباید historyمون رو rewrite کنیم. در این حالت باید بجایِ حذف کامیت اون رو revert (برگرداندن به تغییراتِ قبلی) کنیم.

در این بخش هر دو راه‌حل رو یاد می‌گیریم.

حذفِ آخرین کامیت

در زیر تصویری از تاریخچه‌مون رو داریم. در ابتدا، هر دویِ اشاره‌گرهایِ Master و Head به آخرین کامیت که همون merged commit هست اشاره دارن. می‌خواهیم از دستورِ reset استفاده کنیم تا این دو به کامیتی که قبل از merge بود اشاره کنن. با این کار merge commitای که هیچ اشاره‌گری بهش اشاره نداره، برای گیت بعنوان کامیتِ ‌بدرنخوری (garbage) خواهد بود و هر از گاهی، گیت اقدام به حذفِ چنین کامیت‌هایی از repository می‌کنه.

Undoing-a-Faulty-Merge-1

حالا برای حذفِ کامیتِ آخر از دستورِ زیر استفاده می‌کنیم.

git reset --hard HEAD~1

هارد ریست

منظور از HEAD~1 یعنی یک کامیت قبل از آخرین کامیت هستش.

منظور از آپشنِ hard— چیه؟ وقتی که Head Pointer رو reset می‌کنیم سه تا آپشن برای این کار داریم: soft، mixed و hard.

در تصویر زیر Working Dir، Staging Area و Last Snapshot رو داریم.

وقتی که head رو با گزینه‌ی soft ریست می‌کنیم گیت اشاره‌گر head رو مجبور به اشاره‌ به کامیتِ دیگری می‌کنه اما Working Dir و Staging Areaمون بدونِ هیچ تغییری باقی می‌مونن.

اگه از آپشنِ پیشفرضِ mixed استفاده کنیم (چون پیشفرض هستش می‌تونیم اون رو ننویسیم) گیت اون snapshot رو می‌گیره و در staging area هم قرار می‌ده. اگه local changeهایی رو در working dir داشته باشیم اون‌‌ها هنونجوری دست‌نخورده باقی خواهند ماند.

اگه از آپشنِ hard استفاده کنیم گیت اون snapshot رو در working dir هم اعمال خواهد کرد. و در نتیجه هر سه محیط یکسان خواهند بود. و این همون شرایطی هست که قبل از merge کردن توش بودیم. یعنی همه‌ی محیط‌هایِ کاری یکسان بودن.

Undoing-a-Faulty-Merge-2

حالا برگردیم به ترمینال‌مون.

پس از نوشتنِ دستورِ ریست، merge commit رو دیگه در تاریخچه‌مون نخواهیم دید ولی هنوز در repositoryمون باقیست. پس می‌تونیم اون رو ریکاوری کنیم. البته برای ریکاوری کردنش باید آی‌دی‌ش رو بلد باشیم.

git reset --hard f634b2a

revert کردن آخرین کامیت

با توجه به اینکه می‌خواهیم آخرین کامیت‌مون که همون merge commit هست رو revert کنیم از دستورِ زیر استفاده می‌کنیم.

git revert HEAD

البته اجرای دستورِ بالا با خطایِ نبودِ آپشنِ m- همراه خواهد بود.

با توجه به تصویرِ زیر merge commit دو تا والد داره. یکی در master branch و دیگری در feature branch.

خب، برای revert کردنِ این merge commit باید برای گیت مشخص کنیم که به چه شکلی می‌خواهیم تغییرات رو revert کنیم. در این مثال می‌خواهیم کامیت رو به همون حالتِ قبلیِ پدرش در master branch برگردونیم. همون کامیتِ قبل از شروع به merge کردن. یعنی اولین والدِ merge commit. چون قبل از merge کردن کامیتِ master بوده، سپس والد دوم در feature branch بوجود اومده.

در تصویر زیر merge commit بصورتِ توپرِ پررنگ و والدها بصورتِ توخالیِ پررنگ مشخص شدن.

Undoing-a-Faulty-Merge-3

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

در دستورِ زیر منظور از m- 1 اولین والد هستش. کامیتِ هدف‌مون هم آخرین کامیت هست که اون رو با HEAD مشخص کردیم.

git revert -m 1 HEAD

با اجرایِ دستورِ بالا، گیت ادیتورِ پیشفرض رو باز می‌کنه تا پیامِ کامیت رو مشاهده کنیم و در صورتِ نیاز ویرایشش بدیم.