Git Reset چیست؟ بخش 2
در بخش قبلی با دستور git reset و سه درخت گیت (مسیر جاری، تاریخچه ی commit و staging Index) آشنا شدیم و فهمیدیم که این دستور چگونه کار می کند همچنین با شیوه ی کار یکی از گزینه های ورودی این دستور (hard--
) نیز آشنا شدیم.
در این بخش به گزینه های دیگر از جمله mixed--
و soft--
خواهیم پرداخت و تفاوت reverting و resetting را بررسی خواهیم کرد.
mixed--
این گزینه حالت پیش فرض این دستور است. اشاره گرها به روز می شوند. Staging index به وضعیت commit مشخص شده بازنشانی می شود. هر تغییری که از Staging index حذف شده است به مسیر جاری منتقل می شود.
$ echo 'new file content' > new_file
$ git add new_file
$ echo 'append content' >> reset_lifecycle_file
$ git add reset_lifecycle_file
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
new file: new_file
modified: reset_lifecycle_file
$ git ls-files -s
100644 8e66654a5477b1bf4765946147c49509a431f963 0 new_file
100644 7ab362db063f9e9426901092c00a3394b4bec53d 0 reset_lifecycle_file
در مثال بالا، برخی از تغییرها را در مخزن ایجاد کردیم. مانند مثال قبل یک فایل جدید با نام new_file
اضافه کرده و محتویات فایل reset_lifecycle_file
را تغییر دادیم. سپس این تغییرات را با استفاده از دستور git add به Staging index منتقل می کنیم. حال با این وضعیت مخزن، بازنشانی را اجرا می کنیم.
$ git reset --mixed
$ git status
On branch master
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: reset_lifecycle_file
Untracked files:
(use "git add ..." to include in what will be committed)
new_file
no changes added to commit (use "git add" and/or "git commit -a")
$ git ls-files -s
100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file
در اینجا یک بازنشانی را با استفاده از گزینه ی mixed--
اجرا کردیم. خروجی دستورهای git status
و git ls-files
نشان دهنده ی این است که Staging index به وضعیتی در آمده است که تنها فایل reset_lifecycle_file
در آن وجود دارد و هش SHA برای این فایل به همان نسخه ی قبلی خود بازگشته است.
نکات مهمی که در اینجا باید به آن ها توجه داشت این است که git status
نشان می دهد که در فایل reset_lifecycle_file
تغییراتی وجود دارد و یک فایل باز نشده به نام new_file
وجود دارد. این رفتار صریح گزینه ی mixed--
است که Staging index
را بازنشانی می کند و تغییرات در حال انتظار را به مسیر جاری منتقل می کند. تفاوت گزینه hard--
با این گزینه این است که در حالتی که از گزینه ی hard--
استفاده می کنیم Staging index و مسیر جاری هر دو بازنشانی می شوند ولی در گزینه ی mixed--
تغییرات در مسیر جاری باقی می ماند.
soft--
هنگامی که از ورودی soft--
استفاده شود، اشاره گرها به روز و بازنشانی متوقف می شود که باعث می شود Staging index و مسیر جاری دست نخورده باقی بمانند. بیاید مانند قبل با مخزنی که ایجاد کردیم این گزینه را امتحان کنیم.
$ git add reset_lifecycle_file
$ git ls-files -s
100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: reset_lifecycle_file
Untracked files:
(use "git add ..." to include in what will be committed)
new_file
در اینجا دوباره از git add
برای اضافه کردن reset_lifecycle_file
به Staging index استفاده می کنیم. با اجرای دستور git ls-files -s
مطمئن می شویم index به روز شده است. خروجی دستور git status
اکنون "تغییرات در این commit" را با رنگ سبز نشان می دهد. new_file
از مثال های قبلی ما در مسیر جاری به عنوان یک فایل باز نشده ی شناور است. می توانیم آن فایل را با دستور rm new_file
حذف کنیم زیرا آن را در مثال های بعدی نیاز نداریم. حال در این وضعیت دستور git reset --soft
را اجرا می کنیم.
$ git reset --soft
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: reset_lifecycle_file
$ git ls-files -s
100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file
با اجرای دستور git status
و git ls-files
-s وضعیت مخزن را بررسی می کنیم. خروجی آنها نشان می دهد که هیچ چیز تغییر نکرده است که همین رفتار مورد انتظار بود. اجرای دستور git reset --soft
فقط تاریخچه ی commit را بازنشانی می کند. به طور پیش فرض، git reset با HEAD به عنوان commit مورد نظر سر و کار دارد. به دلیل اینکه تاریخچه ی commit ما روی HEAD تنظیم شده بود و ما آن را بازنشانی کردیم، اتفاق قابل ملاحظه ای را مشاهده نکردیم.
برای درک بهتر نیاز به یک commit داریم که HEAD نباشد. reset_lifecycle_file
را در Staging index داریم، بیاید یک commit جدید ایجاد کنیم.
$ git commit -m"prepend content to reset_lifecycle_file"
در حال حاضر مخزن ما باید سه commit داشته باشد و می خواهیم به commit اول بازگردیم. برای این کار نیاز داریم که شناسه ی commit اول را داشته باشیم. برای پیدا کردن این شناسه می توانیم از خروجی دستور git log
استفاده کنیم.
$ git log
commit 62e793f6941c7e0d4ad9a1345a175fe8f45cb9df
Author: bitbucket
Date: Fri Dec 1 15:03:07 2017 -0800
prepend content to reset_lifecycle_file
commit dc67808a6da9f0dec51ed16d3d8823f28e1a72a
Author: bitbucket
Date: Fri Dec 1 10:21:57 2017 -0800
update content of reset_lifecycle_file
commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4
Author: bitbucket
Date: Thu Nov 30 16:50:39 2017 -0800
initial commit
شناسه ی commit که ما برای مثال خود به آن نیاز داریم برابر با 780411da3b47117270c0e3a8d5dcfd11d28d04a4
است. این شناسه است که مربوط به "initial commit" می باشد.
قبل از اینکه به این commit منتقل شویم، ابتدا وضعیت فعلی مخزن را بررسی می کنیم.
$ git status && git ls-files -s
On branch master
nothing to commit, working tree clean
100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file
اجرای دستورهای git status
و git ls-files -s
نشان می دهد که تغییراتی را در حالت انتظار در مخزن داریم و فایل reset_lifecycle_file
در Staging index
با نسخه ای به شناسه ی 67cc52710639e5da6b515416fd779d0741e3762e
وجود دارد. حال دستور git reset --soft
را با شناسه ی اولین commit اجرا می کنیم.
$git reset --soft 780411da3b47117270c0e3a8d5dcfd11d28d04a4
$ git status && git ls-files -s
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: reset_lifecycle_file
100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file
کد بالا یک دستور بازنشانی با حالت soft را اجرا می کند و همچنین با اجرای دستورهای git status
و git ls-files -s
وضعیت مخزن را نشان می دهد. با توجه به خروجی وضعیت مخزن می توان نکاتی را برداشت کرد.
1. git status نشان می دهد که تغییراتی در فایل reset_lifecycle_file
وجود دارد و آنها را برجسته می کند و نشان می دهد که این تغییرات هستند که commit بعدی را انجام می دهند.
2. خروجی دستور git ls-files -s
نشان می دهد که Staging index و هش SHA آن تغییری نکرده و برابر با مقدار 67cc52710639e5da6b515416fd779d0741e3762e
است.
برای توضیح بیشتر آنچه که در این بازنشانی اتفاق افتاده است، بیایید خروجی دستور git log را بررسی کنیم:
$ git log
commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4
Author: bitbucket
Date: Thu Nov 30 16:50:39 2017 -0800
initial commit
خروجی این دستور نشان می دهد که یک commit در تاریخچه ی commit وجود دارد. این اتفاق به وضوح نشان می دهد که soft--
چه کاری را انجام می دهد. همان طور که در تمام حالت های استفاده از git reset نشان داده شده اولین عملی که دستورgit reset انجام می دهد پاک کردن درخت commit است. مثال های قبلی ما که در حالت های hard--
و mixed--
زده شدند هر دو در برابر HEAD بودند و درخت commit را به عقب بازنگرداندند.
resetting در مقابل reverting
اگر git revert راهی امن برای بازگرداندن تغییرات باشد، شما به این فکر خواهید کرد که git reset روشی خطرناک است. یکی از خطرهای واقعی استفاده از دستور git reset ، از دست دادن کارهای انجام شده است. git reset هرگز یک commit را حذف نمی کند، با این حال، commit می تواند به اصطلاح یتیم شود که یعنی از هیچ مسیر مستقیمی به این commit نمی توان دسترسی داشت. این commit های یتیم می توانند توسط دستور git reflog
پیدا و بازسازی شوند. Git به طور دائم commit های یتیم را پس از اجرای garbage collector
داخلی حذف می کند. به طور پیش فرض، Git تنظیم شده است تا هر 30 روز garbage collector
را اجرا کند. تاریخچه ی commit یکی از سه درخت گیت است که دو درخت دیگر، Staging index و مسیر جاری(working directory) مانند commit ها دائمی نیستند. هنگام استفاده از این ابزار باید مراقب باشید، زیرا جزو تنها دستورهای Git است که توانایی از بین بردن کار شما را دارد.
در حالی که reverting طراحی شده است تا با خیال راحت یک commit عمومی را لغو کند، در مقابل git reset طراحی شده است تا تغییرات محلی Staging index و مسیر جاری را لغو کند. به دلیل اهداف متمایز آنها، دو دستور به طور متفاوتی اجرا می شوند: resetting به طور کامل یک تغییر را حذف می کند، در حالی که reverting تغییرات اصلی را حفظ می کند و از یک commit جدید برای بازگردانی استفاده می کند.
تاریخچه های عمومی را بازنشانی نکنید
بعد از push کردن به یک مخزن عمومی هرگز نباید از دستور git reset استفاده کرد. پس از انتشار یک commit، باید فرض کنید که توسعه دهندگان دیگر وابسته به آن هستند.
از بین بردن یک commit که اعضای دیگر تیم همچنان در حال توسعه آن محصول و استفاده از آن هستند، مشکلات جدی ای را برای همکاری ایجاد می کنند. هنگامی که آنها سعی می کنند با مخزن خود همگام شوند، به نظر می رسد که یک تکه از تاریخچه ی پروژه به طور ناگهانی ناپدید می شود. دنباله ی زیر نشان می دهد زمانی که شما سعی می کنید یک commit عمومی را reset کنید، چه اتفاقی می افتد. شاخه ی origin/master
نسخه ی مخزن مرکزی از شاخه ی master محلی شما است.
پس از تنظیم مجدد، به محض این که commit جدیدی را انجام دهید، git فکر می کند که تاریخچه ی محلی شما از origin/master
جدا شده است و ادغام یک commit نیازمند هماهنگ سازی مخازن شما است، که به احتمال زیاد این اتفاق تیم شما را آشفته و نا امید می کند.
نکته این است که اطمینان حاصل کنید که شما از git reset روی یک محیط آزمایشی محلی استفاده می کنید که در تغییرات منتشر شده، اشتباهی به وجود نیاید. اگر شما نیاز به تغییر یک commit عمومی دارید، دستور git revert به طور خاص برای این منظور طراحی شده است.
مثال های git reset
git reset main.py
فایل مشخص شده را از منطقه ی stage حذف می کند، اما مسیر جاری را بدون تغییر رها می کند. این دستور یک فایل را از منطقه ی stage خارج می کند بدون بازنویسی هیچ تغییری.
git reset
منطقه ی stage را با توجه به آخرین commit انجام شده بازنشانی می کند، اما مسیر جاری را بدون تغییر رها می کند. این دستور، همه ی فایل ها را از منطقه ی stage خارج می کند بدون بازنویسی هیچ تغییری. این دستور به شما این اجازه را می دهد که کار خود را دوباره از ابتدا انجام دهید.
git reset --hard
منطقه ی stage و مسیر جاری را با توجه به آخرین commit انجام شده بازنشانی می کند. علاوه بر تغییر در منطقه ی stage، --hard به git می گوید که تمام تغییرات در مسیر جاری را نیز بازنویسی کند. این دستور همه ی تغییرات commit نشده را لغو می کند، بنابراین مطمئن شوید که واقعا می خواهید قبل از استفاده از آن ها، تغییرات محلی خود را از بین ببرید.