Git 系列 - Git 撤銷提交和更改

在本節中,我們將討論Git的“撤消”策略和命令。首先需要注意的是,Git 沒有像文字處理應用程式那樣的傳統“撤銷”系統。Git 有其自己的“撤銷”操作,如 Git checkout, Git revert, Git clean,Git reset 等。

一個有趣的比喻是將 Git 視為時間機器,對時間線進行管理。一次commit提交是專案歷史時間線中某個時間點的快照。此外,可以透過使用分支來管理多個時間線。在 Git 中進行“撤消”操作時,我們通常會回到過去,或者移到另一個沒有發生錯誤的時間線。

對比差異:檢索舊的提交

任何版本控制系統背後的思想都是儲存專案的“安全”副本,這樣我們就不必擔心對程式碼庫造成不可挽回地破壞。一旦我們對程式碼庫建立了提交的專案歷史記錄,那麼接下來就可以訪問歷史記錄中的任何提交。

git log

命令是檢視 Git 倉庫歷史提交記錄的最佳的選擇,也是後續其他操作的基礎。如果你對該命令還不熟悉,推薦先去檢視本教程的 git log章節。

在下面的示例中,我們使用git log來獲取倉庫 git-example 的歷史提交資訊。

$ git log ——oneline

查詢結果如下

Git 系列 - Git 撤銷提交和更改

Git log檢視歷史提交

每個提交都有一個唯一的 SHA-1 雜湊識別符號。這些 ID 用於遍歷已提交的時間線並訪問特定的提交的資訊。預設情況下,git log將只顯示當前所在分支的提交資訊。然而有時候可能我們要檢視的是其他分支的歷史提交資訊,這種情況就需要我們在 git log 命令後面加上

——branches=*

選項了。

要檢視當前倉庫有哪些分支和我們當前所在哪個分支,可以使用 git branch 命令,關於該命令,我們會在後面的章節中進行介紹。

Git 系列 - Git 撤銷提交和更改

git branch 檢視倉庫分支

從上圖中可以看到,我們一共有兩個分支

dev

master

,並且我們當前在dev分支上。

當找到要訪問的提交記錄點時,我們可以使用

git checkout

命令來訪問該提交。git checkout命令將這些儲存的快照“載入”到我們的開發機器上。在正常的開發過程中,HEAD通常指向main 或其他一些本地分支,但是當我們使用 git checkout 檢出之前的提交時,HEAD不再指向某個分支——它直接指向這個提交。這稱為“分離HEAD”狀態:

Git 系列 - Git 撤銷提交和更改

Git HEAD分離狀態

檢出舊的檔案不會對HEAD指標進行移動。它會保持在同一個分支和同一個提交上,避免了“分離 HEAD”狀態。然後,我們就可以像提交任何其他更改一樣在新快照中將就版本的檔案提交。因此,實際上,git checkout對檔案的這種用法可以作為恢復到單個檔案的舊版本的一種方式。

假設我們已經開始開發一個新功能,但你不確定是否要保留它。為了做出決定,則需要在開始之前檢視專案的狀態。首先,我們需要找到要檢視的修訂的 ID。

$ git log ——oneline

Git 系列 - Git 撤銷提交和更改

Git log 檢視所有分支歷史提交

我們可以使用git checkout檢視“新增撤銷更改相關檔案”這次提交,如下所示:

$ git checkout 26999f6

這使我們工作目錄與 26999f6 提交的狀態匹配。此時我們可以檢視檔案、編譯專案、執行測試,甚至編輯檔案,而不必擔心丟失專案的當前狀態。在此處所做的任何事情都不會儲存在倉庫中。要繼續開發,需要回到專案的“當前”狀態:

$ git checkout master

假設我們在預設 master 分支上進行開發。回到 master 分支後,可以使用

git revert

git reset

撤消任何不需要的更改。

撤消已提交的快照

從技術上講,有幾種不同的策略可以“撤消”提交。假設我們當前的提交歷史如下

$ git log ——oneline05afb5b (HEAD -> master, dev) 增加撤銷更改命令內容26999f6 新增撤銷更改相關檔案4430b62 新增網址c8f042a Init Project

我們將撤消 “26999f6 新增撤銷更改相關檔案” 提交。可以有三種方式

使用 git checkout 撤銷提交

使用

git checkout

命令,我們可以檢出要撤銷的提交的之前的提交,4430b62。此時倉庫置於 26999f6 提交發生之前的狀態。檢出特定的提交將使倉庫處於“分離 HEAD ”狀態。

Git 系列 - Git 撤銷提交和更改

Git checkout 分離HEAD狀態

我們可以看到,紅色文字處表示當前的狀態。這意味著我們不再在任何分支上工作。在分離狀態下,當我們將分支更改回已建立的分支時,那麼所做的任何新提交都將是孤立的。孤立的提交將由 Git 的垃圾收集器刪除。垃圾收集器按配置的時間間隔執行並定期的永久銷燬孤立提交。為了防止孤立提交被垃圾收集,我們需要確保我們在一個未分離的分支上。

從分離的HEAD 狀態,我們可以執行

git checkout -b new_branch_jiyik

命令。 這將建立一個名為 new_branch_jiyik 的新分支並切換到該狀態。倉庫現在位於新的歷史時間線上,其中 26999f6 提交不再存在。

Git 系列 - Git 撤銷提交和更改

git 新分支提交資訊

此時,我們可以繼續在這個不再存在 26999f6 提交的新分支上工作,並認為它“已撤銷”。不幸的是,如果我們需要前一個分支,也許它是我們的 master 分支,這種撤消策略是不合適的。讓我們看看其他一些“撤銷”方式。

使用 git revert 撤銷提交

現在假設我們有如下的歷史提交。

$ git log ——oneline872fa7e Try something crazya1e8fb5 Make some important changes to hello。txt435b61d Create hello。txt9773e52 Initial import

如果我們想撤銷 872fa7e 提交,可以執行以下 git revert 命令

$ git revert HEAD

Git 將建立一個與上次提交相反的新提交。這會向當前分支的提交歷史記錄中新增一個新提交,現在看起來如下:

$ git log ——onelinee2f9a78 Revert “Try something crazy”872fa7e Try something crazya1e8fb5 Make some important changes to hello。txt435b61d Create hello。txt9773e52 Initial import

在這一點上,我們在技術上“撤消”了872fa7e提交。儘管872fa7e看起來仍然存在於提交歷史中,但已經產生了一個新的e2f9a78提交。 與我們之前的策略不同,我們可以繼續使用相同的分支。這是一個令人滿意的撤銷方式。如果需要保留最少的 Git 歷史記錄(將不需要的提交在歷史記錄中去除),則此策略可能也無法令人滿意。

使用 git reset 撤消提交

git reset 是一個具有多種用途的命令。如果我們呼叫以下命令

$ git reset ——hard a1e8fb5

提交歷史將重置為指定的提交。現在的提交歷史看起來如下所示

$ git log ——onelinea1e8fb5 Make some important changes to hello。txt435b61d Create hello。txt9773e52 Initial import

上面輸出中不再在提交歷史記錄中顯示e2f9a78和872fa7e提交。此時,我們可以繼續工作並建立新的提交,就好像 872fa7e 提交從未發生過一樣。這種撤銷更改的方法對歷史有最清晰的影響。reset 對於本地倉庫更改非常有幫助,但是在使用共享遠端倉庫時會增加複雜性。

如果我們有一個共享的遠端倉庫,並且已經將 872fa7e提交推送過去了。現在我們嘗試 git push 我們重置過的提交歷史記錄的分支,Git 將捕獲它並丟擲錯誤。Git 會假設被推送的分支不是最新的,因為它缺少提交。在這種場景中,反過來就得需要使用

git revert

撤銷方式了。