天天看點

git reset 詳解

有時候,我們用Git的時候有可能commit送出代碼後,發現這一次commit的内容是有錯誤的,那麼有兩種處理方法:

1、修改錯誤内容,再次commit一次

2、使用git reset 指令撤銷這一次錯誤的commit

第一種方法比較直接,但會多次一次commit記錄。

而我個人更傾向第二種方法,錯誤的commit沒必要保留下來。

那麼今天來說一下git reset。它的一句話概括

git-reset - Reset current HEAD to the specified state      

意思就是可以讓HEAD這個指針指向其他的地方。例如我們有一次commit不是不是很滿意,需要回到上一次的Commit裡面。那麼這個時候就需要通過reset,把HEAD指針指向上一次的commit的點。

它有三種模式,soft,mixed,hard,具體的使用方法下面這張圖,展示的很全面。

另外git reset --hard HEAD^ 等價于git reset --hard xxx(xxx為commit id)

git reset 詳解

git各個區域和指令關系

這三個模式了解了,對于使用這個指令很有幫助。在了解這三個模式之前,需要略微知道一點Git的基本流程。正如上圖,Git會有三個區域:

  • Working Tree 目前的工作區域
  • Index/Stage 暫存區域,和git stash指令暫存的地方不一樣。使用git add xx,就可以将xx添加近Stage裡面
  • Repository 送出的曆史,即使用git commit送出後的結果
git reset 詳解

檔案存入Repository流程

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

  1. 剛開始 working tree 、 index 與 repository(HEAD)裡面的內容都是一緻的

階段1

  1. 當git管理的檔案夾裡面的内容出現改變後,此時 working tree 的內容就會跟 index 及 repository(HEAD)的不一緻,而Git知道是哪些檔案(Tracked File)被改動過,直接将檔案狀态設定為 modified (Unstaged files)。

階段2

  1. 當我們執行 git add 後,會将這些改變的檔案內容加入 index 中 (Staged files),是以此時working tree跟index的內容是一緻的,但他們與repository(HEAD)內容不一緻。

階段3

  1. 接着執行 git commit 後,將Git索引中所有改變的檔案內容送出至 Repository 中,建立出新的 commit 節點(HEAD)後, working tree 、 index 與與repository(HEAD)區域的内容 又會保持一緻。

階段4

實戰示範

reset --hard:重置stage區和工作目錄:

reset --hard 會在重置 HEAD 和branch的同時,重置stage區和工作目錄裡的内容。當你在 reset 後面加了 --hard 參數時,你的stage區和工作目錄裡的内容會被完全重置為和HEAD的新位置相同的内容。換句話說,就是你的沒有commit的修改會被全部擦掉。

例如你在上次 commit 之後又對檔案做了一些改動:把修改後的ganmes.txt檔案add到stage區,修改後的shopping list.txt保留在工作目錄

git status      
git reset 詳解

最初狀态

然後,你執行了reset并附上了--hard參數:

git reset --hard HEAD^      

你的 HEAD 和目前 branch 切到上一條commit 的同時,你工作目錄裡的新改動和已經add到stage區的新改動也一起全都消失了:

git status      
git reset 詳解

reset --hard head^之後

可以看到,在 reset --hard 後,所有的改動都被擦掉了。

reset --soft:保留工作目錄,并把重置 HEAD 所帶來的新的差異放進暫存區

reset --soft 會在重置 HEAD 和 branch 時,保留工作目錄和暫存區中的内容,并把重置 HEAD 所帶來的新的差異放進暫存區。

什麼是「重置 HEAD 所帶來的新的差異」?就是這裡:

由于 HEAD 從 4 移動到了 3,而且在 reset 的過程中工作目錄和暫存區的内容沒有被清理掉,是以 4 中的改動在 reset 後就也成了工作目錄新增的「工作目錄和 HEAD 的差異」。這就是上面一段中所說的「重置 HEAD 所帶來的差異」。

此模式下會保留 working tree工作目錄的內容,不會改變到目前所有的git管理的檔案夾的內容;也會

保留 index暫存區的內容,讓 index 暫存區與 working tree 工作目錄的內容是一緻的。就隻有 repository 中的內容的更變需要與 reset 目标節點一緻,是以原始節點與reset節點之間的差異變更集合會存在與index暫存區中(Staged files),是以我們可以直接執行 git commit 將 index暫存區中的內容送出至 repository 中。當我們想合并「目前節點」與「reset目标節點」之間不具太大意義的 commit 記錄(可能是階段性地頻繁送出)時,可以考慮使用 Soft Reset 來讓 commit 演進線圖較為清晰點。

git reset 詳解

是以在同樣的情況下,還是老樣子:把修改後的ganmes.txt檔案add到stage區,修改後的shopping list.txt保留在工作目錄

git status      
git reset 詳解

最初狀态

假設此時目前 commit 的改動内容是新增了 laughters.txt 檔案:

git show --stat      
git reset 詳解

git show --stat

如果這時你執行:

git reset --soft HEAD^      

那麼除了 HEAD 和它所指向的 branch1 被移動到 HEAD^ 之外,原先 HEAD 處 commit 的改動(也就是那個 laughters.txt 檔案)也會被放進暫存區:

git status      
git reset 詳解

使用git reset --soft HEAD^後

這就是--soft 和 --hard 的差別:--hard 會清空工作目錄和暫存區的改動,*而 --soft則會保留工作目錄的内容,并把因為保留工作目錄内容所帶來的新的檔案差異放進暫存區。

reset 不加參數(mixed):保留工作目錄,并清空暫存區

reset 如果不加參數,那麼預設使用 --mixed 參數。它的行為是:保留工作目錄,并且清空暫存區。也就是說,工作目錄的修改、暫存區的内容以及由 reset 所導緻的新的檔案差異,都會被放進工作目錄。簡而言之,就是「把所有差異都混合(mixed)放在工作目錄中」。

還以同樣的情況為例:

git status      
git reset 詳解

最初狀态

修改了 的games.txt 和 shopping list.txt,并把 games.txt 放進了暫存區。

git show --stat      
git reset 詳解

git show --stat

最新的 commit 中新增了 laughters.txt 檔案。

這時如果你執行無參數的reset或者帶--mixed參數:

git reset HEAD^
git reset --mixed HEAD^      

工作目錄的内容和 --soft 一樣會被保留,但和 --soft 的差別在于,它會把暫存區清空,并把原節點和reset節點的差異的檔案放在工作目錄,總而言之就是,工作目錄的修改、暫存區的内容以及由 reset 所導緻的新的檔案差異,都會被放進工作目錄

git status      
git reset 詳解

git reset HEAD^之後

總結

reset 的本質:移動 HEAD 以及它所指向的 branch

實質上,reset 這個指令雖然可以用來撤銷 commit ,但它的實質行為并不是撤銷,而是移動 HEAD ,并且「捎帶」上 HEAD 所指向的 branch(如果有的話)。也就是說,reset 這個指令的行為其實和它的字面意思 "reset"(重置)十分相符:它是用來重置 HEAD 以及它所指向的 branch 的位置的。

而 reset --hard HEAD^ 之是以起到了撤銷 commit 的效果,是因為它把 HEAD 和它所指向的 branch 一起移動到了目前 commit 的父 commit 上,進而起到了「撤銷」的效果:

git reset

Git 的曆史隻能往回看,不能向未來看,是以把 HEAD 和 branch 往回移動,就能起到撤回 commit 的效果。

是以同理,reset --hard 不僅可以撤銷送出,還可以用來把 HEAD 和 branch 移動到其他的任何地方。

git reset --hard branch2      

reset三種模式差別和使用場景

  1. --hard:重置位置的同時,直接将 working Tree工作目錄、 index 暫存區及 repository 都重置成目标Reset節點的內容,是以效果看起來等同于清空暫存區和工作區。
  2. --soft:重置位置的同時,保留working Tree工作目錄和index暫存區的内容,隻讓repository中的内容和 reset 目标節點保持一緻,是以原節點和reset節點之間的【差異變更集】會放入index暫存區中(Staged files)。是以效果看起來就是工作目錄的内容不變,暫存區原有的内容也不變,隻是原節點和Reset節點之間的所有差異都會放到暫存區中。
  3. --mixed(預設):重置位置的同時,隻保留Working Tree工作目錄的內容,但會将 Index暫存區 和 Repository 中的內容更改和reset目标節點一緻,是以原節點和Reset節點之間的【差異變更集】會放入Working Tree工作目錄中。是以效果看起來就是原節點和Reset節點之間的所有差異都會放到工作目錄中。
  1. --hard:(1) 要放棄目前本地的所有改變時,即去掉所有add到暫存區的檔案和工作區的檔案,可以執行 git reset -hard HEAD 來強制恢複git管理的檔案夾的內容及狀态;(2) 真的想抛棄目标節點後的所有commit(可能覺得目标節點到原節點之間的commit送出都是錯了,之前所有的commit有問題)。
  2. --soft:原節點和reset節點之間的【差異變更集】會放入index暫存區中(Staged files),是以假如我們之前工作目錄沒有改過任何檔案,也沒add到暫存區,那麼使用reset --soft後,我們可以直接執行 git commit 將 index暫存區中的內容送出至 repository 中。為什麼要這樣呢?這樣做的使用場景是:假如我們想合并「目前節點」與「reset目标節點」之間不具太大意義的 commit 記錄(可能是階段性地頻繁送出,就是開發一個功能的時候,改或者增加一個檔案的時候就commit,這樣做導緻一個完整的功能可能會好多個commit點,這時假如你需要把這些commit整合成一個commit的時候)時,可以考慮使用reset --soft來讓 commit 演進線圖較為清晰。總而言之,可以使用--soft合并commit節點。
  3. --mixed(預設):(1)使用完reset --mixed後,我們可以直接執行 git add 将這些改變果的檔案內容加入 index 暫存區中,再執行 git commit 将 Index暫存區 中的內容送出至Repository中,這樣一樣可以達到合并commit節點的效果(與上面--soft合并commit節點差不多,隻是多了git add添加到暫存區的操作);(2)移除所有Index暫存區中準備要送出的檔案(Staged files),我們可以執行 git reset HEAD 來 Unstage 所有已列入 Index暫存區 的待送出的檔案。(有時候發現add錯檔案到暫存區,就可以使用指令)。(3)commit送出某些錯誤代碼,或者沒有必要的檔案也被commit上去,不想再修改錯誤再commit(因為會留下一個錯誤commit點),可以回退到正确的commit點上,然後所有原節點和reset節點之間差異會傳回工作目錄,假如有個沒必要的檔案的話就可以直接删除了,再commit上去就OK了。