章節 ▾ 第二版

2.4 Git 基礎 - 撤銷操作

撤銷操作

在任何階段,你都可能需要撤銷某些操作。在這裡,我們將回顧幾個用於撤銷已做出更改的基本工具。要小心,因為其中一些撤銷操作並非總是可以撤銷的。這是 Git 中少數可能在你操作不當時導致你丟失部分工作的領域之一。

常見的撤銷操作之一發生在當你過早提交,並且可能忘記新增某些檔案,或者搞亂了你的提交資訊。如果你想重做那個提交,進行你忘記新增的額外更改,暫存它們,然後使用 --amend 選項再次提交。

$ git commit --amend

這個命令會使用你的暫存區來完成提交。如果你上次提交後沒有做任何更改(例如,你在上次提交後立即執行此命令),那麼你的快照將完全相同,你唯一改變的就是你的提交資訊。

相同的提交資訊編輯器會啟動,但它已經包含了你上次提交的資訊。你可以像往常一樣編輯資訊,但這會覆蓋你之前的提交。

例如,如果你提交了一個檔案,然後意識到你忘記暫存想要新增到該提交中的檔案更改,你可以這樣做:

$ git commit -m 'Initial commit'
$ git add forgotten_file
$ git commit --amend

最終你只有一個提交——第二個提交取代了第一個提交的結果。

注意

重要的是要理解,當你修改最後一個提交時,你不是在“修復”它,而是在用一個全新的、改進的提交“替換”它,它將舊的提交推開,並將新的提交放在它的位置上。實際上,這就像之前的提交從未發生過一樣,並且不會出現在你的倉庫歷史記錄中。

修改提交的明顯價值在於對你最後一個提交進行小的改進,而不會用“哦,忘了新增檔案”或“該死,修復最後一個提交中的拼寫錯誤”之類的提交資訊弄亂你的倉庫歷史記錄。

注意

只修改尚未推送到任何地方的本地提交。修改已推送的提交併強制推送分支將給你的協作者帶來麻煩。關於發生這種情況以及如何處理接收方的資訊,請閱讀 Rebase 的危險

取消暫存一個已暫存的檔案

接下來的兩個部分演示瞭如何處理你的暫存區和工作目錄更改。好處是,你用來確定這兩個區域狀態的命令也會提醒你如何撤銷對它們的更改。例如,假設你更改了兩個檔案,並希望將它們作為兩個單獨的更改提交,但你意外地鍵入了 git add * 並暫存了它們。如何取消暫存其中一個?git status 命令會提醒你:

$ git add *
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README
    modified:   CONTRIBUTING.md

在“待提交的更改”文字下方,它會告訴你使用 git reset HEAD <file>…​ 來取消暫存。所以,讓我們使用這個建議來取消暫存 CONTRIBUTING.md 檔案。

$ git reset HEAD CONTRIBUTING.md
Unstaged changes after reset:
M	CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

這個命令有點奇怪,但它確實有效。CONTRIBUTING.md 檔案被修改了,但又一次被取消暫存了。

注意

確實,git reset 可能是一個危險的命令,特別是如果你使用了 --hard 標誌。然而,在上面描述的情況下,你的工作目錄中的檔案沒有被觸動,所以它是相對安全的。

目前,你只需要瞭解 git reset 命令的這個神奇用法。我們將在 Reset 詳解 中更詳細地介紹 reset 的作用以及如何掌握它來做真正有趣的事情。

取消修改一個已修改的檔案

如果你意識到不想保留對 CONTRIBUTING.md 檔案的更改,該怎麼辦?如何輕鬆地取消修改它——將其恢復到上次提交時(或初始克隆時,或無論如何你將其放入工作目錄時)的樣子?幸運的是,git status 也會告訴你如何做到這一點。在上一個示例輸出中,未暫存的區域看起來像這樣:

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

它非常明確地告訴你如何丟棄你所做的更改。讓我們按照它說的做:

$ git checkout -- CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README

你可以看到更改已被恢復。

重要提示

重要的是要理解 git checkout -- <file> 是一個危險的命令。你對該檔案所做的任何本地更改都將丟失——Git 只是用最後暫存或提交的版本替換了該檔案。除非你絕對確定你不需要那些未儲存的本地更改,否則永遠不要使用此命令。

如果你想保留對該檔案的更改,但仍需要暫時將其移開,我們將在 Git 分支 中介紹暫存和分支,這些通常是更好的選擇。

請記住,Git 中任何 *已提交* 的內容幾乎總可以恢復。即使是已刪除分支上的提交,或被 --amend 提交覆蓋的提交也可以恢復(有關資料恢復,請參閱 資料恢復)。但是,任何你丟失但從未提交過的東西很可能再也找不回來了。

使用 git restore 撤銷操作

Git 版本 2.23.0 引入了一個新命令:git restore。它基本上是 git reset 的一個替代品,我們剛才已經討論過。從 Git 版本 2.23.0 開始,Git 將使用 git restore 而不是 git reset 來執行許多撤銷操作。

讓我們回顧一下,用 git restore 來撤銷操作,而不是 git reset

使用 git restore 取消暫存檔案

接下來的兩個部分演示瞭如何使用 git restore 處理你的暫存區和工作目錄更改。好處是,你用來確定這兩個區域狀態的命令也會提醒你如何撤銷對它們的更改。例如,假設你更改了兩個檔案,並希望將它們作為兩個單獨的更改提交,但你意外地鍵入了 git add * 並暫存了它們。如何取消暫存其中一個?git status 命令會提醒你:

$ git add *
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   CONTRIBUTING.md
	renamed:    README.md -> README

在“待提交的更改”文字下方,它會告訴你使用 git restore --staged <file>…​ 來取消暫存。所以,讓我們使用這個建議來取消暫存 CONTRIBUTING.md 檔案。

$ git restore --staged CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	renamed:    README.md -> README

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   CONTRIBUTING.md

CONTRIBUTING.md 檔案被修改了,但又一次被取消暫存了。

使用 git restore 取消修改檔案

如果你意識到不想保留對 CONTRIBUTING.md 檔案的更改,該怎麼辦?如何輕鬆地取消修改它——將其恢復到上次提交時(或初始克隆時,或無論如何你將其放入工作目錄時)的樣子?幸運的是,git status 也會告訴你如何做到這一點。在上一個示例輸出中,未暫存的區域看起來像這樣:

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   CONTRIBUTING.md

它非常明確地告訴你如何丟棄你所做的更改。讓我們按照它說的做:

$ git restore CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	renamed:    README.md -> README
重要提示

重要的是要理解 git restore <file> 是一個危險的命令。你對該檔案所做的任何本地更改都將丟失——Git 只是用最後暫存或提交的版本替換了該檔案。除非你絕對確定你不需要那些未儲存的本地更改,否則永遠不要使用此命令。