توی این درس یاد میگیری اشتباهها را امن برگردانی. ابزار درست به این بستگی دارد که اشتباه کجاست: در پوشهی کاری (git restore)، در staging (git restore --staged)، در آخرین commit (git commit --amend)، یا در یک commit قدیمیتر (git revert و git reset با سه حالت --soft، --mixed، --hard). مهمتر از همه میفهمی کدام برای تاریخچهی مشترک امن است (revert) و کدام فقط برای کار خصوصی خودت (reset، amend). تمرین: سه نوع اشتباه بساز و هر کدام را با روش درست برگردان.
مسئله: اشتباه کجا اتفاق افتاده؟
Section titled “مسئله: اشتباه کجا اتفاق افتاده؟”هر کسی اشتباه میکند: فایلی را خراب ویرایش کردی، چیزی را بهاشتباه add کردی، پیام commit را غلط نوشتی، یا یک commit کامل را نباید میزدی. Git برای هر کدام ابزار جدا دارد، و ابزار اشتباه میتواند کار را برای همیشه پاک کند. پس قبل از دستبردن به دستور، دو سؤال بپرس: اشتباه کجاست؟ (پوشهی کاری، staging، یا commit) و آیا کسی دیگر آن را دیده؟ (آیا push شده؟)
| اشتباه | ابزار | امن؟ |
|---|---|---|
| فایل را خراب ویرایش کردم (commit نشده) | git restore فایل |
⚠ تغییرها برای همیشه از بین میروند |
چیزی را بیجا add کردم |
git restore --staged فایل |
✓ تغییرها در پوشهی کاری میمانند |
| پیام یا محتوای آخرین commit غلط است (push نشده) | git commit --amend |
✓ فقط اگر push نشده |
| چند commit خصوصی را میخواهم پس بگیرم | git reset |
⚠ تاریخچه را بازنویسی میکند |
| commit ای که به اشتراک گذاشته شده باید خنثی شود | git revert |
✓ همیشه امن (commit جدید میسازد) |
تشبیه: دفتر حسابداری
Section titled “تشبیه: دفتر حسابداری”یک حسابدار حرفهای هیچوقت صفحهی ثبتشدهی دفتر را خط نمیزند؛ یک سند اصلاحی مینویسد (آن revert). ولی پیشنویسهای روی میز خودش را که هنوز وارد دفتر نشده، مجاز است پاره کند (restore و reset) یا آخرین سند را قبل از امضای نهایی ویرایش کند (amend). قاعده: هر چه در دفتر مشترک است، فقط با سند اصلاحی. هر چه خصوصی است، آزادانه.
مثالهای عملی
Section titled “مثالهای عملی”یک پروژهی کوچک «یادداشت» با سه commit میسازم و از آن آزمایش میکنم:
mkdir -p ~/gitlab/notes && cd ~/gitlab/notes && git init -qecho "خرید نان" > todo.txtgit add todo.txt && git commit -qm "Add todo list"echo "خرید شیر" >> todo.txtgit commit -qam "Add milk to the list"echo "نوشتن گزارش" >> todo.txtgit commit -qam "Add report task"git log --onelinecat todo.txt2231b5f Add report task78dc659 Add milk to the list3c6ad06 Add todo listخرید نانخرید شیرنوشتن گزارشمثال ۱: پوشهی کاری را برگردان، git restore
Section titled “مثال ۱: پوشهی کاری را برگردان، git restore”فایل را اشتباهاً خراب کردی و میخواهی به آخرین commit برگردد:
cd ~/gitlab/notesecho "اشتباه! همهچیز را پاک کردم" > todo.txtecho "--- وضعیت:"git status -secho "--- فایل الآن:"cat todo.txtgit restore todo.txtecho "--- بعد از git restore todo.txt:"cat todo.txtgit status -secho "(خالی = پوشه تمیز)"--- وضعیت: M todo.txt--- فایل الآن:اشتباه! همهچیز را پاک کردم--- بعد از git restore todo.txt:خرید نانخرید شیرنوشتن گزارش(خالی = پوشه تمیز)git restore فایل محتوای فایل را از آخرین commit (یا از staging اگر در آن باشد) برمیگرداند. هشدار جدی: این تغییرها commit نشده بودند، پس بعد از restore دیگر هیچ راهی برای برگرداندنشان نیست (مثال ۹ را ببین). git restore . همهی فایلهای پوشه را برمیگرداند.
مثال ۲: چیزی را از staging بیرون بیاور، git restore --staged
Section titled “مثال ۲: چیزی را از staging بیرون بیاور، git restore --staged”فایل اشتباهی را add کردی ولی نمیخواهی در commit بعدی باشد:
cd ~/gitlab/notesecho "یادداشت شخصی" > secret.txtecho "تغییر مفید" >> todo.txtgit add .echo "--- اشتباهی هر دو add شدند:"git status -sgit restore --staged secret.txtecho "--- secret.txt را از staging درآوردم (فایل سر جایش مانده):"git status -sls secret.txt--- اشتباهی هر دو add شدند:A secret.txtM todo.txt--- secret.txt را از staging درآوردم (فایل سر جایش مانده):M todo.txt?? secret.txtsecret.txt--staged فقط ناحیهی staging را عوض میکند و فایل را در پوشهی کاری دستنخورده میگذارد؛ پس کاملاً بیخطر است. (secret.txt از A به ?? برگشت: untracked.) ترکیب git restore --staged --worktree فایل هر دو را برمیگرداند. حالا todo.txt را با پیام درست commit میکنم:
cd ~/gitlab/notesgit commit -qm "Add a useful note"rm secret.txtgit log --oneline | head -24795f1c Add a useful note2231b5f Add report taskمثال ۳: برگرداندن فایل حذفشده و فایل از commit قدیمی
Section titled “مثال ۳: برگرداندن فایل حذفشده و فایل از commit قدیمی”restore فقط «آخرین حالت» را برنمیگرداند؛ با --source از هر commit میتوانی فایل را برگردانی:
cd ~/gitlab/notesrm todo.txtecho "--- فایل را حذف کردم:"git status -sgit restore todo.txtecho "--- برگشت:"cat todo.txtecho "--- نسخهی دو commit قبل از همین فایل را به پوشهی کاری بیاور:"git restore --source=HEAD~2 todo.txtcat todo.txtgit restore todo.txt--- فایل را حذف کردم: D todo.txt--- برگشت:خرید نانخرید شیرنوشتن گزارشتغییر مفید--- نسخهی دو commit قبل از همین فایل را به پوشهی کاری بیاور:خرید نانخرید شیر--source=HEAD~2 یعنی «بهجای آخرین commit، از دو commit قبل بخوان». این برای «فقط این یک فایل را به نسخهی دیروز برگردان» عالی است، بدون اینکه بقیهی پروژه به عقب برود. (آخرین دستور نسخهی کنونی را برمیگرداند.)
مثال ۴: آخرین commit را اصلاح کن، git commit --amend
Section titled “مثال ۴: آخرین commit را اصلاح کن، git commit --amend”دو اشتباه رایج: پیام غلط، و فایلی که یادت رفت. هر دو را با --amend درست میکنی (بدون ساختن commit اضافه):
cd ~/gitlab/notesecho "--- شناسهی آخرین commit قبل از amend:"git log --oneline -1echo "--- ۱) پیام را درست میکنم:"git commit --amend -m "Add report task to the todo list"git log --oneline -1echo "--- ۲) فایلی که یادم رفته بود را به همان commit اضافه میکنم:"echo "# یادداشتها" > README.mdgit add README.mdgit commit --amend --no-editgit show --stat --format='%h %s' HEAD--- شناسهی آخرین commit قبل از amend:4795f1c Add a useful note--- ۱) پیام را درست میکنم:[main ae5e6cf] Add report task to the todo list Date: Sun Oct 4 08:15:32 2026 +0000 1 file changed, 1 insertion(+)ae5e6cf Add report task to the todo list--- ۲) فایلی که یادم رفته بود را به همان commit اضافه میکنم:[main adc2b52] Add report task to the todo list Date: Sun Oct 4 08:15:32 2026 +0000 2 files changed, 2 insertions(+) create mode 100644 README.mdadc2b52 Add report task to the todo list
README.md | 1 + todo.txt | 1 + 2 files changed, 2 insertions(+)دقت کن هر بار شناسهی commit عوض شد. amend در واقع commit قبلی را اصلاح نمیکند؛ یک commit جدید میسازد و جای قبلی را میگیرد (قبلی دور ریخته میشود). برای همین فقط قبل از push امن است (مثال ۸). --no-edit یعنی «پیام قبلی را همانطور نگه دار».
مثال ۵: git reset، سه حالت
Section titled “مثال ۵: git reset، سه حالت”reset اشارهگر شاخه را به commit دیگری میبرد. سه حالت فرق دارند در اینکه با staging و پوشهی کاری چه کند. این سه آزمایش را روی سه commit آخر انجام میدهم (بعد از هر کدام، همهچیز را به حالت اول برمیگردانم):
cd ~/gitlab/notesorig=$(git rev-parse HEAD)git log --oneline | head -3echo "=== --soft HEAD~1: commit را بردار، تغییرها staged بمانند:"git reset --soft HEAD~1git status -s; git log --oneline | head -1git reset -q --hard $origecho "=== --mixed HEAD~1 (پیشفرض): commit را بردار، تغییرها فقط در پوشهی کاری:"git reset --mixed HEAD~1git status -s; git log --oneline | head -1git reset -q --hard $origecho "=== --hard HEAD~1: commit و تغییرها، هر دو دور ریخته میشود:"git reset --hard HEAD~1git status -s; git log --oneline | head -1cat todo.txtgit reset -q --hard $origadc2b52 Add report task to the todo list2231b5f Add report task78dc659 Add milk to the list=== --soft HEAD~1: commit را بردار، تغییرها staged بمانند:A README.mdM todo.txt2231b5f Add report task=== --mixed HEAD~1 (پیشفرض): commit را بردار، تغییرها فقط در پوشهی کاری:Unstaged changes after reset:M todo.txt M todo.txt?? README.md2231b5f Add report task=== --hard HEAD~1: commit و تغییرها، هر دو دور ریخته میشود:HEAD is now at 2231b5f Add report task2231b5f Add report taskخرید نانخرید شیرنوشتن گزارش| حالت | شاخه | staging | پوشهی کاری | کاربرد |
|---|---|---|---|---|
--soft |
به commit مقصد میرود | دستنخورده (تغییرها staged) | دستنخورده | «commit را بردار ولی همین را دوباره commit میکنم» (مثلاً چند commit را یکی کنی) |
--mixed (پیشفرض) |
میرود | خالی میشود | دستنخورده (تغییرها modified) | «commit را بردار، میخواهم دوباره تقسیمش کنم» |
--hard |
میرود | خالی | برمیگردد (تغییرها از بین میرود) | «همه را دور بریز» ⚠ |
سه ناحیهی درس قبل را به یاد بیاور: --soft فقط شاخه را میبرد؛ --mixed شاخه و staging؛ --hard هر سه. از --hard با احتیاط استفاده کن، چون تغییرهای commit نشده را هم میبرد.
مثال ۶: git revert، خنثیکردن بدون بازنویسی
Section titled “مثال ۶: git revert، خنثیکردن بدون بازنویسی”برای commit ای که دیگران ممکن است داشته باشند، revert تاریخچه را دست نمیزند و یک commit جدید میسازد:
cd ~/gitlab/notesecho "--- تاریخچه قبل:"git log --onelinegit revert --no-edit HEADecho "--- بعد از revert کردن آخرین commit:"git log --onelineecho "--- فایلها (تغییر آن commit معکوس شد، ولی خود commit هنوز در تاریخچه است):"cat todo.txt; lsgit show --stat --format='%h %s' HEADecho "--- (برای ادامهی مثالها، این revert را در کار خصوصی خودم برمیگردانم)"git reset -q --hard HEAD~1--- تاریخچه قبل:adc2b52 Add report task to the todo list2231b5f Add report task78dc659 Add milk to the list3c6ad06 Add todo list[main 81f24aa] Revert "Add report task to the todo list" Date: Sun Oct 4 08:15:32 2026 +0000 2 files changed, 2 deletions(-) delete mode 100644 README.md--- بعد از revert کردن آخرین commit:81f24aa Revert "Add report task to the todo list"adc2b52 Add report task to the todo list2231b5f Add report task78dc659 Add milk to the list3c6ad06 Add todo list--- فایلها (تغییر آن commit معکوس شد، ولی خود commit هنوز در تاریخچه است):خرید نانخرید شیرنوشتن گزارشtodo.txt81f24aa Revert "Add report task to the todo list"
README.md | 1 - todo.txt | 1 - 2 files changed, 2 deletions(-)--- (برای ادامهی مثالها، این revert را در کار خصوصی خودم برمیگردانم)git revert commit اثر آن commit را معکوس میکند و آن را بهصورت یک commit تازه ثبت میکند (پیامش Revert "..." است). هیچ commit ای پاک نشد؛ تاریخچه فقط یک خط بلندتر شد: هر کس که تاریخچهی قبلی را دارد با pull ساده آن را میگیرد. میشود هر commit قدیمی را هم revert کرد، ولی اگر commit های بعدی روی همان خطها تغییر داده باشند، conflict میشود (اشتباهات رایج ۴).
مثال ۷: تور نجات، git reflog
Section titled “مثال ۷: تور نجات، git reflog”حتی بعد از reset --hard، commit های ثبتشده گم نمیشوند: Git یک دفترچهی داخلی (reflog) از هر جابهجایی HEAD نگه میدارد:
cd ~/gitlab/notesgit log --oneline | head -2git reset -q --hard HEAD~2echo "--- بعد از reset --hard HEAD~2 دو commit «گم» شدند:"git log --onelineecho "--- ولی reflog همهی جابهجاییها را یادش است:"git reflog | head -4echo "--- برمیگردم به قبل از reset (HEAD@{1}):"git reset -q --hard HEAD@{1}git log --oneline | head -3adc2b52 Add report task to the todo list2231b5f Add report task--- بعد از reset --hard HEAD~2 دو commit «گم» شدند:78dc659 Add milk to the list3c6ad06 Add todo list--- ولی reflog همهی جابهجاییها را یادش است:78dc659 HEAD@{0}: reset: moving to HEAD~2adc2b52 HEAD@{1}: reset: moving to HEAD~181f24aa HEAD@{2}: revert: Revert "Add report task to the todo list"adc2b52 HEAD@{3}: reset: moving to adc2b52195e37e523f72d93642360ed80165f202--- برمیگردم به قبل از reset (HEAD@{1}):adc2b52 Add report task to the todo list2231b5f Add report task78dc659 Add milk to the listHEAD@{1} یعنی «جایی که HEAD یک جابهجایی پیش بود». تا چند ماه (بهصورت پیشفرض ۹۰ روز) commit های «گمشده» در .git میمانند. ولی reflog فقط commit ها را نگه میدارد، نه تغییرهای commit نشده (مثال ۹). درس «نجات و ابزارهای پیشرفته» reflog را کامل توضیح میدهد.
مثال ۸: چرا بعد از push نباید amend یا reset کرد؟
Section titled “مثال ۸: چرا بعد از push نباید amend یا reset کرد؟”یک مخزن «سرور» (bare) میسازم، commit را push میکنم، بعد آن را amend میکنم و دوباره push میکنم تا ببینی Git چه میگوید (remote و push در درسهای بعد کامل میآید؛ اینجا فقط نتیجه مهم است):
cd ~/gitlabgit init -q --bare server.gitcd notesgit remote add origin ~/gitlab/server.gitgit push -q -u origin main 2>&1 | tail -1echo "--- commit را (که الان روی «سرور» است) بازنویسی میکنم:"git commit --amend -qm "Reword: add report task"echo "--- دوباره push:"git push 2>&1--- commit را (که الان روی «سرور» است) بازنویسی میکنم:--- دوباره push:To /home/ali/gitlab/server.git ! [rejected] main -> main (non-fast-forward)error: failed to push some refs to '/home/ali/gitlab/server.git'hint: Updates were rejected because the tip of your current branch is behindhint: its remote counterpart. If you want to integrate the remote changes,hint: use 'git pull' before pushing again.hint: See the 'Note about fast-forwards' in 'git push --help' for details.Git رد کرد: ! [rejected] main -> main (non-fast-forward). تاریخچهی محلی (با commit بازنویسیشده) از تاریخچهی سرور جدا شده و Git نمیگذارد با یک push ساده سرور را عقب ببرد. راه زورکی git push --force است که تاریخچهی سرور را بازنویسی میکند و کار هر کسی که از commit قدیمی ادامه داده را خراب میکند. (نسخهی مؤدبتر --force-with-lease است که فقط اگر کسی دیگر چیزی push نکرده اجازه میدهد.) قاعده: بعد از push، بهجای amend و reset از revert استفاده کن. حالا درستش میکنم، یعنی تاریخچهی محلی را با سرور هماهنگ میکنم:
cd ~/gitlab/notesgit reset -q --hard origin/maingit log --oneline | head -3adc2b52 Add report task to the todo list2231b5f Add report task78dc659 Add milk to the list(origin/main همان وضعیت سرور است. اینجا چون فقط من آن را داشتم و میخواستم تغییرِ amend را دور بریزم، reset --hard امن بود.)
مثال ۹: git clean و تغییرهای از دست رفته
Section titled “مثال ۹: git clean و تغییرهای از دست رفته”restore فقط فایلهای دنبالشده را برمیگرداند. فایلهای جدید و untracked را git clean پاک میکند، و آن هم برگشتناپذیر است. همیشه اول با -n (dry run) ببین چه چیزی پاک میشود:
cd ~/gitlab/notesecho "موقت" > tmp1.log; echo "موقت" > tmp2.log; mkdir build && echo x > build/out.oecho "--- فایلهای untracked:"git status -secho "--- git clean -n (فقط نشان بده، پاک نکن):"git clean -necho "--- با -d پوشهها هم:"git clean -ndgit clean -fdqecho "--- بعد از git clean -fd:"git status -secho "(خالی)"--- فایلهای untracked:?? build/?? tmp1.log?? tmp2.log--- git clean -n (فقط نشان بده، پاک نکن):Would remove tmp1.logWould remove tmp2.log--- با -d پوشهها هم:Would remove build/Would remove tmp1.logWould remove tmp2.log--- بعد از git clean -fd:(خالی)-f (force) لازم است (Git برای ایمنی بدون آن کار نمیکند)، -d پوشهها را هم میگیرد. و -x حتی فایلهای .gitignore شده را پاک میکند، که بسیار خطرناک است (ممکن است .env تو را ببرد). همیشه اول -n.
پشت پرده: reset یک اشارهگر را جابهجا میکند
Section titled “پشت پرده: reset یک اشارهگر را جابهجا میکند”شاخه فقط یک فایل کوچک است که هش یک commit را نگه میدارد. git reset همان فایل را عوض میکند. ببین:
cd ~/gitlab/notesecho "--- شاخهی main فقط یک هش است:"cat .git/refs/heads/maingit rev-parse HEADgit reset -q --soft HEAD~1echo "--- بعد از reset --soft HEAD~1، همان فایل هش دیگری دارد:"cat .git/refs/heads/maingit reset -q --soft HEAD@{1}echo "--- و بعد از برگشتن:"cat .git/refs/heads/main--- شاخهی main فقط یک هش است:adc2b52195e37e523f72d93642360ed80165f202adc2b52195e37e523f72d93642360ed80165f202--- بعد از reset --soft HEAD~1، همان فایل هش دیگری دارد:2231b5f0f6b3c8ff57906e03e0c56ddf03f0634e--- و بعد از برگشتن:adc2b52195e37e523f72d93642360ed80165f202commit های دورریختهشده هنوز در .git/objects هستند (برای همین reflog میتواند برشان گرداند)؛ فقط هیچ برچسبی به آنها اشاره نمیکند، و Git بعد از مدتی (garbage collection) پاکشان میکند. revert برعکس: اشارهگر را فقط جلو میبرد، به یک commit تازه.
جدولهای مرجع
Section titled “جدولهای مرجع”| دستور | کار | commit ها | دادهی از دسترفتنی؟ |
|---|---|---|---|
git restore فایل |
پوشهی کاری ← از staging/HEAD | تغییر نمیکند | تغییر commit نشده ⚠ |
git restore --staged فایل |
از staging بیرون بیاور | تغییر نمیکند | نه |
git restore --source=commit فایل |
فایل از commit دلخواه | تغییر نمیکند | تغییر commit نشده ⚠ |
git commit --amend |
جایگزینی آخرین commit | بازنویسی | نه (قبلی در reflog) |
git reset --soft commit |
شاخه عقب؛ staging و کاری سالم | بازنویسی | نه |
git reset commit (mixed) |
شاخه عقب؛ staging خالی | بازنویسی | نه |
git reset --hard commit |
همه عقب | بازنویسی | تغییر commit نشده ⚠ |
git revert commit |
commit جدید که معکوس میکند | فقط اضافه | نه |
git clean -n / -fd |
فایلهای untracked | — | بله ⚠ |
git reflog |
جابهجاییهای HEAD (نجات) | — | — |
| میخواهم… | بزن |
|---|---|
| تغییر نکردهی فایل را دور بریزم | git restore فایل |
add را لغو کنم |
git restore --staged فایل |
| پیام آخرین commit را عوض کنم | git commit --amend -m "..." |
| فایل فراموششده را به آخرین commit بیفزایم | git add فایل && git commit --amend --no-edit |
| ۳ commit خصوصی را یکی کنم | git reset --soft HEAD~3 و git commit |
| commit مشترک را خنثی کنم | git revert commit |
| همهی تغییرها را دور بریزم و مثل commit آخر شوم | git reset --hard HEAD ⚠ |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) reset --hard یا restore روی کار commit نشده
Section titled “۱) reset --hard یا restore روی کار commit نشده”mkdir -p ~/gitlab/danger && cd ~/gitlab/danger && git init -qecho "نسخهی ثبتشده" > f.txt && git add f.txt && git commit -qm "Commit f"echo "یک ساعت کار مهم، هنوز commit نشده" > f.txtgit reset --hard HEADecho "--- محتوای فایل بعد از reset --hard:"cat f.txtecho "--- آیا reflog چیزی از آن کار دارد؟"git reflogHEAD is now at edfab30 Commit f--- محتوای فایل بعد از reset --hard:نسخهی ثبتشده--- آیا reflog چیزی از آن کار دارد؟edfab30 HEAD@{0}: reset: moving to HEADedfab30 HEAD@{1}: commit (initial): Commit fآن «یک ساعت کار» رفت و هیچ راه برگشتی ندارد: reflog فقط commit ها را بهخاطر میسپارد، نه تغییرهای ثبتنشده. راهحل: قبل از هر restore/reset --hard، git status و git diff را بخوان؛ و اگر شک داری اول یک commit موقت بزن (git commit -am wip)، که بعداً با reset یا amend تمیزش میکنی.
۲) amend یا reset بعد از push
Section titled “۲) amend یا reset بعد از push”مثال ۸: non-fast-forward. راهحل: git revert. و اگر عمداً تاریخچهی خودت را بازنویسی کردهای (روی شاخهی خصوصی)، git push --force-with-lease (نه --force).
۳) revert روی یک commit ادغام (merge)
Section titled “۳) revert روی یک commit ادغام (merge)”mkdir -p ~/gitlab/mergerev && cd ~/gitlab/mergerev && git init -qecho 1 > f && git add f && git commit -qm "Start"git switch -qc feature && echo a > g && git add g && git commit -qm "Feature work"git switch -q main && echo 2 >> f && git commit -qam "Main work"git merge -q --no-edit featuregit revert --no-edit HEAD 2>&1 | head -3error: commit 9dd3913470fa0705555dc54547c9caa1a1869614 is a merge but no -m option was given.fatal: revert failedcommit ادغام دو والد دارد و Git نمیداند کدام طرف را «نگه دارد». باید با -m 1 بگویی «والد اول (main) را اصلی بدان»: git revert -m 1 HEAD. (این را بعد از درس merge بهتر میفهمی؛ فعلاً بدان خطای is a merge but no -m option was given یعنی این.)
۴) revert یک commit قدیمی که بعدیها به آن وابستهاند
Section titled “۴) revert یک commit قدیمی که بعدیها به آن وابستهاند”cd ~/gitlab/notesgit log --oneline | head -3echo "--- خنثیکردن commit ای که دو commit قبل است:"git revert --no-edit HEAD~1 2>&1 | head -3echo "--- وضعیت (UU = هر دو طرف عوض کردهاند):"git status -sgit revert --abortecho "--- با --abort به حالت قبل برگشتم:"git status -s; echo "(خالی)"adc2b52 Add report task to the todo list2231b5f Add report task78dc659 Add milk to the list--- خنثیکردن commit ای که دو commit قبل است:Auto-merging todo.txtCONFLICT (content): Merge conflict in todo.txterror: could not revert 2231b5f... Add report task--- وضعیت (UU = هر دو طرف عوض کردهاند):UU todo.txt--- با --abort به حالت قبل برگشتم:(خالی)CONFLICT (content): commit های بعدی همان بخش فایل را عوض کردهاند و Git نمیداند معکوسکردن را کجا و چطور اعمال کند. راهحل: conflict را دستی حل کن (درس بعدی)، یا با git revert --abort منصرف شو.
۵) استفاده از دستور قدیمی git checkout -- فایل
Section titled “۵) استفاده از دستور قدیمی git checkout -- فایل”قبل از Git ۲.۲۳ برای همهی این کارها git checkout بود (که هم شاخه عوض میکرد هم فایل برمیگرداند و گیجکننده بود). حالا git restore (فایلها) و git switch (شاخهها) جدا شدهاند. دستور قدیمی هنوز کار میکند، ولی در آموزش و کد جدید restore را بنویس.
فایلی را ویرایش کن، با git diff تغییر را ببین، و بعد با git restore پوشهی کاری را به آخرین commit برگردان.
دیدن جواب
mkdir -p ~/gitlab/ex1 && cd ~/gitlab/ex1 && git init -qecho "نسخهی خوب" > a.txt && git add a.txt && git commit -qm "Add a"echo "نسخهی خراب" > a.txtgit diffgit restore a.txtcat a.txtgit status -sdiff --git a/a.txt b/a.txtindex cecab49..851717b 100644--- a/a.txt+++ b/a.txt@@ -1 +1 @@-نسخهی خوب+نسخهی خرابنسخهی خوبتمرین اصلی: سه نوع اشتباه بساز و هر کدام را با روش درست برگردان: (۱) یک فایل را add کن و پشیمان شو (بدون از دست دادن تغییرش)، (۲) پیام آخرین commit (push نشده) غلط است، (۳) یک commit قدیمیتر که «به اشتراک گذاشته شده» باید خنثی شود.
دیدن جواب
mkdir -p ~/gitlab/ex2 && cd ~/gitlab/ex2 && git init -qecho one > f && git add f && git commit -qm "Add f"echo bug > g && git add g && git commit -qm "Add g (has a bug)"echo two >> f && git commit -qam "Update f"echo "=== ۱) add اشتباهی:"echo draft > draft.txt && git add draft.txtgit restore --staged draft.txt && git status -s && rm draft.txtecho "=== ۲) پیام غلط:"git commit --amend -qm "Add two to f"git log --oneline | head -1echo "=== ۳) خنثیکردن commit دوم («Add g») که به اشتراک گذاشته شده:"git revert --no-edit HEAD~1 2>&1 | head -2git log --onelinels=== ۱) add اشتباهی:?? draft.txt=== ۲) پیام غلط:fb05b74 Add two to f=== ۳) خنثیکردن commit دوم («Add g») که به اشتراک گذاشته شده:[main d519f19] Revert "Add g (has a bug)" Date: Sun Oct 4 08:15:33 2026 +0000d519f19 Revert "Add g (has a bug)"fb05b74 Add two to fb7483db Add g (has a bug)222a8cc Add ffچون commit های بعدی به فایل g دست نزدهاند، revert بدون conflict انجام شد و فایل g رفت، بدون اینکه تاریخچه بازنویسی شود.
یک commit بزرگ زدهای که دو کار نامرتبط را با هم دارد (a.txt و b.txt). آن را بدون از دست دادن تغییرها به دو commit جدا تقسیم کن. (راهنما: reset --mixed HEAD~1 و بعد دو بار add و commit.)
دیدن جواب
mkdir -p ~/gitlab/ex3 && cd ~/gitlab/ex3 && git init -qecho base > base.txt && git add base.txt && git commit -qm "Base"echo a > a.txt && echo b > b.txtgit add . && git commit -qm "Do two things at once"echo "--- قبل: یک commit بزرگ:"git show --stat --format='%h %s' HEAD | head -4git reset -q --mixed HEAD~1git add a.txt && git commit -qm "Add a"git add b.txt && git commit -qm "Add b"echo "--- بعد: دو commit جدا:"git log --oneline--- قبل: یک commit بزرگ:014a4ea Do two things at once
a.txt | 1 + b.txt | 1 +--- بعد: دو commit جدا:02a5946 Add b2f67a88 Add a8072019 Baseآزمونک
Section titled “آزمونک”یک commit را push کردهای و دیگران آن را گرفتهاند. حالا میفهمی اشتباه بوده. کدام روش امن است؟
reset و amend تاریخچه را بازنویسی میکنند و بعد از push کار دیگران را خراب میکنند. revert فقط یک commit به انتها اضافه میکند و با همه سازگار است.
git restore --staged فایل چه میکند؟
بیخطر است؛ فقط ناحیهی staging را عوض میکند.
تفاوت reset --soft و reset --hard؟
soft ← فقط شاخه؛ mixed ← شاخه و staging؛ hard ← هر سه.
commit --amend چه چیزی میسازد؟
شناسه از محتوا حساب میشود؛ محتوا یا پیام عوض شد، شناسه هم عوض میشود.
بعد از «git reset --hard HEAD~2»، commit های کنار گذاشتهشده را چطور برمیگردانی؟
commit های ثبتشده در reflog میمانند؛ ولی تغییر commit نشده را نه.
چرا git clean بدون -n خطرناک است؟
فایلهایی که هرگز commit نشدهاند راه برگشت ندارند.
جمعبندی
Section titled “جمعبندی”- ابتدا بپرس: اشتباه کجاست (کاری، staging، commit) و آیا push شده؟
git restore فایلپوشهی کاری را برمیگرداند (⚠ تغییر commit نشده نابود میشود)؛--stagedفقط از staging درمیآورد (امن)؛--source=commitاز commit دلخواه.git commit --amendآخرین commit را با یک commit جدید جایگزین میکند (فقط قبل از push).git reset:--soft(فقط شاخه)،--mixed(شاخه + staging)،--hard(همه ⚠)؛ تاریخچه را بازنویسی میکند، فقط برای کار خصوصی.git revert commitیک commit جدید میسازد که اثر قبلی را خنثی میکند؛ برای تاریخچهی مشترک امن است. برای merge:-m 1.git reflogتور نجات است برای commit های گمشده؛ ولی تغییر commit نشده را نه.git clean -nرا قبل از-fdبزن.- بعد از push:
reset/amend+push←non-fast-forward؛ راه امنrevert.
| دستور | کاری که میکند |
|---|---|
git restore فایل | پوشهی کاری را از آخرین commit برگردان ⚠ |
git restore --staged فایل | از staging بیرون بیاور |
git restore --source=HEAD~2 فایل | فایل را از دو commit قبل بیاور |
git commit --amend -m "پیام" | پیام آخرین commit را عوض کن |
git add فایل && git commit --amend --no-edit | فایل فراموششده را به آخرین commit اضافه کن |
git reset --soft HEAD~1 | commit را بردار، تغییرها staged |
git reset HEAD~1 | commit را بردار، تغییرها در پوشهی کاری |
git reset --hard HEAD~1 | commit و تغییرها را دور بریز ⚠ |
git revert commit | commit مشترک را خنثی کن (commit جدید) |
git reflog | جابهجاییهای HEAD؛ نجات commit گمشده |
git clean -nd / git clean -fd | فایلهای untracked: نمایش / حذف ⚠ |