English ▾ 主題 ▾ 最新版本 ▾ git-reset 最後更新於 2.54.0

名稱

git-reset - 將 HEAD 或索引(index)重設至已知狀態

概要

git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
git reset [-q] [<tree-ish>] [--] <pathspec>…​
git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>…​]

描述

git reset 可執行以下任一操作

  1. git reset [<模式>] <提交> 會變更 HEAD 所指向的提交。這使得復原各種 Git 操作成為可能,例如 commit、merge、rebase 和 pull。

  2. 當您指定檔案或目錄,或傳入 --patch 時,git reset 會更新指定檔案的暫存(staged)版本。

    git reset [<模式>] [<提交>]

    將目前分支的 head (HEAD) 指向 <提交>。根據 <模式>,同時更新工作目錄及/或索引以符合 <提交> 的內容。<提交> 預設為 HEAD。在操作之前,ORIG_HEAD 會被設為當前分支的頂端。

    <模式> 必須為下列其中之一(預設為 --mixed

    --mixed

    保持工作目錄不變。更新索引以符合新的 HEAD,因此不會有任何東西被暫存。

    若指定 -N,則將移除的路徑標記為意圖加入(見 git-add[1])。

    --soft

    保持工作區檔案和索引不變。例如,如果您沒有暫存的變更,可以使用 git reset --soft HEAD~5; git commit 將最近 5 次提交合併為 1 次提交。即使工作區中有變更也能運作(變更會被保留),但這種用法可能會導致混淆。

    --hard

    <提交> 中的版本覆寫所有檔案和目錄,且可能會覆寫未追蹤的檔案。不在 <提交> 中的已追蹤檔案將被移除,以使工作區符合 <提交>。更新索引以符合新的 HEAD,因此不會有任何東西被暫存。

    --merge

    重設索引並更新工作區中那些在 <提交>HEAD 之間有所不同的檔案,但保留那些在索引與工作區之間不同的檔案(即尚未加入暫存區的變更)。主要用於重設未合併的索引條目,例如在某些情況下由 git am -3git switch -m 留下的條目。若一個在 <提交> 與索引之間不同的檔案有未暫存的變更,重設將會中斷。

    --keep

    重設索引條目並更新工作區中那些在 <提交>HEAD 之間有所不同的檔案。若一個在 <提交>HEAD 之間不同的檔案有本地變更,重設將會中斷。

    --recurse-submodules
    --no-recurse-submodules

    當更新工作區時,使用 --recurse-submodules 將會根據超級專案(superproject)中記錄的提交,遞迴地重設所有啟動中的子模組工作區,並將這些子模組的 HEAD 設為在該提交處分離(detached)。

    git reset [-q] [<樹對象>] [--] <路徑規格>...
    git reset [-q] [--pathspec-from-file=<檔案> [--pathspec-file-nul]] [<樹對象>]

    對於所有指定的檔案或目錄,將暫存版本設為給定提交或樹對象中的版本(預設為 HEAD)。

    這意味著 git reset <路徑規格>git add <路徑規格> 的相反操作:它會將指定檔案或目錄的所有變更取消暫存。這等同於 git restore --staged <路徑規格>...

    在此模式下,git reset 只更新索引(不會更新 HEAD 或工作區檔案)。如果您也想更新檔案以及索引條目,請使用 git-restore[1]

    git reset (--patch | -p) [<樹對象>] [--] [<路徑規格>...]

    互動式地選擇索引與指定提交或樹對象(預設為 HEAD)之間差異的變更。索引會根據所選的變更進行修改。

    這意味著 git reset -pgit add -p 的相反操作,亦即您可以使用它來選擇性地取消暫存變更。參見 git-add[1] 的「互動模式」章節以學習如何使用 --patch 選項。

請參閱 git[1] 中的「重設 (Reset)、還原 (restore) 與復原 (revert)」以了解這三個命令之間的差異。

選項

-q
--quiet

安靜模式,僅報告錯誤。

--refresh
--no-refresh

在混合模式重設後重新整理索引。預設為啟用。

--pathspec-from-file=<檔案>

路徑規格(pathspec)透過 <file> 傳遞,而不是透過命令列參數。如果 <file> 正好是 -,則使用標準輸入。路徑規格元素由 LFCR/LF 分隔。路徑規格元素可以依照配置變數 core.quotePath 的說明進行引號處理(見 git-config[1])。另請參閱 --pathspec-file-nul 和全域的 --literal-pathspecs

--pathspec-file-nul

僅在使用 --pathspec-from-file 時有意義。路徑規格元素由 NUL 字元分隔,所有其他字元都按字面意思解釋(包括換行符和引號)。

-U<n>
--unified=<n>

產生帶有 <n> 行前後內容的差異。前後內容行數預設為 diff.context,如果未設定配置變數則為 3。(由於歷史意外,不帶 <n>-U 會被默默接受為 -p 的同義詞)。

--inter-hunk-context=<n>

顯示差異修改塊之間的內容,最多到指定的 <n> 行,從而融合彼此接近的修改塊。預設為 diff.interHunkContext,如果未設定配置選項則為 0。

--

不要將後續的任何參數解釋為選項。

<路徑規格>...

限制受操作影響的路徑。

更多詳情請參閱 gitglossary[7] 中的 pathspec 項目。

範例

取消 add(取消暫存)
$ edit                                     (1)
$ git add frotz.c filfre.c
$ mailx                                    (2)
$ git reset                                (3)
$ git pull git://info.example.com/ nitfol  (4)
  1. 您正愉快地進行工作,並發現這些檔案中的變更順序良好。您在執行 git diff 時不想看到它們,因為您打算處理其他檔案,而這些檔案的變更會造成干擾。

  2. 有人要求您 pull,且這些變更聽起來值得合併。

  3. 然而,您已經弄髒了索引(即您的索引與 HEAD 提交不符)。但您知道即將進行的 pull 不會影響 frotz.cfilfre.c,因此您還原這兩個檔案的索引變更。您在工作區中的變更將保持原樣。

  4. 然後您就可以執行 pull 和 merge,並將 frotz.cfilfre.c 的變更保留在工作區中。

取消提交並重新提交
$ git commit ...
$ git reset --soft HEAD^      (1)
$ edit                        (2)
$ git commit -a -c ORIG_HEAD  (3)
  1. 這通常在您記得剛提交的內容不完整,或者提交訊息拼寫錯誤(或兩者皆是)時執行。這會保持工作區狀態為「重設」之前的模樣。

  2. 修正工作區中的檔案。

  3. 「reset」會將舊的 head 複製到 .git/ORIG_HEAD;以原始的提交訊息重新提交。如果您不需要進一步編輯訊息,可以使用 -C 選項。

    另請參見 git-commit[1]--amend 選項。

取消提交,使其成為主題分支
$ git branch topic/wip          (1)
$ git reset --hard HEAD~3       (2)
$ git switch topic/wip          (3)
  1. 您進行了一些提交,但意識到它們太早進入 master 分支。您希望在主題分支中繼續完善它們,因此從當前的 HEAD 建立 topic/wip 分支。

  2. 將 master 分支倒轉以刪除這三次提交。

  3. 切換至 topic/wip 分支並繼續工作。

永久取消提交
$ git commit ...
$ git reset --hard HEAD~3   (1)
  1. 最近三次提交(HEAD, HEAD^, 和 HEAD~2)是有問題的,您不希望再看到它們。若您已經將這些提交提供給其他人,請絕對不要這樣做。(參見 git-rebase[1] 中的「從上游 rebase 恢復」章節了解相關影響。)

取消合併或拉取(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. 嘗試從上游更新導致了許多衝突;您目前沒有時間處理這些合併,因此決定稍後再處理。

  2. 「pull」尚未建立合併提交,因此 git reset --hard(即 git reset --hard HEAD 的同義詞)會清除索引檔案和工作區中的混亂。

  3. 將主題分支合併到當前分支,結果為快轉(fast-forward)。

  4. 但您決定主題分支尚未準備好公開。 「pull」或「merge」總是會將當前分支的原始頂端保留在 ORIG_HEAD 中,因此重設至該點會將索引檔案和工作區恢復至該狀態,並將分支頂端重設至該提交。

在髒的工作區中取消合併或拉取
$ git pull                         (1)
Auto-merging nitfol
Merge made by recursive.
 nitfol                |   20 +++++----
 ...
$ git reset --merge ORIG_HEAD      (2)
  1. 即使工作區中有本地修改,如果您知道另一個分支的變更與它們不重疊,也可以放心地說 git pull

  2. 檢查合併結果後,您可能會發現另一個分支的變更不盡人意。執行 git reset --hard ORIG_HEAD 可以讓您回到原來的位置,但這會丟棄您的本地變更,而您不希望這樣。git reset --merge 可以保留您的本地變更。

中斷的工作流程

假設您在進行大規模變更時被緊急修復請求中斷。工作區中的檔案尚未適合提交,但您需要切換到另一個分支進行快速錯誤修復。

$ git switch feature  ;# you were working in "feature" branch and
$ work work work      ;# got interrupted
$ git commit -a -m "snapshot WIP"                 (1)
$ git switch master
$ fix fix fix
$ git commit ;# commit with real log
$ git switch feature
$ git reset --soft HEAD^ ;# go back to WIP state  (2)
$ git reset                                       (3)
  1. 此提交將被拋棄,因此使用臨時的提交訊息是沒問題的。

  2. 這會從提交歷史中移除 WIP 提交,並將您的工作區設定為建立該快照之前的狀態。

  3. 此時索引檔案仍包含您作為 snapshot WIP 提交的所有 WIP 變更。這會更新索引,將您的 WIP 檔案顯示為未提交狀態。

    另請參見 git-stash[1]

重設索引中的單個檔案

假設您已將檔案加入索引,但後來決定不想將其加入提交中。您可以使用 git reset 從索引中移除檔案,同時保留變更。

$ git reset -- frotz.c                      (1)
$ git commit -m "Commit files in index"     (2)
$ git add frotz.c                           (3)
  1. 這會將檔案從索引中移除,同時保留在工作目錄中。

  2. 這會提交索引中的所有其他變更。

  3. 再次將檔案加入索引。

在放棄部分先前提交時保留工作區中的變更

假設您在工作時提交了變更,隨後又繼續工作了一陣子,但現在您認為工作區中的內容應該屬於另一個與之前提交無關的分支。您可以開始一個新分支並進行重設,同時保留工作區中的變更。

$ git tag start
$ git switch -c branch1
$ edit
$ git commit ...                            (1)
$ edit
$ git switch -c branch2                     (2)
$ git reset --keep start                    (3)
  1. 這會將您在 branch1 中的首次編輯提交。

  2. 理想情況下,您在建立並切換到 branch2 時(例如 git switch -c branch2 start),就能意識到之前的提交不屬於新主題,但人非聖賢。

  3. 但您可以在切換到 branch2 後,使用 reset --keep 來移除不需要的提交。

將一個提交拆分為一系列提交

假設您建立了許多邏輯上獨立的變更並將它們一起提交。後來您決定最好將每個邏輯區塊關聯到自己的提交中。您可以使用 git reset 倒轉歷史而不變更本地檔案內容,然後連續使用 git add -p 來互動式選擇包含在每次提交中的區塊,並使用 git commit -c 來預填提交訊息。

$ git reset -N HEAD^                        (1)
$ git add -p                                (2)
$ git diff --cached                         (3)
$ git commit -c HEAD@{1}                    (4)
...                                         (5)
$ git add ...                               (6)
$ git diff --cached                         (7)
$ git commit ...                            (8)
  1. 首先,將歷史倒轉一次提交,以移除原始提交,但保持工作區的所有變更。-N 確保任何隨 HEAD 加入的新檔案仍被標記,以便 git add -p 能找到它們。

  2. 接下來,我們使用 git add -p 功能互動式選擇要加入的差異區塊。這將詢問您每個差異區塊,您可以使用簡單指令,如「是,包含此項」、「不,不要包含此項」,甚至強大的「編輯」功能。

  3. 一旦滿意所選的區塊,您應該使用 git diff --cached 驗證已為第一次提交準備的內容。這顯示了所有已移入索引並準備提交的變更。

  4. 接下來,提交索引中儲存的變更。-c 選項指定使用原始提交中的訊息進行預填,有助於避免重新輸入。HEAD@{1} 是一個特殊表示法,指原始重設提交之前的 HEAD 位置(1 次變更前)。詳細資訊參見 git-reflog[1]。您也可以使用任何其他有效的提交參考。

  5. 您可以多次重複步驟 2-4,將原始程式碼拆分為任意數量的提交。

  6. 現在您已將許多變更拆分為各自的提交,可能不再使用 git add 的補丁模式,以選擇所有剩餘未提交的變更。

  7. 再次檢查以確認已包含所需的內容。您可能也希望驗證 git diff 沒有顯示任何需要稍後提交的剩餘變更。

  8. 最後,建立最終提交。

討論

下表顯示執行以下指令時會發生什麼:

git reset --option target

根據檔案狀態,使用不同的重設選項將 HEAD 重設至另一個提交(target)。

在這些表格中,A, B, CD 是檔案的不同狀態。例如,第一張表的第一行意味著,如果檔案在工作區中為狀態 A,在索引中為 B,在 HEAD 中為 C,在目標中為 D,則 git reset --soft target 將使工作區中的檔案保持在狀態 A,並將索引中的檔案保持在狀態 B。它將 HEAD(即當前分支的頂端,如果您位於該分支的話)重設(移動)至 target(其檔案狀態為 D)。

working index HEAD target         working index HEAD
----------------------------------------------------
 A       B     C    D     --soft   A       B     D
			  --mixed  A       D     D
			  --hard   D       D     D
			  --merge (disallowed)
			  --keep  (disallowed)
working index HEAD target         working index HEAD
----------------------------------------------------
 A       B     C    C     --soft   A       B     C
			  --mixed  A       C     C
			  --hard   C       C     C
			  --merge (disallowed)
			  --keep   A       C     C
working index HEAD target         working index HEAD
----------------------------------------------------
 B       B     C    D     --soft   B       B     D
			  --mixed  B       D     D
			  --hard   D       D     D
			  --merge  D       D     D
			  --keep  (disallowed)
working index HEAD target         working index HEAD
----------------------------------------------------
 B       B     C    C     --soft   B       B     C
			  --mixed  B       C     C
			  --hard   C       C     C
			  --merge  C       C     C
			  --keep   B       C     C
working index HEAD target         working index HEAD
----------------------------------------------------
 B       C     C    D     --soft   B       C     D
			  --mixed  B       D     D
			  --hard   D       D     D
			  --merge (disallowed)
			  --keep  (disallowed)
working index HEAD target         working index HEAD
----------------------------------------------------
 B       C     C    C     --soft   B       C     C
			  --mixed  B       C     C
			  --hard   C       C     C
			  --merge  B       C     C
			  --keep   B       C     C

git reset --merge 旨在用於從衝突的合併中重設。任何合併操作都保證參與合併的工作區檔案在開始前不會有相對於索引的本地變更,且會將結果寫入工作區。因此,如果我們在索引與目標之間,以及索引與工作區之間看到差異,這表示我們並非從一個因衝突失敗後的合併操作狀態中重設出來。這就是為什麼這種情況下我們不允許 --merge 選項。

git reset --keep 旨在用於移除當前分支中最後幾次提交的同時,保留工作區中的變更。如果我們想移除的提交中的變更,與我們想保留的工作區變更之間可能存在衝突,則不允許重設。這就是為什麼如果工作區與 HEAD 之間,以及 HEAD 與目標之間同時存在變更時,是不允許的。為了安全起見,若存在未合併的條目,該選項也不被允許。

下表顯示當存在未合併條目時會發生什麼

working index HEAD target         working index HEAD
----------------------------------------------------
 X       U     A    B     --soft  (disallowed)
			  --mixed  X       B     B
			  --hard   B       B     B
			  --merge  B       B     B
			  --keep  (disallowed)
working index HEAD target         working index HEAD
----------------------------------------------------
 X       U     A    A     --soft  (disallowed)
			  --mixed  X       A     A
			  --hard   A       A     A
			  --merge  A       A     A
			  --keep  (disallowed)

X 表示任何狀態,U 表示未合併的索引。

GIT

git[1] 套件的一部分