[Git] Reset - mixed, hard and soft

 Reset 區分為 mixed, hard 與 soft 三種模式

咱們就來實際演練一下,看看各自的差異及應用時機吧

前言

Git Reset可用來重置 repository 到特定 commit 結點,換句話說就是可以讓HEAD(最新的commit)移動到指定結點上;這個指令搭配了三種不同參數 soft、mixed(default)及hard 讓使用者調用,我們可以利用其特性在不同場景中達成我們的目的。

 

知識背景

要開始比較 soft、mixed及hard有什麼不同前,我們先來複習一下Git將檔案存入repository的流程。首先先來簡單定義一下流程中會使用到的三區塊。

    1. Working Tree (工作目錄):  Git管理的實體資料夾,也就是我們實際操作的資料夾(檔案)

    2. Index (系統索引):  存放一堆需要被commit的(異動)文件內容集合,把檔案加入索引稱 Stage 或 Cache。

    3. Repository (檔案庫):  是Git存放檔案的位置,許多commit結點(版本)紀錄於此。

以下簡單敘述一下把檔案存入檔案庫流程:

1. 剛開始 working tree 、 index 與 repository(HEAD)資料內容都是一致的

2. 當實體檔案被異動後,此時 working tree 資料內容就會跟 index 及 repository(HEAD)的不一致,而Git知道該那些檔案(Tracked File)被異動過,直接將檔案狀態會調整為 modified (Unstaged files)。

3. 當我們執行 git add 後,會將這些異動檔案內容加入 index 中 (Staged files),所以此時working tree跟index資料內容是一致的,但他們與repository(HEAD)資料內容不一致。

4. 接著執行 git commit 後,將Git索引中所有異動檔案內容提交至 Repository 中,建立出新的 commit 結點(HEAD)後,資料內容又會於 working tree 、 index 與 repository(HEAD) 中保持一致。

 

實際演練

有了前面的一些概念,說穿了 reset 就只是把HEAD(最新commit點)移動到指定結點上罷了,各參數之間的差異就只是在資料恢復的範圍,決定是否把原來 working tree 或 index 中的資料內容一起 reset 成指定結點。

為了實際進行演練,首先建立出如上圖的 commit 線圖

實體檔案(aoo.txt)於各提交結點之內容

執行前的狀態(HEAD),三者資料內容皆一致

 

Soft

此模式下會保留 working tree 資料內容,不會異動到目前所有的實體檔案內容;也會保留 index 資料內容,讓 index 與 working tree 資料內容是一致的。就只有 repository 中的檔案內容變更與 reset 目標結點一致,因此原始結點與 reset 結點之間的差異變更集會存在於 index中(Staged files),所以我們可以直接執行 git commit 將 index 中的資料內容提交至 repository 中。當我們想合併「當前結點」與「reset目標結點」間不具太大意義的 commit 紀錄(可能是階段性地頻繁提交)時,可以考慮使用 Soft Reset 來讓 commit 演進線圖較為清晰。

動手做做,首先執行 git reset --soft HEAD~2 來使用soft模式執行reset功能

目前 working tree 檔案內容會保留,不會被 reset

執行 git status 來查看目前檔案狀態 (發現異動保存在index中 [Staged files])

執行 git diff

比對的是「工作目錄(working tree)」與「索引(index)」之間的差異。無任何差異。

執行 git diff --cached HEAD

代表進行「當前的索引狀態(index)」與「當前分支的最新版(repo. HEAD)」比對。差異確實存在於此。

直接用SourceTree來顯示差異如下( index(Staged file) vs. repository[HEAD])

看一下線圖,HEAD已經reset到目標結點上了(存在未提交之異動)

 

Mixed (default)

此模式下會只保留Working Tree資料內容,不異動到目前所有的實體檔案內容。但會將 Index 與 Repository 中的檔案內容變更與目標結點一致,因此原始結點與Reset結點之間的差異變更集會存在於Working Tree中,Git會知道那些被追蹤檔案(Tracked File)與Index不相同,直接將檔案狀態會調整為 modified (Unstaged files)。我們可以直接執行 git add 將這些異動檔案內容加入 index 中,再執行 git commit 將 Index 中的資料內容提交至Repository中,一樣可以達到合併commit結點之效果;另外最常使用的情境為移除所有Index中準備要提交的檔案(Staged files),我們可以執行 git reset HEAD 來 Unstage 所有已列入 Index 的待提交檔案。

動手做做,執行 git reset HEAD~2 來使用mixed模式執行reset功能

目前working tree檔案內容會保留,不會被 reset

執行 git status 來查看目前檔案狀態 (異動保存在working tree中 [Unstaged files])

執行 git diff

比對的是「工作目錄(working tree)」與「索引(index)」之間的差異。差異確實存在於此。

直接用SourceTree來顯示差異如下(working tree (unstaged files) vs. index )

執行 git diff --cached HEAD

代表進行「當前的索引狀態(index)」與「當前分支的最新版(repo. HEAD)」比對。無任何差異。

看一下線圖,HEAD已經reset到目標結點上了(存在未提交之異動)

 

Hard

此種模式完全不保留原始 commit 結點的任何資訊,會連同資料夾中實體檔案內容都進行重置,也就是直接將 working Tree、index 及 repository 都重置成目標Reset結點的資料內容。使用情境可以是要放棄目前本地的所有變更時,執行 git reset -hard HEAD 來強制恢復資料內容及狀態;亦或是真的想拋棄目標結點後的所有變更。

動手做做,執行 git reset --hard HEAD~2 來使用 hard 模式執行reset功能

目前working tree檔案內容就不會保留了,檔案內容直接 reset 成目標結點

執行 git status 來查看目前檔案狀態 (沒有任何異動存在)

執行 git diff

比對的是「工作目錄(working tree)」與「索引(index)」之間的差異。無任何差異。

執行 git diff --cached HEAD

代表進行「當前的索引狀態(index)」與「當前分支的最新版(repo. HEAD)」比對。無任何差異。

看一下線圖,HEAD已經reset到目標結點上了(沒有任何未提交之異動)

 

復原方式

如果不小心執行 reset 或 rebase 等可能會造成版本號消失的指令時,而我們想要恢復但卻在演進線圖(commit log)上找不到commit結點,這個時候就可透過 git reflog 找出所有歷史紀錄,透過 HEAD@{N} 這個特殊的「參考名稱」來對此版本「定位」,進而使用 git reset --hard HEAD@{N} 指令回歸原有版本。

剛執行了 git reset --hard HEAD~2 來重置,此事若後悔想要恢復,線圖是找不到原始commit節點的

這時候可以執行 git reflog 列出所有歷史紀錄,其中HEAD@{0}~HEAD@{4}都是剛剛進行測試時所產生的,因此需要恢復到執行 reset 指令之前的 commit 結點,也就是HEAD@{5}這個結點位置。

執行 git reset --hard HEAD@{5} 回歸原始版本,會連同working tree(工作目錄)檔案也會一併恢復。

看一下線圖,真的回來了,真的沒有什麼回不來的~

 

參考資訊

Reset, Checkout, and Revert

git reset soft,hard,mixed之區別深解


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !