تمام توسعه دهندگان حرفه ای از Git برای مدیریت پروژه های خودشان استفاده می کنند و در واقع استفاده از Git به یک الزام در دنیای برنامه نویسی تبدیل شده است. در نگاه اول Git برای توسعه دهندگان تازه کار مانند یک بار اضافه است که هیچ مزیتی نداشته و فقط بار کاری را سنگین تر می کند اما اصلا اینطور نیست. حقیقت ماجرا این است که اگر بخواهید یک توسعه دهنده حرفه ای شده و در شرکتی مشغول به کار شوید، آشنایی با Git یک الزام است و مزیت های بی نهایتی دارد. در عین حال این مقاله در رابطه با مزایای گیت نیست بلکه در رابطه با تصحیح اشتباهات است.
بسیار پیش می آید که در هنگام نوشتن کد در پروژه ای که با Git مدیریت می شود، اشتباهی را انجام دهید و حالا نیاز دارید این اشتباه را به حالت قبلی خود برگردانید. ما در گیت انواع و اقسام اشتباهات را داریم، بنابراین من آن ها را به ۱۴ دسته مختلف تقسیم کرده ام تا آن ها را با هم بررسی کنیم. قبل از شروع بحث باید با چند مفهوم زیر آشنا باشید:
سطح مقاله: این مقاله برای افرادی نوشته شده است که با مفاهیم ابتدایی Git آشنا هستند. در صورتی که هیچ آشنایی با Git ندارید، درک این مقاله برایتان دشوار خواهد بود. پیشنهاد می شود ابتدا مقالات آموزشی گیت را مطالعه نمایید. در ضمن در نظر داشته باشید که این مقاله برای سال ۲۰۲۱ نوشته شده است بنابراین به جای git checkout از git restore و git switch استفاده می شود. این دستورات در نسخه 2.23 اضافه شده و جدید هستند.
برای شروع کار ابتدا یک پوشه را بسازید و سه فایل را در آن ایجاد کنید. من نام این سه فایل را roxo.txt و web-dev.txt و misc.txt گذاشته ام. درون فایل roxo.txt متن زیر قرار دارد:
This is the roxo text file.
درون فایل web-dev.txt متن زیر قرار دارد:
This is the web-dev text file.
درون فایل misc.txt نیز متن زیر را نوشته ایم:
This is the misc text file.
در مرحله بعدی مطمئن شوید که Git روی سیستم شما نصب شده است. برای نصب گیت، کاربران لینوکس می توانند دستور sudo apt install git را در ترمینال اجرا کنند و کاربران ویندوز می توانند git for windows را دانلود و نصب کنند. کاربران لینوکس از این بخش به بعد تمام دستورات گیت را درون ترمینال و کاربران ویندوز تمام دستورات را درون Git bash (ترمینالی که با git for windows نصب می شود) اجرا می کنند.
حالا در همان پوشه ای که ساخته اید ترمینال خود را باز کرده و دستور git init را اجرا کنید تا پروژه توسط گیت رهگیری شود. در مرحله بعدی دستور git status را اجرا کنید تا وضعیت فایل ها را ببینید. باید چنین نتیجه ای را دریافت کنید:
On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) misc.txt roxo.txt web-dev.txt nothing added to commit but untracked files present (use "git add" to track)
یعنی سه فایل در این پروژه وجود دارند که untracked هستند (توسط گیت رهگیری نمی شوند). این سه فایل همان سه فایل متنی ما هستند که نامشان را در گزارش بالا می بینید. حالا دستور زیر را اجرا کنید تا هر سه فایل وارد staging area شوند:
git add .
در نهایت اولین commit پروژه را انجام می دهیم:
git commit -m "Initial Commit ✅"
با انجام این کار معمولا نتیجه ای به شکل زیر را دریافت می کنیم:
[master (root-commit) 8637244] Initial Commit ✅ 3 files changed, 3 insertions(+) create mode 100644 misc.txt create mode 100644 roxo.txt create mode 100644 web-dev.txt
حالا محیط کار آماده شده است و می توانیم به بحث تغییرات گیت بپردازیم.
برخی اوقات یک فایل را ویرایش می کنید اما هنوز تغییرات را commit نکرده اید. بعد از مدتی متوجه می شوید که تغییرات ایجاد شده بسیار بد بوده اند تا جایی که تمام این تغییرات باید حذف شوند. من برای شبیه سازی این موقعیت فایل roxo.txt را باز کرده و یک خط دیگر به متن آن اضافه می کنم:
This is the roxo text file. This is an extra line.
با انجام این کار فایل roxo.txt ویرایش شده است بنابراین اگر دستور git status را اجرا کنید به شما نشان داده می شود که یک فایل تغییر کرده است:
On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: roxo.txt no changes added to commit (use "git add" and/or "git commit -a")
در صورتی که می خواهید تفاوت ایجاد شده را در ترمینال خود مشاهده کنید باید از دستور git diff استفاده کنید:
git diff roxo.txt
با اجرای این کد نتیجه ای مانند نتیجه زیر را دریافت می کنید:
diff --git a/roxo.txt b/roxo.txt index 8d9440b..f0a9ce8 100644 --- a/roxo.txt +++ b/roxo.txt @@ -1 +1,3 @@ This is the roxo text file. + +This is an extra line.
همانطور که می بینید علامت + در ابتدای خط نشان دهنده اضافه شدن خط است بنابراین ما دو خط را به roxo.txt اضافه کرده ایم: یک خط خالی (اینتر) و یک خط که متن This is an extra line را دارد.
دقت کنید که ما هنوز این تغییرات را commit نکرده ایم. برای برگرداندن فایل roxo.txt به commit قبلی (به حالت قبل از ویرایش) می توانیم از دو روش استفاده کنیم. روش اول که روشی قدیمی است، استفاده از دستور checkout می باشد:
git checkout HEAD roxo.txt
همانطور که می دانید HEAD همیشه به آخرین commit اشاره می کند بنابراین در دستور بالا گفته ایم فایل roxo.txt را به آخرین commit خودش برگردان. روش دوم که روش جدید تر و بهتری است، از دستور restore به جای checkout استفاده می کند:
git restore HEAD roxo.txt
restore به معنی برگرداندن است بنابراین استفاده از آن واضح تر است. با اجرای هر کدام از این دو دستور، فایل roxo.txt به حالت قبل برمی گردد و اگر git status را اجرا کنید هیچ تغییری را مشاهده نخواهید کرد.
هشدار: زمانی که تغییرات commit نشده را حذف می کنید (مانند کاری که در این مثال انجام دادیم) دیگر نمی توانید محتوای از دست رفته را برگردانید. مثلا در این مثال خط This is an extra line برای همیشه حذف شده است و راهی برای بازگرداندن آن نیست.
برخی از اوقات به جای ویرایش یک فایل، اشتباها یک فایل را حذف می کنیم اما حالا به آن نیاز داریم. در این مثال هنوز تغییرات را commit نکرده ایم. برای شبیه سازی این حالت فایل roxo.txt را حذف می کنم. حالا اگر git status را اجرا کنید چنین نتیجه ای را می گیرید:
On branch master Changes not staged for commit: (use "git add/rm <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) deleted: roxo.txt
در کنار نام فایل عبارت deleted ذکر شده است که یعنی فایل ما حذف شده است. برای برگرداندن این فایل باز هم می توانیم از دستور restore استفاده کنیم!
git restore roxo.txt
با اجرای این دستور فایل roxo برمی گردد اما سوال دیگری مطرح می شود. در صورتی که تغییرات (حذف فایل) را commit کرده باشیم چطور؟ اگر فایل محلی را حذف کرده اید، این تغییر را commit کرده اید اما هنوز آن را به سروری push نکرده اید، دو راه حل دارید.
راه اول: استفاده از hard reset که به شکل زیر انجام می شود:
git reset --hard HEAD~1
HEAD~1 به معنی پدرِ commit قبلی است (آخرین commit نه، بلکه commit قبل از آن). با اجرای این دستور تمام تغییرات فایل های رهگیری شده (tracked files) را حذف می شود بنابراین در استفاده از آن دقت بسیار زیادی داشته باشید. چرا می خواهیم به پدرِ commit قبلی برویم؟ ما ابتدا یک commit را داشته ایم که در آن فایل roxo.txt وجود داشته است و سپس آن را حذف کرده ایم و سپس commit کرده ایم. commit فعلی (آخرین commit) همان commit ای است که فایل roxo.txt در آن حذف شده است بنابراین برگشتن به آن فایده ای ندارد چرا که در حال حاضر در آن commit هستیم بنابراین باید به commit قبل از آن برگردیم.
راه دوم: فلگ hard-- را از دستور بالا حذف کنید تا یک reset عادی داشته باشیم. از این مورد زمانی استفاده می شود که نخواهید تغییرات فایل های دیگرتان را حذف کنید:
git reset HEAD~1
با اجرای این دستور فایل حذف شده به عنوان یک تغییر unstaged در کنار دیگر تغییرات نمایش داده می شود. به زبان دیگر فایل هنوز حذف شده است اما commit نشده است (آن را از commit خارج کرده ایم) بنابراین می توانیم از restore برای برگرداندن آن استفاده کنیم:
git restore roxo.txt
برخی اوقات نوشتن کدها آنچنان به هم پیچیده می شوند که دیگر راهی برای ویرایش آن ها نیست و می خواهیم تمام تغییرات ایجاد شده را حذف کرده و کدها را به commit قبلی برگردانیم. برای انجام این کار چه کار باید کرد؟ برای شبیه سازی این حالت هر سه فایل متنی خود را ویرایش کرده و خطی را به آن ها اضافه می کنیم. در ابتدا فایل misc.txt را بدین صورت ویرایش می کنم:
This is the misc text file. Some Added stuff that should not be here.
خط دوم را به تازگی اضافه کرده ام. در مرحله بعدی فایل roxo.txt را ویرایش می کنیم:
This is the roxo text file. Some Added stuff that should not be here.
و نهایتا به فایل web.dev.txt می رسیم:
This is the web-dev text file. Some Added stuff that should not be here.
ما در هر سه فایل یک خط متن اضافه وارد کرده ایم. در این مرحله برای برگرداندن تمام فایل ها به حالت قبل فقط از دستور زیر استفاده می کنیم:
git restore .
با اجرای این دستور تمام فایل ها به حالت قبل برمی گردد. البته توجه داشته باشید که من هنوز هیچ تغییری را commit نکرده ام. در صورتی که تغییری را commit کرده باشید مبحث کاملا فرق می کند. برای برگشتن به یک commit خاص ابتدا باید هش آن commit را به دست بیاورید. برای این کار می توانید دستور git log را اجرا کنید که چنین نتیجه ای به شما می دهد:
commit 86372440309cc9b5b1e824a3d5a33a9cdc707934 (HEAD -> master) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 10:17:36 2021 +0430 Initial Commit ✅
از آنجایی که من فقط یک commit داشتم تنها همین commit را می بینیم. در commit بالا مشخص است که هش آن 86372440309cc9b5b1e824a3d5a33a9cdc707934 می باشد بنابراین برای برگشتن به یک commit (در صورتی که commit دیگری داشتیم) باید از دستور زیر استفاده کنیم:
git reset --hard 86372440309cc9b5b1e824a3d5a33a9cdc707934
در صورتی که این دستور را اجرا کنید، commit های قبلی حذف می شوند بنابراین حواستان را در هنگام استفاده از این دستور جمع کنید.
تمام موارد قبلی (۱ و ۲ و ۳) همگی مربوط به فایل های commit نشده بودند اما از این بخش به بعد با فایل های commit شده کار خواهیم کرد.
در این بخش می خواهیم در رابطه با تصحیح آخرین commit صحبت کنیم. بسیار پیش می آید که پیام commit خود را به اشتباه تایپ کنیم و یا اینکه فایلی را به staging area اضافه نکرده باشیم و commit را بدون آن انجام بدهیم. اشتباهات این قبیل بسیار رایج هستند و شاید این بخش یکی از پرکاربرد ترین بخش های این مقاله برای شما باشد.
برای شبیه سازی این حالت بیایید دو commit دیگر را در پروژه خودمان ایجاد کنیم. من برای این کار ابتدا فایل roxo.txt را باز کرده و محتوای آن را به شکل زیر ویرایش می کنم:
This is the roxo text file. Roxo is a great platform for learning programming and becoming an experienced web developer. You can visit us at roxo.ir/blog for tips at any time!
حالا این تغییرات را به staging area اضافه می کنیم:
git add .
و نهایتا آن را commit می کنیم:
git commit -m "Adding description to roxo.txt 🔥"
برای commit بعدی فایل misc.txt را باز کرده و آن را به شکل زیر ویرایش می کنیم:
This is the misc text file. Misc is short for miscellaneous which means aggregation of different kinds.
در نهایت این تغییر را نیز با git add به staging area اضافه کرده و سپس با دستور زیر commit می کنیم:
git commit -m "Edited misc.txt ✅"
فرض کنید من بخواهم پیام commit بالا را تغییر بدهم یا اینکه در آن اشتباهی داشته ام و حالا می خواهم آن را تصحیح کنم. برای انجام این کار باید فلگ amend-- را به دستور همیشگی commit اضافه کنید:
git commit --amend -m "Edited the misc.txt and the commit message at once ✅"
حالا برای اینکه چک کنیم و ببینیم آیا آخرین commit ویرایش شده است یا خیر، از دستور git log استفاده می کنیم. با اجرای این دستور در ترمینال چنین نتیجه ای را می گیرید:
commit 3e66d03ab368e6c3d0492d353a9b0249c6f8be9c (HEAD -> master) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:48:53 2021 +0430 Edited the misc.txt and the commit message at once ✅ commit b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:45:17 2021 +0430 Adding description to roxo.txt 🔥 commit 86372440309cc9b5b1e824a3d5a33a9cdc707934 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 10:17:36 2021 +0430 Initial Commit ✅
در کد بالا مشخص است که به جای اضافه شدن یک commit جدید، پیام همان commit آخر ویرایش شده است. ما در این مثال پیام commit را ویرایش کرده ایم اما شما می توانید کار های دیگری را نیز انجام بدهید. مثلا می توانید فایل ها را ویرایش کرده یا فایل خاصی را به staging area اضافه کنید و در نهایت دوباره پیام commit را با amend-- بنویسید. هر زمانی که amend در دستور commit باشد، commit جدیدی نخواهیم داشت بلکه همان commit قبلی ویرایش می شود.
به عنوان یک هشدار عرض می کنم، دستوراتی مانند amend یا hard reset تاریخچه commit ها را بازنویسی می کنند. به عنوان یک قانون کلی به یاد داشته باشید که هیچگاه تاریخچه commit هایی را که قبلا به یک سرور push کرده اید، ویرایش نکنید. این کار باعث سردرگمی و بهم ریختن کدهای توسعه دهندگان دیگری می شود که همراه شما روی یک پروژه کار می کنند.
بازنویسی تاریخچه یا commit history چیست؟ یعنی گذشته نادیده گرفته می شود. مثلا با amend کردن (مانند کد بالا)، هشِ commit آخر تغییر می کند. به زبان ساده تر commit قبلی ویرایش نمی شود بلکه به طور کل حذف می شود و به جای آن یک commit جدید اضافه خواهد شد.
منظور من از commit میانی، commit ای است که بین دو commit دیگر قرار دارد (هر commit ای که commit آخر یا اول نباشد). به مثال زیر توجه کنید:
Commit 1 -> Commit 2 -> Commit 3 -> Commit 4
تصور کنید ما عنوان یک فایل را در Commit شماره ۲ تغییر داده ایم و حالا پشیمان شده ایم. راه مخرب حل این مشکل حذف commit شماره ۲ است که به دلیل بازنویسی تاریخچه اصلا پیشنهاد نمی شود. راه بهتر این است که از دستور revert استفاده کنید. دستور revert به جای دستکاری تاریخچه، commit دوم را گرفته و آن را برعکس می کند و بر همین اساس یک commit جدید می سازد (بدون اینکه به commit دوم دست بزند).
در حال حاضر با اجرای دستور git log نتیجه زیر را می گیریم:
commit 3e66d03ab368e6c3d0492d353a9b0249c6f8be9c (HEAD -> master) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:48:53 2021 +0430 Edited the misc.txt and the commit message at once ✅ commit b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:45:17 2021 +0430 Adding description to roxo.txt 🔥 commit 86372440309cc9b5b1e824a3d5a33a9cdc707934 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 10:17:36 2021 +0430 Initial Commit ✅
بنابراین در مجموع سه commit داریم. commit میانی ما در این حالت commit مربوط به فایل roxo.txt است. فرض کنید ما بخواهیم تغییرات انجام شده در این commit را revert (برعکس) کنیم. برای این کار می گوییم:
git revert b0b637bc14
در هنگام ذکر هش یک commit الزامی ندارد تمام آن هش را بنویسید. همانطور که می بینید من در کد بالا فقط چند رقم ابتدایی آن را نوشته ام. با اجرای دستور بالا پنجره ویرایشگرتان باز می شود که محتوایی شبیه به محتوای زیر را دارد:
Revert "Adding description to roxo.txt 🔥" This reverts commit b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1. # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # On branch master # Changes to be committed: # modified: roxo.txt #
چرا چنین پنجره ای برای ما باز شده است؟ من قبلا توضیح دادم که دستور revert به commit اصلی دست نمی زند بلکه یک commit جدید را ایجاد می کند و طبیعتا هر commit جدید باید یک پیامِ commit داشته باشد. خطوطی که با # شروع می شوند کامنت هستند و تاثیری ندارند. من همین پیام پیش فرض را دوست دارم (دو خط اول) اما شما می توانید آن را ویرایش کنید. در هر حال پنجره ویرایشگر را می بندیم تا عملیات تمام شود.
حالا بیایید یک بار دیگر دستور git log را اجرا کنیم تا ببینیم آیا commit جدیدی اضافه شده است یا خیر:
commit ffd34fd99848da196420b78e9a18b967fc8f169b (HEAD -> master) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 17:20:48 2021 +0430 Revert "Adding description to roxo.txt 🔥" This reverts commit b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1. commit 3e66d03ab368e6c3d0492d353a9b0249c6f8be9c Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:48:53 2021 +0430 Edited the misc.txt and the commit message at once ✅ commit b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:45:17 2021 +0430 Adding description to roxo.txt 🔥 commit 86372440309cc9b5b1e824a3d5a33a9cdc707934 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 10:17:36 2021 +0430 Initial Commit ✅
همانطور که می بینید حالا یک commit کاملا جدید داریم که دقیقا برعکس commit b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1 است. یعنی چه؟ در commit اصلی (commit دوم) یک خط طولانی را به فایل roxo.txt اضافه کردیم اما حالا اگر roxo.txt را باز کنید می بینید که آن خط حذف شده است و دیگر وجود ندارد.
برخی اوقات به جای اینکه بخواهیم فقط یک commit خاص را برگردانیم (revert) می خواهیم به طور کل به یکی از commit های قبلی برگردیم یا به عبارتی سفر در زمان انجام بدهیم. چرا؟ فرض کنید commit های زیر را داشته باشیم:
Commit 1 -> Commit 2 -> Commit 3 -> Commit 4 -> Commit 5
تصور کنید تمام commit های بعد از commit دوم خراب هستند یا به نوعی محتوایشان دیگر فاقد اعتبار می باشند بنابراین می خواهیم مستقیما به commit دوم برگردیم و commit های سوم و چهارم و پنجم را نادیده بگیریم. برای این کار از دستور reset استفاده می کنیم:
git reset --hard b0b637bc14b599
عدد عجیب b0b637bc14b599 همان هش commit مورد نظر ما است (من commit دوم که متعلق به فایل roxo.txt بود را انتخاب کرده ام). در مثال های قبلی توضیح دادم که شما می توانید با دستور git log تمام commit ها و هش های آن ها را مشاهده کنید. با اجرای این ستور تمام پروژه به commit دوم برمی گردد بنابراین تمام commit های دیگر از بین می روند. برای اثبات این موضوع دو راه وجود دارد. راه ساده این است که فایل هایتان را باز کرده و محتویاتشان را ببینید. مثلا فایل misc.txt دیگر خط اضافه شده را ندارد و تک خطی است. روش دوم که بهتر است، استفاده از git log برای نمایش commit های موجود است:
commit b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1 (HEAD -> master) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:45:17 2021 +0430 Adding description to roxo.txt 🔥 commit 86372440309cc9b5b1e824a3d5a33a9cdc707934 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 10:17:36 2021 +0430 Initial Commit ✅
همانطور که می بینید فقط دو commit باقی مانده است.
حالا بیایید در مورد بخش hard-- صحبت کنیم. زمانی که از دستور reset استفاده می کنید سه فلگ یا سه گزینه خاص دارید:
soft--
: کلمه soft به معنی «نرم» است. این دستور تغییرات ایجاد شده را uncommit می کند (یعنی از حالت commit بودن خارج می کند) اما تغییرات حذف نمی شوند بلکه در staging area باقی می مانند. به عبارتی اگر بعد از اجرای ریست نرم دستور git status را اجرا کنید می بینید که تغییرات فایل ها حذف نشده اند (مثلا محتوای misc.txt تغییری نکرده است) اما در staging area و آماده commit شدن هستند.mixed--
: کلمه mixed به معنی «ترکیبی» است. این حالت همان حالت پیش فرض reset است، یعنی اگر فقط git reset را بدون هیچ فلگی اجرا کنید، این گزینه انتخاب می شود. ریست ترکیبی یا mixed باعث می شود تغییرات uncommit شوند اما علاوه بر این، آن ها را unstage نیز می کند (یعنی از staging area خارج می کند). توجه داشته باشید که مانند حالت قبل، فایل های شما و محتوایشان حذف نمی شوند بلکه فقط به حالت commit مورد نظر در می آیند.hard--
: کلمه hard به معنای «سخت» می باشد. ریست سخت یا hard reset یعنی تغییرات uncommit شده، سپس unstage شده و در نهایت حذف می شوند. با این حساب هیچ چیزی در working tree باقی نمی ماند.برای اینکه هر سه حالت را در عمل به شما نشان بدهم نیاز داریم هر بار یک commit را ایجاد کنیم. من هر بار فایل misc.txt را به شکل زیر ویرایش می کنم:
This is the misc text file. Misc is short for miscellaneous which means aggregation of different kinds.
سپس با دستور git add و سپس git commit آن را ثبت می کنم تا پس از هر بار ریست محتوایش را ببینم. یک بار فایل misc.txt را به شکل بالا ویرایش کرده و commit کنید. در حال حاضر اگر git log را اجرا کنید باید چنین نتیجه ای بگیرید:
commit e251d31cbd0e22f87a9724178a59f8c58707a90a (HEAD -> master) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Wed Apr 28 10:23:28 2021 +0430 Adding to misc.txt file commit b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:45:17 2021 +0430 Adding description to roxo.txt 🔥 commit 86372440309cc9b5b1e824a3d5a33a9cdc707934 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 10:17:36 2021 +0430 Initial Commit ✅
در مرحله بعدی دستور زیر را اجرا کنید تا یک ریست نرم انجام بدهیم:
git reset --soft b0b637bc14b599
طبیعتا شما باید به جای b0b637bc14b599 شماره هش commit مورد نظر خودتان را قرار بدهید. با اجرای این دستور محتوای misc.txt دست نمی خورد و هنوز به شکل زیر است:
This is the misc text file. Misc is short for miscellaneous which means aggregation of different kinds.
همچنین اگر git status را اجرا کنید این نتیجه را می گیرید:
On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: misc.txt
همانطور که می بینید misc.txt در staging area قرار دارد و آماده commit شدن است. این یک ریست نرم بود. من با دستور git commit و مثل همیشه دوباره این تغییرات را commit کرده و این بار یک ریست ترکیبی انجام می دهم:
git reset --mixed b0b637bc14b599
با اجرای این دستور محتویات misc.txt هنوز هم سر جایشان هستند اما git status نتیجه متفاوتی به ما نشان می دهد:
On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: misc.txt no changes added to commit (use "git add" and/or "git commit -a")
همانطور که می بینید فایل misc.txt در staging area نیست بنابراین فقط در working directory یا همان پوشه پروژه وجود دارد. حالا یک بار دیگر فایل misc.txt را به شکلی که گفتم ویرایش کرده و commit کنید اما این بار ریست سخت انجام می دهیم:
git reset --hard b0b637bc14b599
اگر پس از اجرای این دستور فایل misc.txt را باز کنید متوجه خواهید شد که محتوای قبلی (تمام تغییرات) از بین رفته است. همچنین اگر git status را اجرا کنید نتیجه زیر را دریافت خواهید کرد:
On branch master nothing to commit, working tree clean
به عبارتی دیگر هیچ تغییری نداریم (چه stageشده و چه stage نشده).
در بخش قبلی به شما توضیح دادم که چطور کل پروژه را به نقطه ای خاص در زمان برگردانیم اما چطور می توانیم فقط یک فایل را به نقطه ای خاص برگردانیم؟ برای شبیه سازی این حالت من فایل web-dev.txt را باز کرده و محتوایش را به شکل زیر ویرایش می کنم:
This is the web-dev text file. Web development is extremely fun!
سپس فایل misc.txt را باز کرده و آن را به شکل زیر ویرایش کنید:
This is the misc text file. Misc is a file without any categories.
در نهایت این تغییرات را commit کنید (از آنجایی که بار ها commit کرده ایم دیگر توضیح نمی دهم چطور این کار را انجام بدهید).
در حال حاضر git log باید چنین گزارشی را برایتان برگرداند:
commit 98967c7fecb50e3190382cb3d3bc509cb00bd0df (HEAD -> master) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Wed Apr 28 11:07:52 2021 +0430 misc.txt & web-dev.txt were both modified. commit b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:45:17 2021 +0430 Adding description to roxo.txt 🔥 commit 86372440309cc9b5b1e824a3d5a33a9cdc707934 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 10:17:36 2021 +0430 Initial Commit ✅
در حال حاضر آخرین commit ما دو فایل را تغییر داده است بنابراین اگر reset انجام بدهیم هر دو فایل به همراه هم برمی گردند. راه حل ما استفاده از restore و پاس دادن فلگ source به آن است:
git restore --source b0b637bc14b599 misc.txt
من در دستور بالا گفته ام که فقط فایل misc.txt به هش b0b637bc14b599 (همان commit دوم که commit هدف ما است) برگردد. پس از اجرای این دستور با یک git status ساده متوجه می شوید که فایل misc.txt تغییر کرده است. همچنین اگر خود فایل misc.txt را باز کنید می بینید که دیگر محتوای ویرایش شده را ندارد و فقط رشته This is the misc text file را در خود جای داده است.
شما می توانید این تغییر را commit کرده و یا آن را نادیده بگیرید (به هدف خودتان بستگی دارد). من git restore misc.txt را اجرا می کنم تا تغییرات دور ریخته شوند و دوباره به همان فایل دو خطی برگردیم.
ممکن است در برخی موارد به اشتباه یک commit را حذف کرده باشید، مثلا hard reset انجام داده اید و حالا پشیمان شده اید. راه حل چیست؟ نکته مهمی برای تمام کاربران گیت وجود دارد و آن هم این است که گیت به صورت پیش فرض commit های حذف شده را واقعا حذف نمی کند بلکه تا ۹۰ روز نزد خود نگه می دارد.
در گیت دستوری به نام reflog وجود دارد که یک فایل log از تمام رویداد های مهم مانند reset ها و checkout ها و commit ها و غیره است. هر زمانی که HEAD تغییر کند reflog آن را ثبت می کند و دستوری که باعث این کار شده است مهم نیست. برای مشاهده reflog باید دستور git reflog را در ترمینال اجرا کنید. با انجام این کار چنین نتیجه ای می گیرید:
98967c7 (HEAD -> master) HEAD@{0}: commit: misc.txt & web-dev.txt were both modified. b0b637b HEAD@{1}: reset: moving to b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1 1b0dc65 HEAD@{2}: commit: web-dev.txt was changed b0b637b HEAD@{3}: reset: moving to b0b637bc14b599 b0b637b HEAD@{4}: reset: moving to b0b637bc14b599 3000d78 HEAD@{5}: commit: Adding to misc.txt b0b637b HEAD@{6}: reset: moving to b0b637bc14b599 23d8854 HEAD@{7}: commit: Adding to misc.txt b0b637b HEAD@{8}: reset: moving to b0b637bc14b599 e251d31 HEAD@{9}: commit: Adding to misc.txt file b0b637b HEAD@{10}: reset: moving to b0b637bc14b599 ffd34fd HEAD@{11}: revert: Revert "Adding description to roxo.txt 🔥" 3e66d03 HEAD@{12}: commit (amend): Edited the misc.txt and the commit message at once ✅ 9803194 HEAD@{13}: commit: Edited misc.txt ✅ b0b637b HEAD@{14}: commit: Adding description to roxo.txt 🔥 8637244 HEAD@{15}: checkout: moving from 86372440309cc9b5b1e824a3d5a33a9cdc707934 to master 8637244 HEAD@{16}: checkout: moving from master to 86372440309cc9b5b1e824a3d5a33a9cdc707934 8637244 HEAD@{17}: checkout: moving from 86372440309cc9b5b1e824a3d5a33a9cdc707934 to master 8637244 HEAD@{18}: checkout: moving from master to 86372440309cc9b5b1e824a3d5a33a9cdc707934 8637244 HEAD@{19}: reset: moving to 86372440309cc9b5b1e824a3d5a33a9cdc707934 b321699 HEAD@{20}: reset: moving to HEAD b321699 HEAD@{21}: commit: new 5d535ca HEAD@{22}: commit: This is the second commit - patch level ✅ 8637244 HEAD@{23}: reset: moving to HEAD 8637244 HEAD@{24}: reset: moving to HEAD 8637244 HEAD@{25}: reset: moving to HEAD 8637244 HEAD@{26}: reset: moving to 86372440309cc9b5b1e824a3d5a33a9cdc707934 b475da7 HEAD@{27}: commit: This is the second commit - patch level ✅ 8637244 HEAD@{28}: commit (initial): Initial Commit ✅
من چندین بار فایل های خودم را تغییر داده ام به طوری که باعث تغییر HEAD شده ام (چند بار از دستور checkout استفاده کرده بودم) بنابراین reflog من با شما تفاوت خواهد داشت. مهم اینجاست که تمام تغییراتی که HEAD را تغییر داده اند در اینجا قرار دارند و می توانیم با استفاده از هش های آن ها commit های حذف شده را برگردانیم!
من می خواهم یک reset انجام بدهم تا به commit دوم برگردیم:
git reset --hard b0b637bc14b599
با این کار commit سوم حذف شده است. حالا دوباره git reflog را اجرا می کنم:
b0b637b (HEAD -> master) HEAD@{0}: reset: moving to b0b637bc14b599 98967c7 HEAD@{1}: checkout: moving from my-new-branch to master 23d8854 HEAD@{2}: checkout: moving from master to my-new-branch // بقیه گزارش 98967c7 HEAD@{7}: commit: misc.txt & web-dev.txt were both modified. // بقیه گزارش
شما می توانید هر کدام از این عملیات ها را انتخاب کرده و به هش آن ها بروید. مثلا یکی از موارد در reflog من (شماره ۷) همان commit من برای misc.txt بوده است. شماره صفر نیز همین reset ای است که در حال حاضر انجام دادیم اما حالا پشیمان شده ایم. اگر بخواهیم به commit هفتم برگردیم دو راه حل داریم. راه حل اول این است که از دستور reset استفاده کرده و هش قبلی را به آن بدهیم. با این کار می توانید به commit حذف شده برگردید اما روش بهتری نیز وجود دارد. در روش دوم یک شاخه جدید می سازیم و در آن شاخه به commit مورد نظر برمی گردیم. این کار باعث می شود شاخه اصلی (master) را بهم نریزیم و نظم پروژه را حفظ کنیم. همچنین در صورتی که هیچ مشکلی نداشتیم می توانیم بعدا این دو شاخه را در هم ادغام کنیم. برای استفاده از روش دوم بدین شکل عمل می کنیم:
git branch my-new-branch 98967c7
عدد 98967c7 همان هش مورد نظر من است و my-new-branch نیز نام انتخابی من برای شاخه جدید می باشد. از طرفی git branch نیز یک شاخه جدید می سازد (در صورتی که نام شاخه را به آن بدهیم) بنابراین دستور بالا می گوید یک شاخه جدید ساخته شود که نقطه شروع آن هشِ 98967c7 می باشد. دقت کنید که باید از یک هش قبل تر استفاده کنید. هش شماره b0b637b آخرین هش ما بوده است که در آن reset را انجام دادیم و در حال حاضر در آن هستیم بنابراین برگشت به جایی که در حال حاضر در آن هستیم هیچ معنایی ندارد.
برای نمایش تمام شاخه های موجود در گیت از دستور git branch استفاده می کنیم. با اجرای این دستور چنین نتیجه ای را خواهید گرفت:
* master my-new-branch
علامت ستاره در کنار master نشان می دهد که در حال حاضر در شاخه master هستیم. حالا برای رفتن به شاخه my-new-branch از دستور switch استفاده می کنیم:
git switch my-new-branch
و سپس git log را اجرا می کنیم و این نتیجه را می گیریم:
commit 98967c7fecb50e3190382cb3d3bc509cb00bd0df (HEAD -> my-new-branch) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Wed Apr 28 11:07:52 2021 +0430 misc.txt & web-dev.txt were both modified. commit b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1 (master) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:45:17 2021 +0430 Adding description to roxo.txt 🔥 commit 86372440309cc9b5b1e824a3d5a33a9cdc707934 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 10:17:36 2021 +0430 Initial Commit ✅
همانطور که می بینید حالا این commit را نیز داریم در صورتی که اگر به شاخه master برگردید و دوباره دستور git log را اجرا کنید فقط این نتیجه را می گیرید:
commit b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1 (HEAD -> master) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:45:17 2021 +0430 Adding description to roxo.txt 🔥 commit 86372440309cc9b5b1e824a3d5a33a9cdc707934 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 10:17:36 2021 +0430 Initial Commit ✅
بنابراین می بینیم که در شاخه master هنوز بدون commit سوم هستیم اما در شاخه my-new-branch آن commit را برگردانده ایم. حالا اگر همه چیز مرتب بود و خواستید دو شاخه را در هم ادغام کنیم چطور؟ ابتدا باید در شاخه اصلی باشیم که در این مثال master است و سپس دستور merge را اجرا کنیم:
git merge my-new-branch
پس از اجرای این دستور یک بار دیگر git log را اجرا کنید. این بار commit حذف شده را در شاخه master نیز می بینید. در ضمن توجه داشته باشید که ادغام کردن my-new-branch در master باعث حذف my-new-branch نمی شود و اگر دستور git branch را اجرا کنید هنوز هم این شاخه را می بینید.
در برخی مواقع حس می کنید نیازی به یک شاخه ندارید بنابراین آن را حذف می کنید. مثلا برخی اوقات قرار است ویژگی جدیدی به برنامه اضافه شود اما در میان کار رئیس شما می خواهد این ویژگی را رها کنید و آن قسمت خاص پروژه کنسل می شود. در این حال شما برای تمیز کردن پروژه، شاخه مورد نظر را حذف می کنید اما بعدا نظر رئیس تغییر می کند! در هر صورت نیاز است که شاخه حذف شده را برگردانید.
برای شبیه سازی این حالت ابتدا بیایید شاخه my-new-branch را حذف کنیم. برای این کار ابتدا مطمئن شوید روی شاخه master هستید و سپس دستور زیر را اجرا کنید:
git branch -d my-new-branch
اگر اطلاعاتی در شاخه my-new-branch وجود داشته باشد که هنوز در master ادغام نشده است ممکن است پیام خطایی را دریافت کنید که این شاخه قابلیت حذف را ندارد. در این حالت می توانید دستور زیر را اجرا کنید:
git branch -D my-new-branch
استفاده از D- به جای d- به گیت می گوید که تحت هر شرایطی شاخه مورد نظر را حذف کند. برای برگرداندن این شاخه باید به سراغ git reflog برویم:
98967c7 (HEAD -> master) HEAD@{0}: merge my-new-branch: Fast-forward b0b637b HEAD@{1}: checkout: moving from my-new-branch to master 98967c7 (HEAD -> master) HEAD@{2}: checkout: moving from master to my-new-branch b0b637b HEAD@{3}: checkout: moving from my-new-branch to master 98967c7 (HEAD -> master) HEAD@{4}: checkout: moving from master to my-new-branch b0b637b HEAD@{5}: checkout: moving from my-new-branch to master b0b637b HEAD@{6}: checkout: moving from master to my-new-branch b0b637b HEAD@{7}: reset: moving to b0b637bc14b599 98967c7 (HEAD -> master) HEAD@{8}: checkout: moving from my-new-branch to master 23d8854 HEAD@{9}: checkout: moving from master to my-new-branch 98967c7 (HEAD -> master) HEAD@{10}: checkout: moving from my-new-branch to master // بقیه گزارش
طبیعتا بر اساس اینکه چه کار هایی در پروژه خود انجام داده اید reflog شما تفاوت کمی با rerflog من (گزارش بالا) دارد اما مهم ترین قسمت آن اولین نتیجه ای است که می بینید. از آنجایی که ما در هنگام حذف شاخه my-new-branch در master بودیم، HEAD تغییری نکرده است بنابراین حذف شاخه را در reflog نداریم. این موضوع مشکلی نیست چرا که ما به دنبال حذف شاخه my-new-branch نیستیم بلکه می خواهیم به حالت قبل از آن برگردیم که در این مثال اولین ورودی از گزارش بالا است. با این حساب من ترجیح می دهم یک شاخه دیگر را ساخته و این ورودی را به عنوان محتوای شاخه قرار بدهم:
git branch old-branch 98967c7
با اجرای دستور بالا محتویات شاخه my-new-branch در شاخه جدیدی به نام old-branch برگردانده می شوند. اگر می خواهید نام شاخه نیز یکی باشد می توانید از دستور زیر استفاده کنید:
git branch my-new-branch 98967c7
حالا شاخه my-new-branch به صورت کامل بازگردانی شده است.
بسیاری از شرکت های نرم افزاری قائده معروفی دارند که می گوید هیچ گاه commit هایتان را به مستقیما در شاخه master انجام ندهید بلکه برای هر قابلیت یک شاخه جداگانه ایجاد کنید و پس از توسعه آن قابلیت و تست کامل آن می توانید شاخه مورد نظر را در master ادغام نمایید. اگر شما به اشتباه commit خود را روی master انجام داده باشید چطور؟ آیا می توانیم این commit را روی شاخه دیگری بیاوریم؟
برای شبیه سازی این حالت من یک فایل جدید به نام feature.txt را در کنار فایل های دیگر ساخته و به آن محتوای زیر را می دهم:
This is a new feature in our project.
طبیعتا شما می توانید به جای این خط هر چیز دیگری که می خواهید در این فایل بنویسید. من در حال حاضر روی شاخه master هستم بنابراین از عمد دو دستور زیر را اجرا می کنم تا فایل را commit کنیم:
git add feature.txt git commit -m "A new feature was added in feature.txt!"
در حال حاضر commit ما روی شاخه master ایجاد شده است. چطور می توانیم این مشکل را حل کنیم؟ شاخه master دقیقا همان شاخه ای است که باید ایجاد می کردیم، درست است؟ تنها تفاوت اینجاست که commit به اشتباه درون آن رخ داده است. برای حل این مشکل ابتدا شاخه master را در شاخه ای جدید کپی می کنیم:
git branch features
در این حالت شاخه جدید ما به نام features بر اساس شاخه master ساخته می شود (این طبیعت گیت است) بنابراین تمام commit های master را دارد. به زبان ساده تر در حال حاضر هر دو شاخه features و master یکی هستند. در مرحله بعدی باید شاخه master را یک commit به عقب برگردانیم. برای این کار مطمئن شوید روی master هستید و سپس دستور زیر را اجرا کنید:
git reset --hard HEAD~1
قبلا در رابطه با 1~HEAD توضیح داده ام بنابراین توضیحات اضافه نمی دهم.
حالت مهم دیگری نیز وجود دارد. فرض کنید شاخه features از قبل وجود داشته باشد و اینطور نیست که بخواهیم شاخه جدیدی را ایجاد کنیم بلکه مشکل جا به جایی commit مورد نظر است. برای شبیه سازی این حالت من به شاخه master می روم و محتوای فایل web-dev.txt را به شکل زیر ویرایش می کنیم:
This is the web-dev text file. Web development is extremely fun! This should be on feature branch.
حالا با git add و git commit این تغییر را روی همین شاخه master ثبت می کنیم. در حال حاضر اگر git log را انجام بدهید چنین نتیجه ای را می گیرید:
commit 934c24437351c435e07fc5f0509c8cdd776f992a (HEAD -> master) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Thu Apr 29 11:14:07 2021 +0430 Edited web-dev.txt on master instead of feature branch commit 98967c7fecb50e3190382cb3d3bc509cb00bd0df Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Wed Apr 28 11:07:52 2021 +0430 misc.txt & web-dev.txt were both modified. commit b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:45:17 2021 +0430 Adding description to roxo.txt 🔥 commit 86372440309cc9b5b1e824a3d5a33a9cdc707934 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 10:17:36 2021 +0430 Initial Commit ✅
بنابراین تغییرات در master ثبت شده اند اما مشکل اینجاست که نباید این کار را می کردیم بلکه این تغییر باید در شاخه feature اتفاق می افتاد. چطور این commit را به شاخه feature ببریم؟ این شاخه از قبل وجود دارد و نمی خواهیم شاخه جدیدی بسازیم بنابراین از دستوری به نام cherry-pick استفاده می کنیم. اولین مرحله این است که به شاخه هدف بروید که در مثال ما feature است بنابراین:
git switch features
در گیت ابزاری به نام cherry-pick وجود دارد که به شما اجازه می دهد یک commit را کپی کرده و در شاخه دیگری قرار بدهید بنابراین:
git cherry-pick 934c24437351c435e07fc5f0509c8cdd776f992a
این همان هش commit مورد نظر من است. طبیعتا شما باید به جای هش بالا از هش commit خودتان استفاده کنید. سپس دوباره به master برمی گردیم:
git switch master
و نهایتا آن را به قبل از انجام commit مورد نظر ریست می کنیم:
git reset --hard 98967c7fecb50e3190382cb3d3bc509cb00bd0df
کارمان به همین سادگی تمام شده است.
یکی از قدرتمند ترین ابزار های گیت interactive rebase است که ابزاری همه کاره می باشد و می تواند انواع و اقسام تغییرات را در commit های مختلفی ایجاد کند. در این بخش و چند بخش آینده به سراغ انجام کار هایی می رویم که با interactive rebase انجام می شوند. از آنجایی که دستور rebase بسیار قدرتمند است باید در هنگام استفاده از آن احتیاط زیادی به خرج دهید در غیر این صورت ممکن است صدمات زیادی به پروژه بزنید.
شاید بگویید که ما قبلا این کار را با دستور amend انجام داده ایم اما در آن بخش فقط آخرین commit را ویرایش کرده بودیم. اگر بخواهیم commit های قبل تر از آن را ویرایش کنیم بحث چیز دیگری خواهد بود. در این حالت باید از interactive rebase استفاده کنیم. برای انجام این کار باید به یک سوال اصلی جواب بدهید: باید تا چند commit به عقب برگردیم؟ زمانی که از interactive rebase استفاده می کنیم در حال برگشت به عقب هستیم و طبیعتا میزان برگشت به عقب بسته به نیاز شما متفاوت است.
من در حال حاضر در شاخه feature هستم و commit های من به شکل زیر هستند:
commit 22e8ebac5ec580236bd9269a66e6938be8550b12 (HEAD -> features) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Thu Apr 29 11:14:07 2021 +0430 Edited web-dev.txt on master instead of feature branch commit 7108152ea72c5097da978ae90a8b2a41aa606c31 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Thu Apr 29 11:05:45 2021 +0430 A new feature was added in feature.txt! commit 98967c7fecb50e3190382cb3d3bc509cb00bd0df (master) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Wed Apr 28 11:07:52 2021 +0430 misc.txt & web-dev.txt were both modified. commit b0b637bc14b599a183d9ceb2d4118a2e6c80d8e1 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:45:17 2021 +0430 Adding description to roxo.txt 🔥 commit 86372440309cc9b5b1e824a3d5a33a9cdc707934 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 10:17:36 2021 +0430 Initial Commit ✅
من می خواهم commit ای که دارای هش b0b637bc14b599 است (اموجی آتش) را ویرایش کنم. اگر از آخرین commit شروع به شمردن کنید، باید چهار commit به عقب برگردیم (خود commit آخر در شمارش حساب می شود). برای انجام این کار دستور زیر را اجرا می کنیم:
git rebase -i HEAD~4
فلگ i- مخفف interactive است و به ما اجازه می دهد interactive rebase انجام بدهیم. با اجرای کد بالا ویرایشگر کد شما باز می شود که محتوای زیر را خواهد داشت:
pick b0b637b Adding description to roxo.txt 🔥 pick 98967c7 misc.txt & web-dev.txt were both modified. pick 7108152 A new feature was added in feature.txt! pick 22e8eba Edited web-dev.txt on master instead of feature branch # Rebase 8637244..22e8eba onto 22e8eba (4 commands) # # Commands: # p, pick <commit> = use commit # r, reword <commit> = use commit, but edit the commit message # e, edit <commit> = use commit, but stop for amending # s, squash <commit> = use commit, but meld into previous commit # f, fixup <commit> = like "squash", but discard this commit's log message # x, exec <command> = run command (the rest of the line) using shell # b, break = stop here (continue rebase later with 'git rebase --continue') # d, drop <commit> = remove commit # l, label <label> = label current HEAD with a name # t, reset <label> = reset HEAD to a label # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>] # . create a merge commit using the original merge commit's # . message (or the oneline, if no original merge commit was # . specified). Use -c <commit> to reword the commit message. # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # # However, if you remove everything, the rebase will be aborted. #
نکته اول درباره محتوای این فایل این است که ترتیب commit ها (سه خط اولی که کامنت نیستند) برعکس شده است یا به عبارتی commit های قدیمی تر در ابتدا قرار گرفته اند بنابراین حواستان باشد که در حال ویرایش چه چیزی هستید. نکته دوم لیست طولانی از دستورات مختلف rebase است که در قالب کامنت برایتان نمایش داده شده است. نکته بسیار مهمی در این باره وجود دارد: شما نباید تحت هیچ شرایطی شروع به تغییر پیام commit در commit های نمایش داده شده کنید. مثلا انجام این کار غلط است:
pick b0b637b Adding description to roxo.txt 🔥🔥🔥🔥🔥🔥 pick 98967c7 misc.txt & web-dev.txt were both modified. pick 7108152 A new feature was added in feature.txt! pick 22e8eba Edited web-dev.txt on master instead of feature branch
من چند اموجی آتش را به انتهای commit مورد نظرم اضافه کرده ام اما این کار اشتباه است. برای انجام عملیات های مختلف ابتدا باید ابتدا درک کنید که نحوه کار rebase چطور است؟ rebase چند commit را گرفته و آن ها را دوباره ثبت می کند. به همین دلیل است که ترتیب برعکس شده است (ابتدا باید commit های قدیمی را ثبت کرده و سپس به سراغ جدید ها برود). با این حساب اگر ترتیب این commit ها را تغییر بدهید واقعا ترتیب ثبت commit هایتان بهم خواهد خورد.
همچنین اگر به کنار commit ها توجه کنید، واژه pick را می بینید. pick یکی از دستوراتی است که که در کامنت ها برایتان توضیح داده شده است. من این دستورات را برایتان ترجمه می کنم:
با این حساب ما برای ویرایش پیام commit از دستور reword استفاده می کنیم:
reword b0b637b Adding description to roxo.txt 🔥 pick 98967c7 misc.txt & web-dev.txt were both modified. pick 7108152 A new feature was added in feature.txt! pick 22e8eba Edited web-dev.txt on master instead of feature branch // بقیه محتوای فایل
توجه کنید که من در اینجا pick را برای commit مورد نظرم به reword تغییر داده ام اما به هیچ چیز دیگری دست نزده ام. حواستان باشد که اشتباها پیام جدید را در این بخش تایپ نکنید، فقط pick را به reword تغییر داده و با کلید های Ctrl + S فایل را ذخیره کنید و آن را ببندید. با انجام این کار یک پنجره دیگر برایتان باز می شود که باید در آن پیام commit را بنویسید.
نکته: اگر از ویرایشگر gedit (لینوکس) استفاده می کنید ممکن است خطایی مانند There was a problem with the editor gedit --wait گرفته و پنجره جدید باز نشود. در این حالت به فایل پیکربندی خود مراجعه کنید (gitconfig.) و در قسمت ادیتور از آرگومان های gedit -w -s یا gedit --wait --new-window
استفاده نمایید. اگر مشکل رفع نشد از یک ویرایشگر دیگر مانند vscode استفاده کنید.
در این پنجره جدید چنین متنی را خواهید دید:
Adding description to roxo.txt 🔥 # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Date: Tue Apr 27 16:45:17 2021 +0430 # # interactive rebase in progress; onto 8637244 # Last command done (1 command done): # reword b0b637b Adding description to roxo.txt 🔥 # Next commands to do (3 remaining commands): # pick 98967c7 misc.txt & web-dev.txt were both modified. # pick 7108152 A new feature was added in feature.txt! # You are currently editing a commit while rebasing branch 'features' on '8637244'. # # Changes to be committed: # modified: roxo.txt #
من متن commit را به شکل زیر ویرایش می کنم:
Adding description to roxo.txt 🔥🔥🔥🔥
در نهایت فایل را ذخیره کرده و ببندید. برای مشاهده تغییرات دستور git log را اجرا کنید تا ببینید که پیام commit تغییر کرده است:
commit d397dada48ca46bfaf0a15582c65f146925575b6 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:45:17 2021 +0430 Adding description to roxo.txt 🔥🔥🔥🔥 // بقیه کامیت ها
برخی اوقات commit ای وجود دارد که از نظر ما اصلا نباید وجود می داشت. به مثال زیر توجه کنید:
Commit 1 -> Commit 2 -> Commit 3 -> Commit 4 -> Commit 5
فرض کنید پروژه ای بدین شکل داشته باشیم و بخواهیم یکی از این commit ها را حذف کنیم (مثلا commit سوم). در این حالت چه کار باید کرد؟ بله دوباره به سراغ دستور git rebase می رویم. همانطور که در بخش قبلی نیز توضیح دادم اولین کاری که باید در هنگام استفاده از rebase انجام بدهیم، این است که بدانیم چند مرحله به عقب برمی گردیم. در حال حاضر من روی شاخه feature هستم و اگر git log را اجرا کنم نتیجه ای مانند نتیجه زیر را دریافت می کنم:
commit 9b1535f52414b440265c6f048dac1e5585f39bde (HEAD -> features) Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Thu Apr 29 11:14:07 2021 +0430 Edited web-dev.txt on master instead of feature branch commit d07d0212dc43b16e6e3f0a1d4ee5ebd8aa8a7981 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Thu Apr 29 11:05:45 2021 +0430 A new feature was added in feature.txt! commit 5e1ff1c868564ca0d85cd6779f076bf97da55c0d Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Wed Apr 28 11:07:52 2021 +0430 misc.txt & web-dev.txt were both modified. commit d397dada48ca46bfaf0a15582c65f146925575b6 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 16:45:17 2021 +0430 Adding description to roxo.txt 🔥🔥🔥🔥 commit 86372440309cc9b5b1e824a3d5a33a9cdc707934 Author: AmirZM <1518971-Amir13@users.noreply.gitlab.com> Date: Tue Apr 27 10:17:36 2021 +0430 Initial Commit ✅
من می خواهم commit دوم (A new feature was added in feature.txt) را حذف کنم. برای انجام این کار باید دو مرحله به عقب برگردیم بنابراین دستور مورد نیاز ما بدین شکل است:
git rebase -i HEAD~2
با این کار ویرایشگر شما باز می شود و دو commit مورد نظر را در آن می بینید:
drop d07d021 A new feature was added in feature.txt! pick 9b1535f Edited web-dev.txt on master instead of feature branch
همانطور که در کد بالا مشخص است من به جای pick از دستور drop استفاده کرده ام که باعث حذف این commit می شود. تنها کاری که باید انجام بدهید ذخیره کردن فایل با Ctrl + S و سپس بستن ویرایشگرتان است. پس از انجام این کار یک بار دیگر دستور git log را اجرا کنید. آیا داده های شما حذف شده است؟ بله دیگر خبری از آن commit نیست! همچنین دیگر فایلی به نام feature وجود ندارد! به همین سادگی یک commit را حذف کرده ایم.
اگر به صورت حرفه ای از گیت استفاده می کنید حتما قانون مهم کار با آن را شنیده اید. همیشه commit هایتان را به صورت جزئی انجام بدهید یا به عبارتی زود به زود commit کنید. چرا؟ همه توسعه دهندگان تازه کار این تجربه را دارند که تصور می کنند commit های زیاد باعث شلوغ شدن گیت می شود اما این تمام ماجرا نیست. در صورتی که commit هایتان را دیر به دیر و با فواصل زیاد انجام بدهید، در صورت بروز اشتباه کارتان به شدت پیچیده می شود. تصور کنید یک پروژه API دارید و فقط پس از اضافه کردن یک قابلیت جدید commit می کنید. در این حالت اگر جایی به مشکل برخورد کنید نمی توانید به راحتی به عقب ریست کنید چرا که کل ویژگی (صد ها خط کد) حذف می شود بلکه مجبور خواهید شد همه چیز را به صورت دستی تصحیح کنید.
البته در برخی از اوقات این کار را از جهت اطمینان بیش از حد انجام می دهیم تا جایی که تاریخچه commit ها واقعا شلوغ می شود. در این حالت می توانیم قبل از push کردن به یک سرور گیت، commit هایمان را در هم ادغام کنید تا یک commit شوند. من ابتدا دستور git reflog را اجرا می کنم و سپس هش commit ای که در بخش قبل حذف کردیم را پیدا می کنم. چرا؟ به دلیل اینکه می خواهم فایل feature.txt را برگردانم بنابراین یک git reset را با استفاده از آن انجام می دهم. از آنجایی که قبلا چندین بار این کار را انجام داده ایم دیگر آن را توضیح نمی دهم. پس از بازگردانی این فایل، آن را باز کرده و محتوایش را به شکل زیر (یا هر شکلی که دوست دارید) ویرایش کنید:
This is a new feature in our project. I'm trying to squash two commits together.
در نهایت پس از git add با دستور زیر این تغییر را commit می کنم:
git commit -m "Edited feature.txt to be squashed with another commit"
طبیعتا این کار یک commit دیگر ایجاد می کند بنابراین تعداد ردیف های git log بیشتر می شود. حالا فرض کنید بخواهم دو commit آخرمان در این شاخه (feature) را در هم ادغام کنیم:
commit 7d7844cf2daadbd6d8b4dc4b1f8df2bcecacff08 (HEAD -> features) Author: MockingBird13 <6917991-MockingBird13@users.noreply.gitlab.com> Date: Fri Apr 30 14:11:33 2021 +0430 Edited feature.txt to be squashed with another commit commit 9b1535f52414b440265c6f048dac1e5585f39bde Author: MockingBird13 <6917991-MockingBird13@users.noreply.gitlab.com> Date: Thu Apr 29 11:14:07 2021 +0430 Edited web-dev.txt on master instead of feature branch // بقیه گزارش
طبیعتا شما می توانید هر دو commit دیگری را نیز انتخاب کنید و این مسئله سلیقه ای است. برای شروع عملیات ادغام باید دستور rebase را بدین شکل شروع کنیم:
git rebase -i HEAD~2
با این کار پنجره ویرایشگر شما باز شده و commit ها را نشان می دهد:
pick 9b1535f Edited web-dev.txt on master instead of feature branch pick 7d7844c Edited feature.txt to be squashed with another commit
ابزاری که به ما اجازه می دهد این دو را ادغام کنیم squash نام دارد. قانون استفاده از این ابزار این است که اگر squash را برای یک commit در نظر بگیرید، آن commit با commit قبلی خود ادغام می شود. با این حساب برای ترکیب دو commit بالا باید حتما دستور squash را به commit جدید تر بدهیم:
pick 9b1535f Edited web-dev.txt on master instead of feature branch squash 7d7844c Edited feature.txt to be squashed with another commit
همانطور که می بینید من squash را برای خط آخر (commit جدید تر) انتخاب کرده ام. پس از ذخیره ویرایشگر و بستن آن یک پنجره دیگر برایتان باز می شود که محتوای زیر را دارد:
# This is a combination of 2 commits. # This is the 1st commit message: Edited web-dev.txt on master instead of feature branch # This is the commit message #2: Edited feature.txt to be squashed with another commit # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Date: Thu Apr 29 11:14:07 2021 +0430 # # interactive rebase in progress; onto d07d021 # Last commands done (2 commands done): # pick 9b1535f Edited web-dev.txt on master instead of feature branch # squash 7d7844c Edited feature.txt to be squashed with another commit # No commands remaining. # You are currently rebasing branch 'features' on 'd07d021'. # # Changes to be committed: # modified: feature.txt # modified: web-dev.txt #
اگر بخش کامنت شده را مطالعه کنید، دقیقا متوجه می شوید که این پنجره چیست. زمانی که دو commit را ادغام می کنید، یک commit جدید از آن ها به وجود می آید و هر commit جدیدی نیاز به یک پیام commit دارد. حتما می دانید که بخش های کامنت شده در فایل بالا نادیده گرفته می شوند بنابراین پیام commit جدید به صورت عادی مجموعه ای از دو پیام commit های قبلی است. من دوست دارم آن را به شکل زیر ویرایش کنم:
# This is a combination of 2 commits. # This is the 1st commit message: Squashed Commit of: 1- Edited web-dev.txt on master instead of feature branch # This is the commit message #2: 2- Edited feature.txt to be squashed with another commit // ادامه محتوای فایل
با نوشتن عبارت Squashed Commit of به طور صریح مشخص کرده ام که این commit محصول ادغام دو commit دیگر با این پیام ها بوده است تا اگر در آینده نیازی به بررسی آن ها داشتم، تشخیص بدهم که کدام commit محصول ادغام بوده است. با ذخیره فایل بالا و بستن ویرایشگر کارتان تمام می شود. حالا اگر git log را اجرا کنید نتیجه زیر را می بینید:
commit 90fdcbe94caf8ed8ff6e86aa2016160055b9f4a4 (HEAD -> features) Author: MockingBird13 <6917991-MockingBird13@users.noreply.gitlab.com> Date: Thu Apr 29 11:14:07 2021 +0430 Squashed Commit of: 1- Edited web-dev.txt on master instead of feature branch 2- Edited feature.txt to be squashed with another commit commit d07d0212dc43b16e6e3f0a1d4ee5ebd8aa8a7981 Author: MockingBird13 <6917991-MockingBird13@users.noreply.gitlab.com> Date: Thu Apr 29 11:05:45 2021 +0430 A new feature was added in feature.txt! commit 5e1ff1c868564ca0d85cd6779f076bf97da55c0d Author: MockingBird13 <6917991-MockingBird13@users.noreply.gitlab.com> Date: Wed Apr 28 11:07:52 2021 +0430 misc.txt & web-dev.txt were both modified. commit d397dada48ca46bfaf0a15582c65f146925575b6 Author: MockingBird13 <6917991-MockingBird13@users.noreply.gitlab.com> Date: Tue Apr 27 16:45:17 2021 +0430 Adding description to roxo.txt 🔥🔥🔥🔥 commit 86372440309cc9b5b1e824a3d5a33a9cdc707934 Author: MockingBird13 <6917991-MockingBird13@users.noreply.gitlab.com> Date: Tue Apr 27 10:17:36 2021 +0430 Initial Commit ✅
هر دو commit ما تبدیل به یک commit واحد شده اند.
توجه داشته باشید که ما نمی خواهیم commit آخر را ویرایش کنیم یا نمی خواهیم یک commit جدید ساخته و آن را با commit های قدیمی ادغام کنیم. در برخی از اوقات پیش می آید که یادمان رفته است یکی از فایل ها را به commit اضافه کنیم و از آن رد شده ایم یا اینکه فایل اشتباهی را commit کرده ایم. معمولا اکثر توسعه دهندگان فایل را ویرایش کرده و یک commit جدید را ثبت می کنند. مشکل این روش اینجاست که این کدها در حال حاضر تصحیح شده اند اما آن commit اشتباه و پر از خطا هنوز در تاریخچه commit هایمان موجود است.
برای حل این مشکل ابزاری به نام fixup وجود دارد. فرض کنید بخواهیم یکی از commit هایمان را ویرایش کنیم. شما می توانید هر commit ای را انتخاب کنید اما من commit دوم را انتخاب می کنم که پیام زیر را دارد:
Adding description to roxo.txt 🔥🔥🔥🔥
هش این commit برای من d397dada48ca46bfaf0a15582c65f146925575b6 است. ما فرض می کنیم این commit دارای خطا بوده و باید اصلاح شود. برای حل این مشکل ابتدا به فایل مورد نظر (در این مثال roxo.txt) می رویم و محتوای آن را ویرایش می کنیم:
This is the roxo text file. Roxo is a great platform for learning programming and becoming an experienced web developer. You can visit us at roxo.ir/blog for tips at any time! This is for the --fixup part.
من در اینجا فقط یک خط ساده را اضافه کرده ام در حالی که در پروژه های واقعی کدها را ویرایش می کنیم اما به طور کلی مفهوم هر دو یکی است. حالا که این تغییر را ایجاد کرده ایم باید این فایل را به staging area اضافه کنیم:
git add roxo.txt
در مرحله بعدی باید هش commit مورد نظر (commit ای که باید اصلاح شود) را داشته باشیم که برای من d397dada48ca46bfaf0a15582c65f146925575b6 است. در نهایت commit جدید را با این هش انجام می دهیم:
git commit --fixup d397dada48ca46bfaf0a15582c65f146925575b6
پس از انجام این کار یک بار git log را اجرا کنید:
commit 6a4e1af58b76cdc158436530f6ea7de187ae505c (HEAD -> features) Author: MockingBird13 <6917991-MockingBird13@users.noreply.gitlab.com> Date: Fri Apr 30 14:56:26 2021 +0430 fixup! Adding description to roxo.txt 🔥🔥🔥🔥 commit 90fdcbe94caf8ed8ff6e86aa2016160055b9f4a4 Author: MockingBird13 <6917991-MockingBird13@users.noreply.gitlab.com> Date: Thu Apr 29 11:14:07 2021 +0430 Squashed Commit of: 1- Edited web-dev.txt on master instead of feature branch 2- Edited feature.txt to be squashed with another commit // بقیه گزارش
احتمالا شما هم متوجه شده اید که fixup دقیقا یک commit جدید را ایجاد کرده است اما قرار بود اینطور نشود! نگران نباشید هنوز در میانه راه هستیم. توجه داشته باشید که پیام commit جدید ما به صورت خودکار و با پیشوند fixup ثبت شده است. مرحله دوم این کار استفاده از rebase است:
git rebase -i HEAD~5 --autosquash
ما چندین بار از rebase استفاده کرده ایم بنابراین نحوه کار با آن را می دانید اما autosquash چیست؟ این فلگ به صورت خودکار fixup را با commit اصلی خود ادغام یا squash می کند. با اجرای دستور بالا ویرایشگر شما باز می شود و نتیجه زیر را می بینید:
pick d397dad Adding description to roxo.txt 🔥🔥🔥🔥 fixup 6a4e1af fixup! Adding description to roxo.txt 🔥🔥🔥🔥 pick 5e1ff1c misc.txt & web-dev.txt were both modified. pick d07d021 A new feature was added in feature.txt! pick 90fdcbe Squashed Commit of:
همانطور که می بینید خود git به صورت خودکار fixup را برای commit تصحیح شده در نظر گرفته است و من اصلا کاری نکرده ام. دقت کنید که گیت به صورت خودکار جای این commit ها را تغییر داده و در کنار هم گذاشته است! در واقع commit ای که هش d397dad را دارد چندین commit با fixup فاصله داشت اما گیت ترتیب آن ها را به صورت خودکار کنار هم قرار داده است. چرا؟ در بخش قبلی به شما گفتم که squash یک commit را گرفته و آن را با commit قبلی ادغام می کند. گیت این کار را به صورت خودکار برایمان انجام داده است بنابراین بدون دستکاری این فایل فقط آن را ببندید.
امیدوارم این مقاله به درک بهتر شما از گیت کمک کرده باشد. از یاد نبرید که دنیای گیت بسیار بزرگ است و هنوز مباحث زیادی برای یادگیری وجود دارد. نقش این مقاله آشنا کردن شما با انواع مهم تصحیح کد بود.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.