~/icsd.ir — bash
SYSTEM_ONLINE

اصلاح اشتباهات – reset، revert، reflog

همه ما اشتباه می‌کنیم - فایل اشتباه commit کردیم، پیام را اشتباه نوشتیم، یا یک commit بد ساختیم. خوشبختانه Git ابزارهای قدرتمندی برای اصلاح دارد: reset، revert، reflog، checkout و cherry-pick.

همه ما اشتباه می‌کنیم – فایل اشتباه commit کردیم، پیام را اشتباه نوشتیم، یا یک commit بد ساختیم. خوشبختانه Git ابزارهای قدرتمندی برای اصلاح دارد: reset، revert، reflog، checkout و cherry-pick.

دسته‌بندی اصلاح‌ها

وضعیت تغییر دستور
تغییر uncommitted در Working Directory git restore
تغییر staged ولی هنوز commit نشده git restore --staged
commit شده ولی push نشده git reset یا git commit --amend
commit شده و push شده git revert (امن) یا git push --force (خطرناک)
کلاً پروژه را خراب کردم! git reflog + git reset

git restore – برگرداندن فایل‌ها

برگرداندن تغییرات Working Directory

# تغییرات یک فایل را به آخرین commit برگردان
git restore file.py

# همه فایل‌ها
git restore .

# فقط زیرپوشه خاص
git restore src/

# دستور قدیمی (هنوز کار می‌کند)
git checkout -- file.py
هشدار: این تغییرات برگشت‌ناپذیر است! اگر فایلی را restore کنید، تغییرات uncommitted از بین می‌روند.

Unstage کردن (خارج کردن از staging)

# فایلی را که add کرده‌اید، از staging خارج کنید
git restore --staged file.py

# همه را
git restore --staged .

# دستور قدیمی
git reset HEAD file.py

برگرداندن به یک commit خاص

# برگرداندن file.py به یک commit قدیمی
git restore --source=a3f4b2c file.py

# یا
git checkout a3f4b2c -- file.py

git commit –amend – تصحیح آخرین commit

تغییر پیام آخرین commit

# Editor باز می‌شود
git commit --amend

# یا مستقیم
git commit --amend -m "پیام جدید"

اضافه کردن فایل فراموش‌شده

# اوه، forgotten.py را add نکردم
git add forgotten.py
git commit --amend --no-edit             # بدون تغییر پیام

تغییر نویسنده

git commit --amend --author="Real Name <real@email.com>"
هشدار: اگر آن commit را push کرده‌اید، amend مشکل‌ساز است. چون commit جدید hash متفاوت دارد، باید force-push کنید.

git reset – بازنویسی تاریخچه

سه نوع reset وجود دارد:

نوع HEAD Staging Working Dir کاربرد
--soft عقب می‌رود دست‌نخورده دست‌نخورده commit دوباره با تغییرات
--mixed (پیش‌فرض) عقب می‌رود reset دست‌نخورده تغییرات می‌مانند ولی unstaged
--hard عقب می‌رود reset پاک می‌شود! پاک کردن کامل (خطرناک)

git reset –soft

# آخرین commit را undo کن، اما تغییرات را در staging نگه دار
git reset --soft HEAD~1

# مفید: می‌خواهید commit را دوباره بسازید با پیام/فایل‌های متفاوت
git commit -m "پیام جدید"

git reset –mixed (یا فقط reset)

# آخرین commit را undo، تغییرات unstaged می‌شوند
git reset HEAD~1
git reset --mixed HEAD~1               # معادل

# حالا git status تغییرات را modified نشان می‌دهد
git add .                              # دوباره stage
git commit -m "پیام بهتر"

git reset –hard

# خطرناک! همه چیز را به آخرین commit (یا commit مشخص) برگردان
git reset --hard HEAD~1                # یک commit عقب
git reset --hard a3f4b2c               # به commit خاص

# تغییرات uncommitted از دست می‌روند!
# تغییرات commit شده اخیر هم از دست می‌روند!
اخطار: --hard فایل‌های uncommitted را پاک می‌کند بدون امکان بازگشت. قبلش حتماً git status بزنید.

سناریو: لغو چند commit

# تاریخچه:
# C1 ── C2 ── C3 ── C4 (HEAD)

# می‌خواهیم به C2 برگردیم
git reset --hard HEAD~2                # 2 commit عقب
# C1 ── C2 (HEAD)
# C3 و C4 ناپدید شدند (تا زمانی که garbage collected شوند)

git revert – لغو امن

برخلاف reset که تاریخچه را بازنویسی می‌کند، revert یک commit جدید می‌سازد که تغییرات یک commit قبلی را لغو می‌کند.

# commit C3 را undo کن (یعنی commit جدید بساز که برعکس C3 است)
git revert C3-hash

# تاریخچه:
# C1 ── C2 ── C3 ── C4 ── C5_revert_of_C3 (HEAD)
# تاریخچه حفظ می‌شود، اما تغییرات C3 خنثی شده

این روش امن برای commit‌های push شده است:

# ساده
git revert HEAD                        # آخرین commit را revert کن
git revert HEAD~3                      # سه commit قبل

# revert چند commit
git revert HEAD~3..HEAD                # سه commit آخر

# بدون open شدن editor (پیش‌فرض پیام را قبول کن)
git revert --no-edit HEAD

# revert بدون commit (در staging بماند)
git revert -n HEAD

revert vs reset

قبل:    A ── B ── C ── D ←── main

reset --hard B:
        A ── B ←── main
        (C و D حذف شدند)

revert C:
        A ── B ── C ── D ── E (revert C) ←── main
        (تاریخچه حفظ، تغییر C خنثی)

git reflog – نجات‌دهنده!

Git یک log داخلی از همه حرکات HEAD نگه می‌دارد. حتی اگر reset --hard زده باشید، می‌توانید پیدا کنید!

git reflog

# a3f4b2c HEAD@{0}: commit: Add feature
# b2c3d4e HEAD@{1}: reset: moving to HEAD~2
# c4d5e6f HEAD@{2}: commit: Important work !!!
# d5e6f7a HEAD@{3}: commit: Yesterday's work

سناریو: اشتباهاً hard reset زدم

# کارهای امروز را با reset --hard پاک کردم!
git reflog
# می‌بینم HEAD@{2} commit "Important work !!!" بود

# بازگشت
git reset --hard HEAD@{2}
# یا
git reset --hard c4d5e6f                # hash از reflog

reflog یک branch خاص

git reflog show feature-branch
نکته: reflog فقط محلی است. اگر روی سیستم دیگر کلون کرده‌اید، reflog آنجا نیست. همچنین reflog بعد از ~۹۰ روز garbage collected می‌شود.

git checkout – بازگشت به نسخه قدیم

دیدن یک فایل در نسخه قدیم

# فقط نمایش، بدون تغییر
git show a3f4b2c:src/main.py

# گرفتن نسخه قدیم به working directory
git checkout a3f4b2c -- src/main.py
# حالا main.py نسخه a3f4b2c است

# سپس commit کنید
git add src/main.py
git commit -m "Restore old version of main.py"

Detached HEAD – رفتن به یک commit قدیم

git checkout a3f4b2c

# You are in 'detached HEAD' state...
# می‌توانید نگاه کنید، تست کنید
# اما commit‌های جدید به branch تعلق ندارند

# برای بازگشت
git switch main

ساخت branch از یک commit قدیم

# شاید بخواهید از یک commit قدیم branch جدید بسازید
git checkout -b old-version a3f4b2c
# یا با switch
git switch -c old-version a3f4b2c

بازیابی Branch حذف‌شده

# branch را اشتباهاً حذف کردید
git branch -D feature-x

# پیدا کردن آخرین commit branch در reflog
git reflog
# a3f4b2c HEAD@{5}: checkout: moving from feature-x to main

# ساخت دوباره branch
git branch feature-x a3f4b2c
# یا
git switch -c feature-x a3f4b2c

git cherry-pick – دوباره

یادآوری: cherry-pick یک commit خاص از branch دیگر را به branch فعلی منتقل می‌کند:

# من یک bug fix در branch feature دارم که الان در main لازم است
git switch main
git cherry-pick a3f4b2c

# Conflict؟
git status                            # ببین کدام فایل
# حل کن
git add resolved-file.py
git cherry-pick --continue

# لغو
git cherry-pick --abort

cherry-pick بدون commit

# فقط تغییرات را stage کن، خودت commit بساز
git cherry-pick -n a3f4b2c

# حالا می‌توانید تغییرات را ویرایش کنید
git add modified.py
git commit -m "Cherry-pick with modifications"

بازیابی فایل پاک‌شده

# اگر هنوز commit نکردید
git restore deleted-file.py

# اگر commit شده اما هنوز در تاریخچه است
git log -- deleted-file.py            # پیدا کردن آخرین commit
git checkout HEAD~1 -- deleted-file.py
# یا
git checkout a3f4b2c -- deleted-file.py

git clean – حذف فایل‌های untracked

# Dry run - فقط نشان بده چه چیزی حذف خواهد شد
git clean -n

# حذف فایل‌های untracked (نه پوشه‌ها)
git clean -f

# حذف فایل‌ها و پوشه‌ها
git clean -fd

# حذف فایل‌های untracked + ignored (مثل node_modules)
git clean -fdx

# با تأیید interactive
git clean -fi
هشدار: git clean فایل‌ها را برای همیشه پاک می‌کند. اول -n بزنید.

حذف فایل از کل تاریخچه

سناریو: secret یا فایل بزرگی را اشتباهاً push کرده‌اید و باید از کل تاریخچه حذف شود.

روش ۱: BFG Repo-Cleaner (پیشنهاد – سریع‌تر)

# دانلود از https://rtyley.github.io/bfg-repo-cleaner/

# clone با --mirror
git clone --mirror git@github.com:USER/repo.git
cd repo.git

# حذف یک فایل
java -jar bfg.jar --delete-files "secret.env" .

# حذف فایل‌های > 100MB
java -jar bfg.jar --strip-blobs-bigger-than 100M .

# پاکسازی
git reflog expire --expire=now --all
git gc --prune=now --aggressive

# push (همه را force می‌کند)
git push

روش ۲: git filter-branch (داخلی Git، اما کند)

git filter-branch --force --index-filter 
  "git rm --cached --ignore-unmatch secret.env" 
  --prune-empty --tag-name-filter cat -- --all

git push origin --force --all
هشدار مهم: اگر secret push شده، حذف از تاریخچه کافی نیست! آن secret را rotate کنید (تغییر دهید) چون احتمالاً قبلاً جایی کش شده.

git bisect – یافتن commit معیوب

سناریو: کد در یک نقطه کار می‌کرد، الان نمی‌کند. نمی‌دانیم کدام commit آن را خراب کرده. bisect با binary search کمک می‌کند:

# شروع
git bisect start

# commit فعلی bad است
git bisect bad

# commit قدیمی (که می‌دانیم کار می‌کرد) good است
git bisect good a3f4b2c

# Git نصف می‌کند و به یک commit می‌رود
# Bisecting: 50 revisions left to test after this (roughly 6 steps)

# تست کنید (اجرا، چک)
# اگر کار می‌کند:
git bisect good

# اگر خراب است:
git bisect bad

# Git ادامه می‌دهد تا commit مقصر را پیدا کند

# پایان
git bisect reset

اتوماسیون bisect

# اگر اسکریپت تست داریم
git bisect start HEAD a3f4b2c
git bisect run npm test

# Git خودکار بین commit‌ها bisect می‌کند تا جایی که تست fail شود

git fsck – بررسی سلامت

# بررسی integrity
git fsck

# پیدا کردن object‌های "گمشده" (مثلاً بعد از reset --hard)
git fsck --lost-found

# نتیجه: dangling commit‌ها
# این‌ها commit‌هایی هستند که هیچ branch به آن‌ها اشاره نمی‌کند
# می‌توانید آن‌ها را با reset بازیابی کنید

سناریوهای عملی

۱. اشتباهاً به branch اشتباه commit کردم

# روی main کار کردم، باید روی feature می‌کردم
git log --oneline                    # دیدن commit
git reset HEAD~1                     # undo (تغییرات می‌مانند)

git switch -c feature                # یا برو به feature موجود
git add .
git commit -m "..."

۲. پیام آخرین commit اشتباه است

git commit --amend -m "پیام درست"

۳. یک فایل را اشتباهاً پاک کردم

git restore deleted-file.py

۴. فقط تغییرات یک فایل را undo کنم

git restore file.py
# فقط این فایل به آخرین commit برمی‌گردد

۵. اشتباهاً commit کردم با فایل بزرگ

# اگر هنوز push نکرده‌ام
git reset HEAD~1
# .gitignore را آپدیت کن
echo "huge-file.zip" >> .gitignore
git add .gitignore
git commit -m "..."

# اگر push کرده‌ام، با BFG/filter-branch

۶. branch دیگر merge کردم اشتباه

git reset --hard HEAD~1              # اگر هنوز push نشده
# یا
git revert -m 1 a3f4b2c              # اگر push شده، revert با parent 1

۷. همه چیز را خراب کردم!

git reflog                            # پیدا کن آخرین حالت سالم
git reset --hard HEAD@{N}             # برگرد

بهترین شیوه‌ها

  • قبل از reset --hard، حتماً git status و git stash
  • برای commit‌های push شده، همیشه revert به‌جای reset
  • force-push را با --force-with-lease امن‌تر کنید
  • reflog دوست شماست – قبل از panic، یک git reflog بزنید
  • secret که اشتباهاً push شد را rotate کنید (نه فقط حذف از تاریخچه)
  • push روزانه = backup روزانه
  • amend فقط برای commit‌های local

جمع‌بندی

  • git restore: برگرداندن فایل‌ها
  • git commit --amend: تصحیح آخرین commit
  • git reset: بازنویسی تاریخچه (soft/mixed/hard)
  • git revert: لغو امن با commit جدید
  • git reflog: log همه حرکات HEAD – نجات‌دهنده!
  • git checkout: نگاه به نسخه قدیمی
  • git cherry-pick: انتخاب یک commit
  • git clean: حذف untracked
  • git bisect: پیدا کردن commit معیوب با binary search
  • BFG برای حذف از تاریخچه کامل

در فصل بعد، استراتژی‌های branching سازمانی – Git Flow، GitHub Flow، Trunk-Based Development.

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.