گیت reset چیست؟
دستور git reset یک ابزار پیچیده و چند منظوره برای لغو تغییرات است. این دستور سه فرم اصلی فراخوانی دارد. این فرم ها به ورودی های خط فرمان --soft, --mixed, --hard مربوط می شوند. سه ورودی که هر کدام مربوط به سه مکانیزم مدیریت داخلی git می شود که عبارت اند از: درخت commit (HEAD)، Staging index و مسیر جاری.
Git reset و سه درخت git
برای درک درست از استفاده ی git reset، ابتدا باید سیستم های مدیریت داخلی git را درک کنیم. گاهی اوقات این مکانیزم ها را "سه درخت git" می نامند. واژه ی درخت ممکن است شما را به اشتباه بیاندازد، همانطور که آنها ساختار داده ای درختی دقیقی نیز ندارند. با این حال git از آنها برای پیگیری و ویرایش یک جدول زمانی از ساختارهای داده ای مبتنی بر گره و اشاره گر استفاده می کند. بهترین راه برای شرح دادن این مکانیسم ها، ایجاد یک تغییر در یک مخزن و دنبال کردن سه درخت آن است.
برای شروع، ما یک مخزن جدید را با دستور های زیر ایجاد خواهیم کرد:
$ mkdir git_reset_test
$ cd git_reset_test/
$ git init .
Initialized empty Git repository in /git_reset_test/.git/
$ touch reset_lifecycle_file
$ git add reset_lifecycle_file
$ git commit -m"initial commit"
[master (root-commit) d386d86] initial commit
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 reset_lifecycle_file
مثال بالا یک مخزن git را بایک فایل خالی جدید به نام reset_lifecycle_file
ایجاد می کند. در این مرحله از مثال، مخزن یک commit با شناسه d386d86
دارد که حاوی اضافه شدن فایل reset_lifecycle_file
است.
مسیر جاری (Working Directory)
درخت اولی که در این مثال بررسی خواهیم کرد "مسیر جاری" است. این درخت با فایل سیستم محلی همگام سازی شده است و نماینده ی تغییرات ایجاد شده به محتوا در فایل ها و پوشه ها است.
$ echo 'hello git reset' > reset_lifecycle_file
$ 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
در مخزن نسخه ی نمایشیمان، ما برخی از مطالب را در فایل reset_lifecycle_file
تغییر می دهیم. اجرای دستور git status
نشان می دهد که git از تغییرات در فایل آگاه است. این تغییرات در حال حاضر بخشی از درخت اول " مسیر جاری " است. از دستور git status می توان برای نشان دادن تغییرات در مسیر جاری استفاده کرد. تغییرات با استفاده از پیشوند modified با رنگ قرمز نمایش داده می شود.
Staging index
بعد از مسیر جاری درخت "Staging index" را بررسی می کنیم. این درخت تغییرات مسیر جاری را دنبال می کند، که با git add برای ذخیره شدن در commit بعدی ارتقا یافته است. این درخت یک مکانیزم ذخیره سازی پیچیده داخلی است. git به طور کلی تلاش می کند تا جزئیات پیاده سازی Staging index را از کاربر پنهان کند.
برای مشاهده ی دقیق وضعیت Staging index باید از دستور کمتر شناخته شده ی git ls-files
استفاده کرد. دستور git ls-files
در اساس یک ابزار اشکال زدایی برای بازرسی وضعیت از درخت Staging index است.
git ls-files -s
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 reset_lifecycle_file
در اینجا دستور git ls-files
را همراه با گزینه ی -s
یا --stage
اجرا کرده ایم. بدون گزینه -s
خروجی دستور git ls-files
به سادگی فهرستی از نام فایل ها و مسیرهایی است که در حال حاضر بخشی از index هستند. گزینه ی -s اطلاعات بیشتری درمورد فایل های موجود در Staging index نمایش می دهد. این اطلاعات حالت bits محتویات stage، نام شیء و شماره ی stage است. در اینجا ما به نام شیء علاقه مند هستیم، که برابر با مقدار دوم (e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
) است. این یک نمونه ی استاندارد از هش SHA-1 git از محتوای فایل هاست. تاریخچه ی commit، هش SHA خود را برای شناسایی اشاره گرها به منظور استفاده از commit و مرجع آن ذخیره می کند و index Staging دارای هش خودش برای ردیابی نسخه های فایل ها در index است.
در ادامه، ما فایل reset_lifecycle_file
که اصلاح شده بود را به index Staging منقل می کنیم.
$ git add reset_lifecycle_file
$ git status
On branch master Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: reset_lifecycle_file
در اینجا با اجرای دستور git add reset_lifecycle_file
فایل نام برده شده به index Staging
اضافه می شود. اجرای دستور git status در حال حاضر reset_lifecycle_file
را به رنگ سبز در زیر "تغییرات commit" نمایش می دهد. مهم است که توجه داشته باشید که دستور git status نماینده ی درست و واقعی index Staging نیست. خروجی دستور git status تغییرات بین تاریخچه ی commit و index Staging
را نمایش می دهد. بیایید محتوای index Staging را در این مرحله بررسی کنیم.
$ git ls-files -s
100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file
ما می توانیم ببینیم که هش SHA برای فایل reset_lifecycle_file
ازe69de29bb2d1d6434b8b29ae775ad8c2e48c5391 به d7d77c1b04b5edd5acfc85de0b592449e5303770 به روز شده است.
تاریخچه ی commit
درخت آخر تاریخچه ی commit است. دستور git commit تغییرات را به یک عکس فوری دائمی اضافه می کند که در تاریخچه ی commit قرار می گیرد. این عکس فوری همچنین شامل وضعیتی از index Staging
در زمان انجام commit است.
$ git commit -am"update content of reset_lifecycle_file"
[master dc67808] update content of reset_lifecycle_file
1 file changed, 1 insertion(+)
$ git status
On branch master
nothing to commit, working tree clean
در اینجا ما یک commit با پیام "update content of resetlifecyclefile
" ایجاد کرده ایم. این تغییرات به تاریخچه ی commit اضافه شده است. اجرای دستور git status
در این لحظه نشان می دهد که هیچ تغییری در هیچ یک از درختان وجود ندارد. اجرای دستور git log
تاریخچه ی commit را نشان می دهد. حالا که ما این تغییرات را از راه سه درخت دنبال کردیم می توانیم شروع به استفاده از git reset کنیم.
Git reset چگونه کار می کند
به طور سطحی، git reset در رفتار مشابه git checkout است. در حالی که git checkout تنها روی اشاره گر HEAD کار می کند، git reset با اشاره گر HEAD و اشاره گر شاخه ی جاری کار می کند. برای نشان دادن بهتر این رفتار، مثال زیر را در نظر بگیرید:
این مثال یک دنباله ای از commit ها را در شاخه ی master نشان می دهد. اشاره گر HEAD و اشاره گر شاخه ی master در حال حاضر اشاره به commit، d دارند. حالا بیایید دو دستور git checkout b و git reset b را اجرا کنیم و نتیجه را مقایسه کنیم.
git checkout b
با اجرای دستور git checkout
اشاره گر شاخه ی master هنوز به d اشاره می کند و اشاره گر HEAD منتقل شده است و در حال حاضر به commit b اشاره می کند. مخزن در حال حاضر در وضعیت "detached HEAD
" است.
git reset b
با اجرای دستور git reset هر دو اشاره گر به commit مشخص، یعنی commit b اشاره می کنند.
افزون بر به روز رسانی اشاره گرها، git reset وضعیت سه درخت را نیز تغییر می دهد. نشانگر مرجع به طور مداوم تغییر می کند و یک به روز رسانی برای درخت سوم یا همان درخت commit است. ورودی های دستور git reset که شامل --soft، --mixed و --hard می شوند شیوه ی تغییر درخت های Staging index و مسیر جاری را مشخص می کنند.
گزینه های اصلی
دستور git reset به طور پیش فرض دو مقدار --mixed
و HEAD
را به عنوان ورودی دارد. این به این معنی است که اجرای دستور git reset معادل با اجرای git reset --mixed HEAD
است. در این حالت commit ای که HEAD به آن اشاره می کند، مشخص شده است. به جای HEAD می تواند هر هش SHA-1 دیگری که متعلق به یک commit است مورد استفاده قرار بگیرد.
--hard
این گزینه مستقیم و خطرناک است و اغلب مورد استفاده قرار می گیرد. بعد از اجرای دستور با گزینه ی --hard اشاره گر تاریخچه ی commit به commit مشخص شده اشاره می کند. سپس، Staging index و مسیر جاری بازنشانی می شوند تا با commit مشخص شده مطابقت پیدا کنند. هر گونه تغییرات در حال انتظار موجود در Staging index و مسیر جاری بازنشانی می شوند تا با وضعیت درخت commit مطابقت پیدا کنند. این به این معنی است که هر کار در حال انتظاری که در حلقه ی Staging index و مسیر جاری قرار بگیرد از بین خواهد رفت.
برای نشان دادن این، بیایید با سه نمونه ی درختی که قبلا ساختیم ادامه دهیم. ابتدا بیاید تغییراتی را به مخزن اضافه کنیم. دستور های زیر را در مخزن اجرا کنید:
$ echo 'new file content' > new_file
$ git add new_file
$ echo 'changed content' >> reset_lifecycle_file
این دستور های یک فایل جدید به نام new_file
ایجاد کرده و آن را به مخزن اضافه می کند. افزون بر این، محتوای فایل RESET_LIFECYCLE_FILE
را نیز اصلاح می کند. با اعمال این تغییرات بیاید اکنون با اجرای دستور git status وضعیت مخزن را بررسی کنیم.
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
new file: new_file
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
در حال حاضر می توانیم ببینیم که تغییرات در حال انتظاری در مخزن وجود دارد. درخت Staging index دارای یک تغییر در حال انتظار برای اضافه کردن new_file
است و مسیر جاری دارای یک تغییر در حال انتظار برای تغییرات در فایل reset_lifecycle_file
است.
قبل از انجام کار بعدی، بیاید وضعیت Staging index را بررسی کنیم:
$ git ls-files -s
100644 8e66654a5477b1bf4765946147c49509a431f963 0 new_file
100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file
می توانیم ببینیم که new_file
بهindex اضافه شده است. ما تغییراتی در فایل reset_lifecycle_file
ایجاد کرده ایم اما می بینیم که هش SHA آن در Staging index (d7d77c1b04b5edd5acfc85de0b592449e5303770) بدون تغییر باقی مانده است. این رفتار مورد انتظار بود زیرا که از دستور git add
برای اضافه کردن تغییرات این فایل به Staging index
استفاده نشده بود.
اکنون بیاید دستور git reset --hard
را اجرا کنیم.
$ git reset --hard
HEAD is now at dc67808 update content of reset_lifecycle_file
و سپس وضعیت مخزن را بررسی کنیم.
$ git status
On branch master
nothing to commit, working tree clean
$ git ls-files -s
100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file
در اینجا یک "بازنشانی سخت" را با استفاده از گزینه ی --hard اجرا کردیم. خروجی git نشان می دهد که اشاره گر HEAD به آخرین commit یعنی commit dc67808 اشاره می کند. سپس با استفاده از دستور git status وضعیت مخزن را بررسی می کنیم. git نشان می دهد که تغییرات در حال انتظاری وجود ندارد. همچنین وضعیت Staging index را بررسی می کنیم و می بینیم که قبل از اضافه شدن new_file
بازنشانی شده است. تغییرات ما برای reset_lifecycle_file
و افزودن new_file
از بین رفتند. به این نکته ی مهم توجه کنید که دیگر این داده های از دست رفته را نمی توان باز گرداند.
جمع بندی
در این قسمت با دستور git reset و سه درخت گیت (مسیر جاری، تاریخچه ی commit و staging Index) آشنا شدیم و فهمیدیم که این دستور چگونه کار می کند همچنین با شیوه ی کار یکی از گزینه های ورودی این دستور نیز آشنا شدیم.
این قسمت ادامه دارد و در بخش بعدی با گزینه های دیگر این دستور آشنا خواهیم شد و مثال هایی نیز خواهیم زد و همچنین تفاوت استفاده ی دستورهای git reset و git revert را بررسی خواهیم کرد.