در این بخش در مورد دستورها و استراتژی های قابل استفاده در Git برای لغو تغییرها یا commit ها بحث خواهیم کرد. توجه به این نکته مهم است که Git دارای سیستم "لغو (undo)" مانند آن چه در یک برنامه ی پردازش کلمه یافت می شود، نیست. خودداری از برگرداندن عملیات Git به هر یک از شکل های سنتی، مفید خواهد بود. افزون بر این، Git دارای نام خاص خود برای عملیات لغو است که بهتر است از آن ها استفاده شود. این نام های خاص شامل اصلاحاتی مانند reset، revert، checkout، clean و موارد دیگری است.
یک تشبیه جالب این است که Git را به عنوان یک ابزار مدیریت جدول زمانی در نظر بگیریم. Commit ها، ثبت های لحظه ای از زمان یا نقاط جالب توجه در جدول زمانی تاریخچه ی یک پروژه هستند. افزون بر این، چندین جدول زمانی را می توان با استفاده از شاخه ها مدیریت کرد. هنگام انجام لغو در Git، معمولا به گذشته یا خط زمانی دیگری که خطایی در آن رخ نداده است، باز می گردید.
این آموزش کلیه ی مهارت های لازم برای کار با ویرایش های پیشین یک پروژه ی نرم افزاری را ارائه می دهد. در ابتدا، شیوه ی کاوش در commit های قدیمی را به شما نشان می دهد، سپس تفاوت بین revert کردن commit های عمومی در تاریخچه ی پروژه را در مقابل reset کردن تغییرهای منتشر نشده در دستگاه محلی تان توضیح می دهد.
یافتن آنچه از دست رفته: مرور commit های قدیمی
ایده ی اصلی پشت هر سیستم کنترل نسخه، ذخیره ی نسخه های "ایمن" از یک پروژه است تا هرگز نگران خراب شدن غیرقابل جبران کد خود نباشید. پس از ایجاد یک تاریخچه از commit ها، می توانید هر commit ای را در تاریخچه ی پروژه مرور و بازبینی کنید. یکی از بهترین ابزارها برای بررسی تاریخچه ی مخزن Git، دستور git log است. در مثال زیر، از git log برای دریافت لیستی از جدیدترین commit ها به یک کتابخانه ی گرافیکی منبع باز محبوب، استفاده می کنیم.
git log --oneline
e2f9a78fe Replaced FlyControls with OrbitControls d35ce0178 Editor: Shortcuts panel Safari support. 9dbe8d0cf Editor: Sidebar.Controls to Sidebar.Settings.Shortcuts. Clean up. 05c5288fc Merge pull request #12612 from TyLindberg/editor-controls-panel 0d8b6e74b Merge pull request #12805 from harto/patch-1 23b20c22e Merge pull request #12801 from gam0022/improve-raymarching-example-v2 fe78029f1 Fix typo in documentation 7ce43c448 Merge pull request #12794 from WestLangley/dev-x 17452bb93 Merge pull request #12778 from OndrejSpanel/unitTestFixes b5c1b5c70 Merge pull request #12799 from dhritzkiv/patch-21 1b48ff4d2 Updated builds. 88adbcdf6 WebVRManager: Clean up. 2720fbb08 Merge pull request #12803 from dmarcos/parentPoseObject 9ed629301 Check parent of poseObject instead of camera 219f3eb13 Update GLTFLoader.js 15f13bb3c Update GLTFLoader.js 6d9c22a3b Update uniforms only when onWindowResize 881b25b58 Update ProjectionMatrix on change aspect
هر commit، یک هش شناسایی SHA-1 منحصر به فرد دارد. این شناسه ها برای مرور جدول زمانی commit ها و بازبینی آن ها استفاده می شوند. به طور پیش فرض، git log فقط commit های مربوط به شاخه ی فعلی انتخاب شده را نشان می دهد. ممکن است commit ای که به دنبال آن هستید در شاخه ی دیگری باشد. با اجرای git log --branches=* می توانید تمامی commit های موجود در همه ی شاخه ها را مشاهده کنید. از دستور git branch برای مشاهده و بازدید از شاخه های دیگر استفاده می شود. فراخوانی دستور git branch -a لیستی از نام شاخه ها را برمی گرداند. سپس می توان نام یکی از این شاخه ها را با دستور git log وارد کرد.
وقتی مرجع مربوط به commit ای را در تاریخچه ای که می خواهید بازنگری کنید پیدا کردید، می توانید از دستور git checkout برای بازبینی آن commit استفاده کنید. Git Checkout راهی آسان برای بارگیری هر یک از commit های ذخیره شده روی دستگاه توسعه ی شما است. در طی روند عادی توسعه، HEAD معمولا به master یا یک شاخه ی محلی اشاره می کند، اما وقتی به commit قبلی checkout می کنید، HEAD، دیگر به شاخه ای اشاره نمی کند، بلکه به طور مستقیم به یک commit اشاره می کند. این حالت "HEAD detached" نامیده می شود و می توان آن را به صورت زیر تجسم کرد:
Check out یک فایل قدیمی نشان گر HEAD را جابه جا نمی کند. با اجتناب از "detached head"، در همان شاخه و همان commit باقی می مانید. سپس می توانید همانند سایر تغییرها، نسخه ی قدیمی فایل را در یک commit جدید ثبت کنید. در واقع، این استفاده از git checkout روی یک فایل، به عنوان روشی برای بازگشت به نسخه ی قدیمی یک فایل منحصر به فرد عمل می کند. برای کسب اطلاعات بیشتر در مورد این دو حالت به صفحه ی git checkout مراجعه کنید.
مشاهده ی نسخه قدیمی
این مثال فرض می کند که شما شروع به توسعه ی یک آزمایش crazy کرده اید، اما مطمئن نیستید که می خواهید آن را حفظ کنید یا نه. برای کمک به تصمیم گیری، می خواهید قبل از شروع آزمایش، نگاهی به وضعیت پروژه بیاندازید. ابتدا باید شناسه ی نسخه ی مورد نظر را برای مشاهده پیدا کنید.
git log --oneline
بگذارید بگوییم تاریخچه ی فایل شما چیزی شبیه به زیر است:
b7119f2 Continue doing crazy things
872fa7e Try something crazy
a1e8fb5 Make some important changes to hello.txt
435b61d Create hello.txt 9773e52 Initial import
می توانید از git checkout برای مشاهده ی commit با عنوان "Make some import changes to hello.txt" استفاده کنید:
git checkout a1e8fb5
این باعث می شود که مسیر کاری شما با وضعیت دقیق commit با شناسه ی a1e8fb5 مطابقت داشته باشد. بدون نگرانی در مورد از دست دادن وضعیت فعلی پروژه، می توانید به فایل ها نگاه کنید، پروژه را کامپایل (compile) کنید، تست ها را اجرا کنید و حتی فایل ها را ویرایش کنید. هیچ یک از کارهایی که در این جا انجام می دهید در مخزن شما ذخیره نمی شود. برای ادامه ی توسعه، باید به وضعیت "کنونی" پروژه ی خود برگردید:
git checkout main
این حالت فرض می کند که در حال توسعه در شاخه ی اصلی پیش فرض هستید. پس از بازگشت به شاخه ی اصلی، برای لغو تغییرهای نامطلوب می توانید از git revert یا git reset استفاده کنید.
لغو یک commit
از نظر فنی چندین استراتژی مختلف برای "لغو" یک commit وجود دارد. مثال های زیر فرض می کنند که ما تاریخچه commit ای داریم که به صورت زیر است:
git log –oneline
872fa7e Try something crazy
a1e8fb5 Make some important changes to hello.txt
435b61d Create hello.txt 9773e52 Initial import
ما بر لغو 872fa7e با عنوان "Try something crazy" تمرکز خواهیم کرد.
چگونه با git checkout، یک commit را لغو کنیم
با استفاده از دستور git checkout می توانیم commit قبلی (a1e8fb5) را checkout کنیم و مخزن را در حالتی قبل از وقوع commit crazy قرار دهیم. بررسی یک commit خاص، مخزن را در حالت "HEAD detached" قرار می دهد. یعنی شما دیگر در هیچ شاخه ای کار نمی کنید. در حالت detached، وقتی شاخه ها را به شاخه ی معینی برگردانید؛ commit های جدید، یتیم می شوند. Commit های یتیم برای حذف توسط garbage collector موجود در Git، آماده هستند. garbagecollector با یک وقفه ی پیکربندی شده کار می کند و commit های یتیم را برای همیشه از بین می برد. برای جلوگیری از حذف commit های یتیم، باید اطمینان حاصل کنیم که در یک شاخه هستیم.
از حالت detached HEAD می توانیم این دستور را اجرا کنیم:
git checkout -b new_branch_without_crazy_commit
این دستور یک شاخه ی جدید با نام new_branch_without_crazy_commit ایجاد می کند و به آن حالت، تغییر وضعیت می دهد. اکنون مخزن روی یک جدول زمانی تاریخچه ی جدید است که در آن commit 872fa7e دیگر موجود نیست. در این مرحله، ما می توانیم کار روی این شاخه ی جدید را که در آن commit 872fa7e دیگر وجود ندارد ادامه دهیم و آن را "لغو شده" بدانیم.
متاسفانه اگر به شاخه ی قبلی نیاز داشته باشید، که شاخه ی master شما باشد؛ این استراتژی لغو مناسب نیست. به دنبال دیگر استراتژی های لغو باشید. برای اطلاعات و مثال های بیشتر، بحث جامع ما در مورد git checkout را مرور بفرمایید.