git reset和git revert都可以用于撤銷已存在的commit,其中g(shù)it reset可以細(xì)化到針對單個文件進(jìn)行操作。
????首先, 一個Git倉庫主要有三個重要的組成:工作目錄,緩存區(qū)和提交歷史,如圖所示:
1. git reset
git reset通常用來撤銷對緩存區(qū)和工作目錄的修改。用法如下:
(1)git reset <file>
????????從緩存區(qū)移除特定的文件,但不改變工作目錄。
(2)git reset
????????重設(shè)緩存區(qū),匹配最近的一次提交,工作目錄不變。
(3)git reset --hard
????????重設(shè)緩存區(qū)和工作目錄,匹配最近的一次提交。除了取消緩存之外,--hard標(biāo)記還會清除工作目錄中所有未提交的更改,因此使用前確定你想扔掉你所有的本地開發(fā)。
(4)git reset <commit>
????????將當(dāng)前分支的末端移動到commit,將緩存區(qū)重設(shè)到這個commit,但不改變工作目錄。
(5)git reset --hard <commit>
????????將當(dāng)前分支的末端移動到commit,并將緩存區(qū)和工作目錄都重設(shè)到這個commit。
git reset命令會將HEAD指向你指定的commit,也就是說,可以直接通過git reset命令來移除你指定的commit之后的所有commit。例如,下面這兩條命令讓hotfix分支向后回退了兩個commit:
git checkout hotfix
git reset HEAD~2
注意,hotfix分支現(xiàn)在最后的兩個commit變成了懸掛提交,這意味著下次git進(jìn)行垃圾回收的時候,這兩個commit就會被刪除。因此,如果你希望改變此git倉庫的提交歷史,你可以使用git reset命令。
除了在當(dāng)前分支上操作,你還可以傳入以下參數(shù)來更新緩存區(qū)和工作區(qū):
--soft 緩存區(qū)和工作區(qū)都不會改變
--mixed 默認(rèn)選項。緩存區(qū)和你指定的commit同步,工作區(qū)不發(fā)生改變。
--hard 緩存區(qū)和工作區(qū)都同步到你指定的commit
這些參數(shù)往往針對HEAD來使用。例如你add了某個你不想add的文件,這時可以運行g(shù)it --mixed reset HEAD,此時暫存區(qū)被清空,而你的工作空間并沒有改變。
或者你希望將最近的兩個commit合并成為一個,此時你可以運行g(shù)it --mixed reset HEAD~2,這樣你的commit記錄被改變,緩存區(qū)被清空,但工作空間沒有改變,因此你可以重新add,再重新commit,這樣兩個commit記錄就變成了一個。
另一方面,你希望完全放棄你沒有提交的改動,你可以運行g(shù)it reset --hard HEAD,這樣你的緩存區(qū)和工作空間就被強(qiáng)行更新了。
針對已經(jīng)提交到遠(yuǎn)程的commit,你希望reset到HEAD~1,此時運行:
(1)git reset HEAD~1 此時查看status,發(fā)現(xiàn)有待stage的文件,因為工作空間的文件與HEAD~1的文件不同,因此運行g(shù)it checkout future2.txt即可。(或者最開始就運行:git reset --hard HEAD~1)
(2)然后執(zhí)行強(qiáng)制推送:git push -f
git reset可以針對某個文件,此時reset命令會將你指定的文件加入到緩存區(qū)中(因此文件從HEAD狀態(tài)變成了你指定的某個commit的狀態(tài))。
可以看到,暫存區(qū)產(chǎn)生了一個變更,這是因為文件從HEAD的內(nèi)容變成了HEAD~2的內(nèi)容(邏輯上),實際上,工作區(qū)域不會發(fā)生改變。而產(chǎn)生Changes not staged for commit的原因是此時工作區(qū)的文件和HEAD~2的文件不相同。
將暫存區(qū)的內(nèi)容commit并push之后,此時future2.txt文件內(nèi)容就回退為了兩個commit之前的版本,并且commit log都沒有更改。此時查看status:
發(fā)現(xiàn)future2.txt依舊待stage,這是因為git reset命令工作區(qū)不會發(fā)生改變,因此工作區(qū)的文件內(nèi)容與HEAD下的文件內(nèi)容不一致,此時運行:
git checkout future2.txt
來撤銷所有的更改。再次查看status:
并且此時future2的文件內(nèi)容已經(jīng)處于最新狀態(tài)。
git reset總結(jié):(1)git reset在提交級別上,可以撤銷已有的commit,并且清除git log,適用于消除還未提交到遠(yuǎn)程的commit,或者是自己私有的分支,或者并不在意篡改commit log的情況。
(2)git reset針對單個文件時,工作區(qū)不會被改變,暫存區(qū)一定改變。并且不會清除git log,往往最后需要通過git checkout <file>來同步工作區(qū)。 --soft、--mixed?和?--hard?對文件層面的?git reset?毫無作用,因為緩存區(qū)中的文件一定會變化,而工作目錄中的文件一定不變。
2. git revert
git revert命令可以通過指定commit id來對某個commit進(jìn)行撤銷,此命令會生成一個新的commit來執(zhí)行撤銷動作,不會修改過去已有的commit,這避免了Git丟失項目歷史,這一點對你的版本歷史和協(xié)作的可靠性來說是很重要的。git revert用法如下:
(1)git revert <commit>
????????生成了一個新的提交,此提交用于撤銷<commit>引入的修改。
例如,下面的命令會撤銷HEAD前面的第二個commit:
git checkout hotfix
git revert HEAD~2
相比?git reset,它不會改變現(xiàn)在的提交歷史。因此,git revert?可以用在公共分支上,git reset?應(yīng)該用在私有分支上。你也可以把?git revert?當(dāng)作撤銷已經(jīng)提交的更改,而?git reset HEAD?用來撤銷沒有提交的更改。
3. git checkout
git checkout這個命令有三個作用:檢出文件,檢出提交和檢出分支。
(1)git checkout master
????????回到master分支。
(2)git checkout <commit> <file>
????????查看文件之前的版本。此時工作目錄中下的<file>被替換為<commit>中的<file>,并將它加入緩存區(qū)。
(3)git checkout <commit>
????????更新工作目錄中的所有文件,使得和某個特定提交下的文件一致。此操作會使你處于HEAD分離的狀態(tài),因此是只讀操作。
git checkout命令除了用于常見的分支切換以外,還可以用于代碼更改的撤銷。例如:
當(dāng) future2.txt 存在還沒有add的changes時,運行g(shù)it checkout future2.txt 可以撤銷future2.txt的更改
git checkout? HEAD~2 future2.txt 可以將當(dāng)前的future2.txt恢復(fù)為HEAD~2的版本,此時工作區(qū)和暫存區(qū)都會改變。
總結(jié):如果文件A修改了,你希望撤銷這個修改,那么可以使用以下方案:
(1)修改還未加入暫存區(qū)(還沒有add),直接運行:
? ? ? ? ?git checkout A
(2)修改已經(jīng)加入了暫存區(qū),但還沒有commit:
? ? ? ? ?git reset HEAD A(修改暫存區(qū))
? ? ? ? ?git checkout A(修改工作區(qū))
(3)修改已經(jīng)commit
? ? ? ? ?git reset HEAD~1(reset到前一個commit)
? ? ? ? ?git checkout A