Git
Chapters ▾ 2nd Edition

2.4 Git 基礎 - 復原

復原

在任何一個過程中,你都可能想要復原某些內容, 在這裡我們會回顧一些基本的工具用來復原你做過的修改內容; 小心!因為復原操作不是永遠都可逆的, 這是少數在使用 Git 時,執行錯誤的動作會遺失資料的情況。

一個常見的復原操作發生在當你太早提交(commit),接著才發現忘了加入某些檔案,或者寫錯了提交訊息; 如果你想要重新提交,你可以在提交命令上使用 --amend 選項:

$ git commit --amend

這個命令會再次把預存區(staging area)拿來提交, 如果自從上次提交以來你沒有做過任何檔案修改(例如:在上一次提交後,馬上執行此命令),那麼整個快照看起來會與上次提交的一模一樣,唯一有可能更動的是提交訊息。

同樣用來提交訊息的文字編輯器會先啟動,並且已填好上一次提交的訊息內容; 你可以像往常一樣編輯這些訊息,接著它會覆蓋掉上一次的提交。

例如:如果你提交後才意識到你想要把某些忘記預存(stage)的修改也一併加入到上一個提交中,你可以這樣做:

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

最終只會得到一個提交——第二次的提交取代了第一次提交的結果。

將已預存的檔案移出預存區

接下來的兩節會展示如何操作預存區和工作目錄中已修改的檔案; 用來顯示這二個區域狀態的命令也會好心地提示你如何做復原操作, 例如:假設你已經修改了二個檔案,並且想要分別提交它們,但是你卻意外地使用 git add * 把它們二個都預存了, 要如何將其中一個「移出預存區(unstage)」呢? 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

在「Changes to be committed」文字正下方,說明了使用 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 檔案現在又回到了「修改」但「未預存(unstaged)」的狀態。

筆記

雖然 git reset 命令加上 --hard 選項會讓它成為一個危險的命令,但在本例中工作目錄內的檔案卻不會被修改到; 這是因為執行沒有附加選項的 git reset 命令並不危險——它只會修改預存區。

關於 git reset 命令,到目前為止所有你需要知道的就只有這個神奇用法; 我們將在 Reset Demystified 中深入了解 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 分支 中介紹「收藏(stashing)」和「分支(branching)」,一般而言它們是比較好的做法。

切記,在 Git「已提交」的任何東西幾乎總是能夠被復原的, 即使是在被刪除的分支上曾經出現過的提交,或者因為 --amend 而被覆蓋掉的提交,它們都是可以被復原的(詳見 Data Recovery 以了解資料復原); 然而,從來沒被提交過的內容,失去後大概就沒辦法再救回來了。