تفاوت git reset و git revert (و اینکه کِی سراغ کدام برویم)
آموزش

تفاوت git reset و git revert (و اینکه کِی سراغ کدام برویم)

یک کامیت اشتباه زده‌اید و دنبال راه برگشت هستید. قبل از اینکه دستور بعدی را کپی کنید، فقط یک چیز مهم است: آن تغییر push شده یا نه؟

تحریریه‌ی دینا کد
نویسنده
۶ دقیقه مطالعه۲۷ بازدید
اشتراک گذاری۲۷
فهرست مطالب
  1. ۱اول ببینید تغییر کجاست
  2. ۲هنوز کامیت نکرده‌اید: restore
  3. ۳کامیت کرده‌اید ولی push نکرده‌اید: reset
  4. ۴push کرده‌اید: فقط revert
  5. ۵اگر با reset --hard چیزی از دست دادید: reflog
  6. ۶کدام دستور، در کدام وضعیت

کامیتی زده‌اید که نباید می‌زدید. یا فایلی را پاک کرده‌اید، یا نصف کار نیمه‌کاره را با هم فرستاده‌اید. جست‌وجو می‌کنید و اولین چیزی که پیدا می‌شود git reset --hard است. همین دستور است که بیشترین کدِ از‌دست‌رفته‌ی دنیا روی وجدانش دارد.

قبل از هر دستوری، فقط یک سؤال مهم است: آن تغییر push شده یا نه؟ اگر نه، git reset کار شماست؛ اگر بله، فقط git revert. تفاوت این دو در همین یک چیز است: اولی تاریخچه را بازنویسی می‌کند، دومی رویش می‌نویسد.

اول ببینید تغییر کجاست

گیت سه جای متفاوت دارد که کار شما می‌تواند در آن باشد: فایل‌های روی دیسک (working tree)، ناحیه‌ی staging، و کامیت. git status هر سه را نشان می‌دهد و لازم است قبل از هر کاری یک بار بخوانیدش. ابزارِ برگرداندن، بر اساس همین موقعیت انتخاب می‌شود، نه بر اساس اینکه کدام دستور را بلدید. برای دیدن خودِ خط‌های عوض‌شده، ابزارِ این کار git diff است؛ در diff در گیت چیست و چه‌وقت استفاده می‌شود خروجی‌اش را با مثال واقعی توضیح داده‌ایم.

هنوز کامیت نکرده‌اید: restore

bash# فایل را به آخرین نسخه‌ی کامیت‌شده برگردان
# هرچه ذخیره نشده از بین می‌رود و برنمی‌گردد
git restore src/app.py

# فقط از staging بیرونش بیاور؛ خود تغییرها دست‌نخورده می‌مانند
git restore --staged src/app.py

اگر جایی git checkout -- file دیده‌اید، همین کار را می‌کند؛ restore از نسخه‌ی ۲.۲۳ گیت اضافه شد تا این کار با «جابه‌جا شدن بین شاخه‌ها» قاطی نشود. سراغ فرم قدیمی نروید.

اگر نمی‌خواهید تغییرها را از دست بدهید ولی الان مزاحمتان هستند، git stash دقیقاً برای همین است.

کامیت کرده‌اید ولی push نکرده‌اید: reset

اینجا تاریخچه فقط مال خودتان است، پس بازنویسی‌اش هیچ ایرادی ندارد:

bash# کامیت آخر را باز کن، تغییرها را staged نگه دار
# برای وقتی که می‌خواهید همان کار را دوباره و درست کامیت کنید
git reset --soft HEAD~1

# کامیت را باز کن، تغییرها را در فایل‌ها نگه دار (حالت پیش‌فرض)
git reset HEAD~1

# کامیت و تغییرهایش را با هم پاک کن
git reset --hard HEAD~1

تفاوت این سه فقط در این است که کارتان تا کجا عقب می‌آید: --soft تا staging، پیش‌فرض تا فایل‌ها، --hard تا هیچ‌جا.

و خیلی وقت‌ها به هیچ‌کدام نیاز ندارید. اگر مشکل فقط متن پیام کامیت یا یک فایل جاافتاده است:

bashgit add فایل_جاافتاده.py
git commit --amend

push کرده‌اید: فقط revert

revert تاریخچه را دست نمی‌زند؛ یک کامیت جدید می‌سازد که اثر کامیت قبلی را برمی‌گرداند:

bashgit revert 4f2a9c1

# چند کامیت پشت سر هم را با هم برگردان
# بدون علامت ^، خودِ 4f2a9c1 برگردانده نمی‌شود
git revert --no-commit 4f2a9c1^..HEAD
git commit -m "برگرداندن تغییرهای ناقص فرم ثبت‌نام"

آن ^ اشتباه رایجی است که مردم را گیج می‌کند: در گیت، بازه‌ی A..B یعنی «هرچه در B هست و در A نیست»، پس خودِ A بیرون از بازه می‌ماند. اگر می‌خواهید همان کامیت هم برگردد، A^ را شروع بازه بگذارید.

روی شاخه‌ی مشترک، reset --hard و بعد push --force نزنید. تاریخچه‌ای که بازنویسی می‌کنید مال شما نیست. همکارتان همان کامیت‌ها را دارد؛ در git pull بعدی با تاریخچه‌ای مواجه می‌شود که دیگر وجود ندارد و کارش را باید روی آن دوباره بسازد. اگر روزی واقعاً مجبور شدید تاریخچه‌ی مشترک را عوض کنید، حداقل --force-with-lease بزنید تا اگر کسی چیزی push کرده باشد، دستور شما رد شود.

اگر با reset --hard چیزی از دست دادید: reflog

هر جابه‌جایی HEAD در reflog ثبت می‌شود، حتی وقتی کامیت از تاریخچه بیرون افتاده:

bashgit reflog
# a3c1f8e HEAD@{0}: reset: moving to HEAD~1
# 4f2a9c1 HEAD@{1}: commit: افزودن اعتبارسنجی فرم

git reset --hard 4f2a9c1   # برگشت به همان نقطه

صادقانه بگویم که reflog فقط چیزی را نجات می‌دهد که کامیت شده باشد.

ولی یک شانس دوم هم هست. هر چیزی که حتی یک بار git add شده، به‌عنوان یک شیء در دیتابیس گیت نوشته شده و با --hard هم پاک نمی‌شود:

bashgit fsck --lost-found
# dangling blob fd783bc2146f42436a411d0d2bf274234a7697d3

git cat-file -p fd783bc   # محتوای همان فایل، دست‌نخورده

اگر تغییری هیچ‌وقت add نشده باشد، این هم کمکی نمی‌کند. تفاوتِ «قابل بازیابی» و «رفت» دقیقاً همان یک بار git add است.

کدام دستور، در کدام وضعیت

وضعیت دستور
تغییر ذخیره‌نشده در فایل git restore <file>
فقط از staging دربیاید git restore --staged <file>
پیام یا محتوای کامیت آخر (push‌نشده) git commit --amend
کامیت push‌نشده git reset --soft HEAD~1
کامیت push‌شده git revert <hash>
اشتباهاً پاکش کردم git reflog

یک عادت هم ارزشش را دارد؛ قبل از هر آزمایش خطرناکی، یک شاخه‌ی پشتیبان بسازید. یک اشاره‌گر ساده است، هیچ فایلی کپی نمی‌شود و تفاوتش با یک شب کارِ از‌دست‌رفته همین یک خط است:

bashgit branch backup-before-reset

اگر می‌خواهید گیت را از پایه و با تمرین عملی یاد بگیرید، دوره‌ی گیت و گیت‌هاب همین مسیر را قدم‌به‌قدم می‌رود؛ از شاخه‌سازی و merge تا حل تعارض و کار تیمی روی گیت‌هاب.

برچسب‌ها:#گیت#مبتدی
تحریریه‌ی دینا کد
نویسنده

نظرها

اولین نفری باشید که نظر می‌دهد
برای ثبت نظر وارد شوید

سؤالتان را بپرسید یا تجربه‌تان را بنویسید تا نویسنده‌ی مطلب پاسخ بدهد.

مطالب مرتبط

از همین دسته
دستور stash در گیت چیست و چه کاربردی دارد
آموزش

دستور stash در گیت چیست و چه کاربردی دارد

دستور stash در گیت تغییرات ثبت‌نشده شاخه کاری را موقتا کنار می‌گذارد تا بدون ثبت کامیت ناقص بتوانید روی وظایف فوری کار کنید.

تحریریه‌ی دینا کد۶ دقیقه
diff در گیت چیست و چه‌وقت استفاده می‌شود؟
آموزش

diff در گیت چیست و چه‌وقت استفاده می‌شود؟

git diff دو نسخه از کدها را کنار هم می‌گذارد و خط‌به‌خط نشان می‌دهد چه چیزی عوض شده. یک نگاه به diff قبل از هر add و commit کافی است

تحریریه‌ی دینا کد۷ دقیقه