git reset
命令用於將當前HEAD
復位到指定狀態。一般用於撤消之前的一些操作(如:git add
,git commit
等)。
簡介
git reset [-q] [<tree-ish>] [--] <paths>…?
git reset (--patch | -p) [<tree-ish>] [--] [<paths>…?]
git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
在第一和第二種形式中,將條目從<tree-ish>
複製到索引。 在第三種形式中,將當前分支頭(HEAD
)設定為<commit>
,可選擇修改索引和工作樹進行匹配。所有形式的<tree-ish>/<commit>
預設為 HEAD
。
這裡的 HEAD
關鍵字指的是當前分支最末梢最新的一個提交。也就是版本庫中該分支上的最新版本。
以下是一些範例 -
在git的一般使用中,如果發現錯誤的將不想暫存的檔案被git add
進入索引之後,想回退取消,則可以使用命令:git reset HEAD <file>
,同時git add
完畢之後,git也會做相應的提示,比如:
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: test.py
git reset [--hard|soft|mixed|merge|keep] [<commit>或HEAD]
:將當前的分支重設(reset
)到指定的<commit>
或者HEAD
(預設,如果不顯示指定<commit>
,預設是HEAD
,即最新的一次提交),並且根據[mode]
有可能更新索引和工作目錄。mode
的取值可以是hard
、soft
、mixed
、merged
、keep
。下面來詳細說明每種模式的意義和效果。
A). --hard
:重設(reset) 索引和工作目錄,自從<commit>
以來在工作目錄中的任何改變都被丟棄,並把HEAD指向<commit>
。
下面是具體一個例子,假設有三個commit, 執行 git status
結果如下:
commit3: add test3.c
commit2: add test2.c
commit1: add test1.c
執行git reset --hard HEAD~1
命令後,
顯示:HEAD is now at commit2
,執行git log
,如下所示 -
commit2: add test2.c
commit1: add test1.c
下面列出一些git reset的典型的應用場景:
(A) 回滾新增操作
$ edit file1.c file2.c # (1)
$ git add file1.c file1.c # (1.1) 新增兩個檔案到暫存
$ mailx # (2)
$ git reset # (3)
$ git pull git://info.example.com/ nitfol # (4)
(1). 編輯檔案 file1.c
, file2.c
,做了些更改,並把更改新增到了暫存區。
(2). 檢視郵件,發現某人要您執行git pull
,有一些改變需要合併下來。
(3). 然而,您已經把暫存區搞亂了,因為暫存區同HEAD commit不匹配了,但是即將git pull
下來的東西不會影響已經修改的file1.c
和 file2.c
,因此可以revert
這兩個檔案的改變。在revert後,那些改變應該依舊在工作目錄中,因此執行git reset
。
(4). 然後,執行了git pull
之後,自動合併,file1.c
和 file2.c
這些改變依然在工作目錄中。
(B)回滾最近一次提交
$ git commit -a -m "這是提交的備註資訊"
$ git reset --soft HEAD^ #(1)
$ edit code #(2) 編輯程式碼操作
$ git commit -a -c ORIG_HEAD #(3)
(1) 當提交了之後,又發現程式碼沒有提交完整,或者想重新編輯一下提交的資訊,可執行git reset --soft HEAD^
,讓工作目錄還跟reset
之前一樣,不作任何改變。HEAD^
表示指向HEAD
之前最近的一次提交。
(2) 對工作目錄下的檔案做修改,比如:修改檔案中的程式碼等。
(3) 然後使用reset
之前那次提交的注釋、作者、日期等資訊重新提交。注意,當執行git reset
命令時,git會把老的HEAD拷貝到檔案.git/ORIG_HEAD
中,在命令中可以使用ORIG_HEAD參照這個提交。git commit
命令中 -a
引數的意思是告訴git,自動把所有修改的和刪除的檔案都放進暫存區,未被git跟蹤的新建的檔案不受影響。commit
命令中-c <commit>
或者 -C <commit>
意思是拿已經提交的物件中的資訊(作者,提交者,注釋,時間戳等)提交,那麼這條git commit
命令的意思就非常清晰了,把所有更改的檔案加入暫存區,並使用上次的提交資訊重新提交。
(C) 回滾最近幾次提交,並把這幾次提交放到指定分支中
回滾最近幾次提交,並把這幾次提交放到叫做topic/wip
的分支上去。
$ git branch topic/wip (1)
$ git reset --hard HEAD~3 (2)
$ git checkout topic/wip (3)
(1) 假設已經提交了一些程式碼,但是此時發現這些提交還不夠成熟,不能進入master
分支,希望在新的branch
上暫存這些改動。因此執行了git branch
命令在當前的HEAD上建立了新的叫做 topic/wip
的分支。
(2) 然後回滾master
分支上的最近三次提交。HEAD~3
指向當前HEAD-3
個提交,git reset --hard HEAD~3
,即刪除最近的三個提交(刪除HEAD
, HEAD^
, HEAD~2
),將HEAD指向HEAD~3
。
(D) 永久刪除最後幾個提交
$ git commit ## 執行一些提交
$ git reset --hard HEAD~3 (1)
(1) 最後三個提交(即HEAD
, HEAD^
和HEAD~2
)提交有問題,想永久刪除這三個提交。
(E) 回滾merge和pull操作
$ git pull (1)
Auto-merging nitfol
CONFLICT (content): Merge conflict in nitfol
Automatic merge failed; fix conflicts and then commit the result.
$ git reset --hard (2)
$ git pull . topic/branch (3)
Updating from 41223... to 13134...
Fast-forward
$ git reset --hard ORIG_HEAD (4)
`
(1) 從origin
拉取下來一些更新,但是產生了很多衝突,但您暫時沒有這麼多時間去解決這些衝突,因此決定稍候有空的時候再重新執行git pull
操作。
(2) 由於git pull
操作產生了衝突,因此所有拉取下來的改變尚未提交,仍然再暫存區中,這種情況下git reset --hard
與 git reset --hard HEAD
意思相同,即都是清除索引和工作區中被搞亂的東西。
(3) 將topic/branch
分支合併到當前的分支,這次沒有產生衝突,並且合併後的更改自動提交。
(4) 但是此時又發現將topic/branch
合併過來為時尚早,因此決定退滾合併,執行git reset --hard ORIG_HEAD
回滾剛才的pull/merge
操作。說明:前面講過,執行git reset
時,git會把reset
之前的HEAD放入.git/ORIG_HEAD
檔案中,命令列中使用ORIG_HEAD參照這個提交。同樣的,執行git pull
和git merge
操作時,git都會把執行操作前的HEAD放入ORIG_HEAD
中,以防回滾操作。
(F) 在汙染的工作區中回滾合併或者拉取
$ git pull (1)
Auto-merging nitfol
Merge made by recursive.
nitfol | 20 +++++----
...
$ git reset --merge ORIG_HEAD (2)
(1) 即便你已經在本地更改了工作區中的一些東西,可安全的執行git pull
操作,前提是要知道將要git pull
下面的內容不會覆蓋工作區中的內容。
(2) git pull
完後,發現這次拉取下來的修改不滿意,想要回滾到git pull
之前的狀態,從前面的介紹知道,我們可以執行git reset --hard ORIG_HEAD
,但是這個命令有個副作用就是清空工作區,即丟棄本地未使用git add
的那些改變。為了避免丟棄工作區中的內容,可以使用git reset --merge ORIG_HEAD
,注意其中的--hard
換成了 --merge
,這樣就可以避免在回滾時清除工作區。
(G) 中斷的工作流程處理
在實際開發中經常出現這樣的情形:你正在開發一個大的新功能(工作在分支:feature
中),此時來了一個緊急的bug需要修復,但是目前在工作區中的內容還沒有成型,還不足以提交,但是又必須切換的另外的分支去修改bug。請看下面的例子 -
$ git checkout feature ;# you were working in "feature" branch and
$ work work work ;# got interrupted
$ git commit -a -m "snapshot WIP" (1)
$ git checkout master
$ fix fix fix
$ git commit ;# commit with real log
$ git checkout feature
$ git reset --soft HEAD^ ;# go back to WIP state (2)
$ git reset (3)
(1) 這次屬於臨時提交,因此隨便新增一個臨時注釋即可。
(2) 這次reset
刪除了WIP commit,並且把工作區設定成提交WIP快照之前的狀態。
(3) 此時,在索引中依然遺留著「snapshot WIP」提交時所做的未提交變化,git reset
將會清理索引成為尚未提交」snapshot WIP「時的狀態便於接下來繼續工作。
(H) 重置單獨的一個檔案
假設你已經新增了一個檔案進入索引,但是而後又不打算把這個檔案提交,此時可以使用git reset
把這個檔案從索引中去除。
$ git reset -- frotz.c (1)
$ git commit -m "Commit files in index" (2)
$ git add frotz.c (3)
(1) 把檔案frotz.c
從索引中去除,
(2) 把索引中的檔案提交
(3) 再次把frotz.c
加入索引
(I) 保留工作區並丟棄一些之前的提交
假設你正在編輯一些檔案,並且已經提交,接著繼續工作,但是現在你發現當前在工作區中的內容應該屬於另一個分支,與之前的提交沒有什麼關係。此時,可以開啟一個新的分支,並且保留著工作區中的內容。
$ git tag start
$ git checkout -b branch1
$ edit
$ git commit ... (1)
$ edit
$ git checkout -b branch2 (2)
$ git reset --keep start (3)
(1) 這次是把在branch1
中的改變提交了。
(2) 此時發現,之前的提交不屬於這個分支,此時新建了branch2
分支,並切換到了branch2
上。
(3) 此時可以用reset --keep
把在start
之後的提交清除掉,但是保持工作區不變。