git reset 命令詳解(一)—— Git 學習筆記 07
git reset 命令詳解(一)
簡而言之,git reset
命令是用來將當前 branch 重置到另外一個 commit 的,這個動作可能同時影響到 index 以及 work directory.
先舉個例子,來一個感性的認識。下面這兩條命令讓 hotfix 分支向後回退兩個提交。
git checkout hotfix
git reset HEAD~2
hotfix 分支末端的兩個提交現在變成了孤兒提交。下次 Git 執行垃圾回收的時候,這兩個提交會被刪除。如果你的提交還沒有共享給別人,可以用git reset
撤銷這些提交。
三棵樹
理解 reset (以後還要說 checkout )的最簡方法,就是以 Git 的思維框架(將其作為內容管理器)來管理三棵不同的樹。“樹” 在我們這裡的實際意思是 “檔案集合”,而不是指特定的資料結構。
Git 作為一個系統,是以它的一般操作來管理並操縱這三棵樹的:
樹 | 用途 |
---|---|
HEAD | 上一次提交的快照,下一次提交的父結點 |
Index | 預期的下一次提交的快照 |
Working Directory | 沙盒 |
HEAD
HEAD
是當前分支引用的指標,它總是指向該分支上的最後一次提交。 這表示 HEAD
將是下一次提交的父結點。 通常,可以把 HEAD
看做你的上一次提交的快照。
下面是我從網上搜來的 2 張圖片,感謝原作者。
大意就是:HEAD 指向分支(branch),分支指向提交。
索引(index)
索引是你預期的下一次提交。這就是當你執行 git commit
時 Git 看起來的樣子。Git 將上一次檢出到工作目錄中的所有檔案填充到索引(暫存區),之後你會將其中一些檔案替換為新版本,接著通過 git commit
將它們轉換為樹來用作新提交。
工作目錄
另外兩棵樹以一種高效但並不直觀的方式,將它們的內容儲存在 .git
資料夾中。工作目錄會將它們解包為實際的檔案以便編輯。 你可以把工作目錄當做 “沙盒”,在你將修改提交到暫存區並記錄到歷史之前,可以隨意更改。
流程圖解
下文會用一幅幅圖說明從初始化倉庫到操作工作區,再到 add 檔案,最後到提交等整個流程。
僅工作目錄有內容
假設我們進入到一個新目錄,其中有一個檔案。 我們稱其為該檔案的 v1 版本,將它標記為藍色。 現在執行 git init
,這會建立一個 Git 倉庫,其中的 HEAD 引用指向未建立的分支(master 還不存在)。
新增到索引
現在我們想要提交這個檔案(file.txt ),所以用 git add 把工作目錄中的內容複製到索引中。
提交
接著執行 git commit,它會取得索引中的內容並將它儲存為一個永久的快照,然後建立一個指向該快照的提交物件,最後更新 master 來指向本次提交。
此時如果我們執行 git status,會發現沒有任何改動,因為現在三棵樹完全相同。
修改檔案
現在我們想要對檔案進行修改然後提交它。我們將會經歷同樣的過程;首先在工作目錄中修改檔案。 我們稱其為該檔案的 v2 版本,並將它標記為紅色。
如果現在執行 git status,我們會看到檔案顯示在 “Changes not staged for commit” 下面,並被標記為紅色,因為該條目在索引與工作目錄之間存在不同。 接著我們執行 git add 來將它暫存到索引中。
再次新增到索引
此時,由於 Index 和 HEAD 不同,若執行 git status 的話就會看到 “Changes to be committed” 下的該檔案變為綠色 ——也就是說,現在預期的下一次提交與上一次提交不同。 最後,我們執行 git commit 來完成提交。
再次提交
現在執行 git status 會沒有輸出,因為三棵樹又變得相同了。
切換分支或克隆的過程也類似。 當檢出一個分支時,它會修改 HEAD 指向新的分支引用,將索引填充為該次提交的快照,然後將索引的內容複製到工作目錄 中。
reset 的作用
在以下情景中觀察 reset 命令會更有意義。
為了演示這些例子,假設我們再次修改了 file.txt 檔案並第三次提交它。 現在的歷史看起來是這樣的:
現在,假設我們執行git reset HEAD~
(後面可能會跟不同的引數)。
第 1 步:移動 HEAD
reset 做的第一件事是移動 HEAD 的指向。 這與改變 HEAD 自身不同(checkout 所做的);reset 移動 HEAD 指向的分支。 這意味著如果 HEAD 設定為 master 分支(例如,你正在 master 分支上),執行 git reset 9e5e6a4
將會使 master 指向 9e5e6a4。
使用 reset –soft,它將僅僅停在那兒。
結合上圖,我們理解一下發生的事情:它本質上是撤銷了上一次 git commit 命令。 當你在執行 git commit 時,Git 會建立一個新的提交,並移動 HEAD 所指向的分支來使其指向該提交。 當你將它 reset 回 HEAD~(HEAD 的父結點)時,其實就是把該分支移回原來的位置,而不會改變索引和工作目錄。
第 2 步:更新索引(–mixed)
接下來,reset 會用 HEAD 指向的當前快照的內容來更新索引。
如果指定 –mixed 選項,reset 將會在這裡停止。 這也是預設行為,即如果沒有指定任何選項(在本例中是 git reset HEAD~
),reset 將會在這裡停止。
現在再看一眼上圖,理解一下發生的事情:它依然會撤銷一上次提交,但還會取消所有暫存。 於是,我們回滾到了所有 git add 和 git commit 的命令執行之前。
第 3 步:更新工作目錄(–hard)
如果使用 –hard 選項,reset 要做的的第三件事情就是讓工作目錄看起來像索引。
現在讓我們回想一下剛才發生的事情:你撤銷了最後的提交(git commit )、git add 和工作目錄中的所有工作。
必須注意,–hard 標記是 reset 命令唯一的危險用法,它也是 Git 會真正地銷燬資料的僅有的幾個操作之一。其他任何形式的 reset 呼叫都可以輕鬆撤消,但是 –hard 選項不能,因為它強制覆蓋了工作目錄中的檔案。
總結
reset 命令會以特定的順序重寫這三棵樹,在你指定以下選項時停止:
- 移動 HEAD 指向的分支 (若指定了
--soft
,則到此停止); - 重置 index 以便和 HEAD 相匹配 (若未指定
--hard
,則到此停止); - 使工作目錄看起來像索引
參考資料
【0】《Pro Git》(Scott Chacon, Ben Straub Version 2.1.14, 2018-05-19)
【1】https://www.atlassian.com/git/tutorials/resetting-checking-out-and-reverting
相關文章
- git reset 命令詳解(二)—— Git 學習筆記 08Git筆記
- git checkout 命令詳解—— Git 學習筆記 16Git筆記
- git checkout 和 git reset 的區別 —— Git 學習筆記 09Git筆記
- Git命令學習筆記Git筆記
- git cherry-pick 詳解 —— Git 學習筆記 18Git筆記
- GIT學習筆記一git initGit筆記
- git常用命令-Git學習筆記Git筆記
- Git 學習之命令詳解Git
- git status 命令總結 —— Git 學習筆記 06Git筆記
- git學習筆記(一)Git筆記
- [網摘學習]Git版本恢復命令resetGit
- git學習筆記Git筆記
- Git進階命令-resetGit
- Pro Git 學習筆記Git筆記
- Git的學習筆記Git筆記
- git學習筆記5Git筆記
- git學習筆記6Git筆記
- git學習筆記(二)Git筆記
- Git—學習筆記1Git筆記
- git學習筆記 1Git筆記
- git學習筆記1Git筆記
- Git 的特點—— Git 學習筆記 02Git筆記
- 取得 Git 倉庫 —— Git 學習筆記 04Git筆記
- git rebase(變基)—— Git 學習筆記 19Git筆記
- 【Java學習筆記(五十三)】之Git簡介,Git常用命令Java筆記Git
- Git極簡學習筆記Git筆記
- git入門學習筆記Git筆記
- Git進階學習筆記Git筆記
- git 和 vim 學習筆記Git筆記
- Git筆記-部分命令Git筆記
- git(一):瞭解、學習、安裝gitGit
- GIT學習筆記——第一章Git筆記
- git高階命令學習記錄Git
- git status命令詳解Git
- git init命令詳解Git
- git checkout命令詳解Git
- git remote 命令詳解GitREM
- git archive命令詳解GitHive