# 撤銷操作
撤銷是 Git 提供的一個非常優秀的功能,它可以允許你撤消剛剛所做的操作。這就意味著你不必害怕搞砸你正在工作的項目: Git 一直會讓你的項目處于一個安全的狀態。
## 修改最后一次提交
無論你如何精心地推敲你的提交,你總是有可能出錯的。比如忘記了把一個改動過的文件添加到提交中,或者是輸入了錯誤的提交注釋等等。當你認為提交有問題時,你都可以使用 “git commit” 命令,并附帶上 “--amend” 參數,這個操作可以非常輕松地來修改你的_最后一次_提交。
如果你僅僅是想修改上一次的提交注釋,你并不需要操作暫存區,簡單地再次輸入 “git commit --amend” 并附帶上正確的注釋就可以了:
```
$ git commit --amend -m "This is the correct message"
```
如果你想要添加更多的改動到上一次提交中,你可以像平常一樣把這些新的改動添加到暫存區。然后再次使用 “--amend” 參數進行提交:
```
$ git add some/changed/file.ext
$ git commit --amend -m "commit message"
```
##### 黃金法則
#### #5: 不要修改已經被發布的提交
“amend” 操作是一個非常強大的小幫手,這點你會很快地領會到。但是在你使用它的同時,你一定要考慮到以下一些方面:
* (a) 你只能使用它來修正你的**上一次**提交。更早的提交是不能使用 “amend” 來進行操作的。
* (b) 你不要對一個已經在遠程倉庫上被發布,或者說已經被共享的提交進行 “amend” 操作!這是因為 “amend” 操作實際上在后臺打包了一個全新的提交來替換舊的提交。如果在這個遠程倉庫里僅僅只有你一個人在工作,那么這種操作是沒有問題的。但是在團隊工作中,如果開發團隊的其他人員已經得到了你所發布的改動,并且在此基礎之上進行了他自己的改動,再次整合一個被修改過的(amended)提交就會出現很多麻煩。
## 撤銷本地改動
當改動還沒有被提交之前,它們仍然被稱之為 “本地” 改動。這些在你的工作目錄(working directory)的修改還仍然在本地,它們屬于未被提交的改動(uncommitted changes)。
有時候你對代碼進行了一些修改,但是發現這些改動帶來的問題比之前還要多。在這種情況下,你可能想要放棄你剛剛的改動,讓代碼恢復到你改動之前的版本,也就是上次提交之后的狀態。
恢復一個文件到上次提交之后的狀態,你可以使用 “git checkout” 命令:
```
$ git checkout -- file/to/restore.ext
```
我們已經知道了 “checkout” 命令主要是用來切換分支用的。但是你同樣可以給這個命令附帶上 “--” 參數,并加上用一個空格來分隔的文件路徑。這個操作將撤銷在特定文件上所有的未提交的改動。
如果你想要放棄你在工作副本(working copy)中的所有本地改動,并讓你的本地副本恢復到上次提交之后的版本,你可以使用 “git reset” 命令:
```
$ git reset --hard HEAD
```
上面這個操作會通知 Git 將你本地副本上的所有文件替換到和 “HEAD” 分支一致的版本(也就是上次提交之后的版本狀態)上,并放棄所有的本地改動。
##### 注釋
放棄本地未被提交的改動是不能被撤銷的。這是因為這些改動還沒有保存在你的倉庫中。因此,Git 也就沒有任何機會來挽回這種操作帶來的改動。
請在你撤銷本地改動時始終牢記這一點。
## 撤銷已提交的改動
有時你也許想撤銷某一個之前的提交。例如當你發現你的改動存在錯誤,或是整個改動就是錯誤的,又或者你的客戶決定不需要這個改動了等等。
使用 “git revert” 命令可以撤銷某個之前的提交。但是這個命令并不是刪除那個提交,相反的,它是恢復那個提交的改動,這只是看起來像是撤銷而已。這個操作實際上會自動產生一個新的提交。在提交中包括了你想要撤銷的那個提交的所有反向改動。例如在原始提交中,你在某一個位置添加一些字符,那么這個恢復提交(reverting commit)就會把這些字符刪除掉。

如果想要撤銷已提交的改動,你只需要簡單地給出這個提交的 commit hash:
```
$ git revert 2b504be
[master 364d412] Revert "Change headlines for about and imprint"
2 files changed, 2 insertions(+), 2 deletions (-)
$ git log
commit 364d412a25ddce997ce76230598aaa7b9759f434
Author: Tobias Günther <support@learn-git.com>
Date: Tue Aug 6 10:23:57 2013 +0200
Revert "Change headlines for about and imprint"
This reverts commit 2b504bee4083a20e0ef1e037eea0bd913a4d56b6.
```
另外一種撤銷提交的方法是使用 “git reset” 命令。這個操作不會自動產生一個新的提交,或是刪除你要撤銷的提交,它會重置你當前的 HEAD 分支到一個特定舊的版本,也被稱作 “回滾(rolling back)” 到舊的版本:
```
$ git reset --hard 2be18d9
```
在執行了這個操作之后,你當前簽出的分支將被重置為版本 2be18d9。在這個版本之后的一個或者多個版本將被真正的放棄,它們也不會顯示在分支的歷史記錄中。

如果在這個命令上使用 “--hard” 參數則一定要小心,Git 將會丟棄所有你當前可能擁有的本地改動。整個項目將會被恢復成一個之前的舊版本。
如果你使用 “--keep” 參數來替代 “--hard” 參數,那么在 “回滾” 到的版本之后的所有改動將會轉換成本地改動,并保留在你的工作目錄中。
##### 注釋
和 “revert” 命令一樣, “reset” 命令也不會刪除任何已存在的提交。這些操作僅僅是做得好像這個提交不存在似的,并從歷史記錄中刪除它們。無論如何,提交會被保存在 Git 的數據庫中至少30天。因此,如果你發現你曾經不小心刪除了一個仍然有用的提交,任何一個精通 Git 的同事都能為你恢復它們。
這兩個命令(revert and reset)只是工作于當前 HEAD 分支上。因此在你執行這些操作之前,你必須要切換到正確的分支上去。
- Learn Version Control with Git 中文版
- 前言
- Part 1 - 基礎知識
- 什么是版本控制?
- 為什么要使用版本控制系統?
- 準備工作
- 版本控制的基本工作流程
- 從一個未被納入版本控制的項目開始
- 從一個已被納入版本控制的項目開始
- 工作在你的項目上
- Part 2 - 分支與合并
- 分支可以改變你的生命
- 在分支上工作
- 暫時保存更改
- 切換一個本地分支
- 合并改動
- 分支的工作流程
- Part 3 - 遠程倉庫
- 關于遠程倉庫
- 連接一個遠程倉庫
- 查看遠程數據
- 整合遠程的改動
- 發布一個本地分支
- 刪除分支
- Part 4 - 高級應用
- 撤銷操作
- 用 diff 來檢查改動
- 處理合并沖突
- Rebase 代替合并
- 子模塊
- git-flow 的工作流程
- 使用 SSH 公鑰驗證
- Part 5 - 工具與服務
- 桌面應用程序
- 比較和整合工具
- 代碼托管服務
- 更多學習資源
- 附錄
- 版本控制的最佳實踐
- 命令 101
- 從 Subversion 過渡到 Git
- 為什么選擇 Git