git reset، revert و restore: سه راه اصلاح بدون سردرگمی
کی soft/mixed/hard reset بزنید، کی revert برای تاریخچهٔ مشترک، و کی restore برای فایلها — با جدول تصمیم و اشتباههای خطرناک.
Founder & product engineer

«اشتباه commit کردم» سه مشکل متفاوت است: گاهی فقط staging را زیاد پر کردهاید، گاهی آخرین commit محلی بد است، و گاهی commit بد از دیروز روی main مشترک نشسته. یک دستور برای هر سه وجود ندارد. reset، revert و restore هر کدام لایهٔ متفاوتی از وضعیت Git را لمس میکنند؛ قاطی کردنشان علت رایج از دست رفتن کار یا خشم تیم است.
این مقاله معیار انتخاب میدهد: آیا تغییر فقط روی ماشین شماست؟ آیا قبلاً push شده؟ آیا میخواهید فایل برگردد یا تاریخچه عوض شود؟

پاسخ کوتاه
برای برگرداندن محتوای فایل در working tree/index بدون بازنویسی تاریخچهٔ منتشرشده: `git restore`. برای جابهجا کردن شاخهٔ فعلی و اختیاریاً پاک کردن commitهای محلی هنوزpushنشده: `git reset` (با فهم soft/mixed/hard). برای خنثی کردن اثر یک commit که دیگران ممکن است کشیده باشند: `git revert` که commit جدید معکوس میسازد. روی شاخهٔ مشترک، reset --hard + force push را پیشفرض ندانید.
revert تاریخچه را درست میکند؛ reset اغلب تاریخچه را جابهجا میکند.
سه لایهٔ وضعیت را دوباره ببینید
- Working tree: فایلهایی که در ادیتور میبینید.
- Index / staging: آنچه برای commit بعدی آماده است.
- Repository / HEAD: آخرین commit شاخهٔ جاری.
restore بیشتر با دو لایهٔ اول کار دارد. reset میتواند هر سه را جابهجا کند. revert لایهٔ سوم را با commit جدید گسترش میدهد.
git restore: اصلاح فایل، نه بازنویسی داستان
restore برای سناریوهایی مثل «این فایل را خراب کردم، نسخهٔ HEAD را میخواهم» یا «از staging بیرونش کن» است.
bash
git restore path/to/file.py git restore --staged path/to/file.py git restore --source=HEAD~1 path/to/file.py git restore --worktree --source=main -- path/to/file.py
بدون --staged معمولاً working tree را از index (یا منبع مشخص) بازیابی میکند. با --staged، فایل را از index به وضعیت مشخص برمیگرداند (unstage رایج). این دستور جایگزین بخشهایی از checkout قدیمی برای مسیرهاست و خوانایی بهتری دارد.
restore تاریخچهٔ remote را عوض نمیکند؛ امنترین ابزار روزمره برای پشیمانی از ویرایش محلی است. توجه: تغییرات uncommitted ذخیرهنشده ممکن است از بین بروند — قبل از restore سخت، وضعیت را ببینید یا stash کنید.
git reset: جابهجایی HEAD و اختیاریاً پاکسازی
reset اشارهگر شاخهٔ فعلی را به یک commit دیگر میبرد و بسته به حالت، index و working tree را هم همتراز میکند.
سه حالت کلاسیک
| حالت | HEAD | Index | Working tree | کاربرد رایج |
|---|---|---|---|---|
| --soft | جابهجا | دستنخورده (نسبت به قبل از جابهجایی محتوا حفظ میشود بهشکل staged) | دستنخورده | جمع کردن commitها با نگه داشتن تغییرات |
| --mixed (پیشفرض) | جابهجا | مثل هدف | دستنخورده | خارج کردن commit ولی نگه فایلها unstaged |
| --hard | جابهجا | مثل هدف | مثل هدف | دور ریختن کامل تغییرات تا آن نقطه |
bash
git log --oneline -5 git reset --soft HEAD~1 git reset HEAD~1 git reset --hard HEAD~1
مثال: سه commit محلی شلوغ دارید که هنوز push نشده؛ میخواهید یک commit تمیز بسازید:
bash
git reset --soft HEAD~3 git status git commit -m "feat: کامل کردن ثبتنام"
خطر reset روی تاریخچهٔ مشترک
اگر commitها را دیگران clone/pull کرده باشند، reset آنها را از شاخهٔ شما «حذفشده از نوک شاخه» نشان میدهد. برای بهروز کردن remote معمولاً force push لازم میشود و کلونهای دیگران واگرا میشوند. روی main/master تیم این کار باید استثنا باشد، با هماهنگی، نه عادت.
git revert: خنثیسازی با commit جدید
revert اثر یک commit مشخص را با ساختن commit معکوس از بین میبرد. تاریخچه خطیتر و برای همکاری امنتر میماند چون گذشته پاک نمیشود؛ فقط گفته میشود «آن تغییر را برگرداندیم».
bash
git log --oneline -10 git revert <commit-sha> # برای merge commit گاهی نیاز به -m است git revert -m 1 <merge-sha>
اگر conflict رخ داد، مثل merge حلش کنید و `git revert --continue` بزنید، یا با `--abort` منصرف شوید. برای چند commit میتوانید بازه revert کنید، ولی ترتیب و تعارضها را جدی بگیرید.
مزیت سازمانی: در audit و code review مشخص است چه چیزی چرا برگشته. عیب: تاریخچه طولانیتر میشود و برای «پاک کردن secret از تاریخچه» کافی نیست — آن مسئله مسیر جداگانه دارد.
جدول تصمیم سریع
| هدف | ابزار |
|---|---|
| unstage یک فایل | git restore --staged |
| دور ریختن ویرایش محلی یک فایل | git restore |
| عوض کردن آخرین commit محلی هنوز push نشده (پیام/محتوا) | amend یا reset --soft و recommit |
| جمع کردن چند commit محلی | reset --soft |
| خنثی کردن commit روی main مشترک | revert |
| پاک کردن کامل کار محلی تا HEAD | reset --hard (با احتیاط) یا restore گسترده |
سناریوهای واقعی
۱) فایل اشتباه stage شده
bash
git restore --staged secrets.env # سپس به .gitignore اضافه کنید؛ commit نکنید
۲) آخرین commit محلی پیام بد دارد و push نشده
bash
git commit --amend -m "fix: جلوگیری از NPE در checkout"
اگر فقط پیام است و محتوا درست، amend کافی است. اگر محتوا هم باید عوض شود، تغییرات را اعمال و دوباره amend کنید. روی commit pushشده amend+force فقط با سیاست تیم.
۳) باگ از commit دیروز روی main
bash
git switch main git pull git revert <bad-sha> git push
۴) آزمایش محلی را کامل دور بریزید
bash
git status git reset --hard HEAD git clean -fd # فایلهای untracked؛ دوبار فکر کنید
clean مخرب است؛ ابتدا `git clean -fdn` برای dry-run.
reset و reflog: آخرین کمربند ایمنی
اگر hard reset اشتباه زدید، اغلب هنوز میتوانید با reflog commit قبلی را پیدا کنید — تا قبل از prune/gc.
bash
git reflog git reset --hard HEAD@{1}
این جادو نیست؛ پشتیبان remote و شاخهٔ محافظتشده همچنان مهماند.
اشتباههای رایج
- reset --hard بهجای restore برای یک فایل.
- revert نکردن روی مشترک و بهجایش history rewrite بدون هماهنگی.
- فراموش اینکه soft تغییرات را نگه میدارد؛ تصور «حذف شدند».
- force push به main بدون branch protection و بدون اطلاع تیم.
- استفاده از reset برای پاک کردن secret و تصور که از GitHub محو شده.
چه وقت از کدام در CI/فرآیند تیم استفاده کنید
در PR، «برگشت تغییر» معمولاً با commit جدید یا revert روی main بعد از merge است، نه reset تاریخچهٔ remote. ابزارهای branch protection اغلب force push به شاخهٔ پیشفرض را میبندند تا همین اشتباه ساختاری کمتر رخ دهد.
خلاصه
restore برای فایل و staging است، reset برای جابهجایی نوک شاخهٔ محلی (با سه سطح شدت)، و revert برای خنثیسازی امن روی تاریخچهٔ مشترک. قبل از هر دستور مخرب بپرسید: آیا کسی این commit را دارد؟ آیا فقط محتوا را میخواهم یا داستان تاریخچه را؟
سوالات متداول
آیا reset --mixed فایلهایم را حذف میکند؟
خیر؛ تغییرات معمولاً در working tree میمانند ولی از commit/index جدا میشوند. hard است که درخت را هم همتراز commit هدف میکند.
revert همان undo است؟
از نظر اثر محتوایی اغلب بله؛ از نظر تاریخچه خیر — commit جدید اضافه میکند.
چرا restore دارم اگر checkout هست؟
checkout هم مسیر و هم شاخه را جابهجا میکرد و گیجکننده بود؛ restore و switch مسئولیت را جدا کردند.
منابع و مراجع
- Git — git-reset: https://git-scm.com/docs/git-reset
- Git — git-revert: https://git-scm.com/docs/git-revert
- Git — git-restore: https://git-scm.com/docs/git-restore
- Git — git-reflog: https://git-scm.com/docs/git-reflog
- Pro Git — Reset Demystified: https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified
Author
Soheil Ebrahimpour is the founder of FutureForge. He works on product design, architecture, and getting custom software into production.
Related notes
If you are unsure about architecture or the build path, we can talk about the project.
Describe the problem and the constraints. If there is a fit, we will schedule a conversation.




