Git進階系列 | 8. 用Reflog恢復丟失的提交

語言: CN / TW / HK

Git是最流行的代碼版本控制系統,這一系列文章介紹了一些Git的高階使用方式,從而幫助我們可以更好的利用Git的能力。本系列一共8篇文章,這是最後一篇。原文:Using the Reflog to Restore Lost Commits [1]

“Reflog”是Git不太為人所知的特性之一,但可能非常有用。有些人把它稱為“安全網”,而我喜歡把它看作Git的“日記”,因為Git用它來記錄 HEAD 指針的每次移動(例如,每次提交、合併、rebase、cherry-pick、reset等)。Git會將操作記錄在Reflog中,使它成為一個有價值的日誌,當出現問題時,這是一個很好的起點。

在“Git進階”系列的最後一部分,我將解釋 git loggit reflog 之間的區別,並展示如何使用reflog來恢復已刪除的提交和已刪除的分支。

git loggit reflog 有什麼區別?

在之前的文章中,我建議使用 git log 命令檢查事件並查看提交歷史,這正是它的工作。它可以顯示當前的 HEAD 及其祖先,即父提交,下一個父提交,等等。 git log 通過遞歸打印每個提交的父節點來回溯提交歷史,這是代碼庫的一部分,這意味着在push、fetch、pull之後這些信息都會被複制。

另一方面, git reflog 是一個私有的、與工作空間相關的記錄。它不遍歷祖先列表。相反,它顯示一個有序列表,包含 HEAD 過去所指向的所有提交。這就是為什麼可以把它看作某種“撤銷歷史(undo history)”,就像在文字處理器、文本編輯器等中看到的那樣。

技術上來説,這個本地記錄不是代碼庫的一部分,它與提交分開存儲。Reflog是 .git/logs/refs/heads/ 中的一個文件,用來跟蹤每個分支的本地提交。Git的日誌通常會在90天后被清理(這是默認設置),但是可以輕鬆調整Reflog的過期日期。要將過期時間更改為180天,只需輸入以下命令:

$ git config gc.reflogExpire 180.days.ago
倉庫配置文件(.git/config)包含變量reflogExpire,值為180.days.ago

或者可以設置Reflog永不過期:

$ git config gc.reflogExpire never

提示:記住,Git區分了代碼庫的配置文件( .git /config )、每個用户的全局配置( $HOME/.gitconfig )和系統全局設置( /etc/gitconfig )。要為用户或系統調整Reflog的過期時間,請在上面所示的命令後面添加 --system--global 參數。

好了,現在我們有了足夠的理論背景知識,接下來可以展示如何使用 git reflog 來糾正錯誤。

恢復刪除的提交

想象一下下面這個場景: 在查看了提交歷史之後,我們決定刪除最後兩次提交。勇敢的執行了一次 git reset 後,兩個提交從提交歷史中消失了……過了一會兒,我們發現犯了個錯誤,我們丟失了有價值的更改,完蛋了!

真的要從頭再來嗎?不。換句話説,保持冷靜,利用 git reflog

所以,讓我們嘗試把事情搞砸,在現實生活中真的犯一下錯。下圖展示了我們最初在Tower中的提交歷史:

我們想要刪除兩個提交,並將“Change headlines for about and imprint”提交(ID: 2b504bee )作為 master 分支上的最後一個修改。我們需要做的就是將哈希ID複製到剪貼板,然後在命令行中使用 git reset 並輸入哈希:

$ git reset --hard 2b504bee

瞧。提交已經消失。現在,我們假設這是一個錯誤,並查看Reflog來恢復丟失的數據。在終端中輸入 git reflog 查看日誌:

有沒有注意到所有條目都是按時間順序排列的,這意味着頂部是最近的(也就是最新的)提交。如果仔細看,會注意到幾分鐘前致命的 git reset 操作就在頂部。

日記似乎起作用了,這是個好消息。因此,我們用它來撤銷最後一個操作,並在執行reset命令之前恢復狀態。與前面一樣,將哈希ID(在這個特定示例中為 e5b19e4 )複製到剪貼板,再次使用 git reset ,這完全有效。但在本例中,我將基於舊狀態創建一個新分支:

$ git branch happy-ending e5b19e4

再看看圖形化Git客户端:

如你所見,已經創建了新的 happy-ending 分支,包含了之前刪除的提交。太棒了,什麼都沒丟!

接下來看看另一個示例,用Reflog來恢復整個分支。

恢復刪除的分支

下面的示例和第一個場景類似,我們要刪除一些東西,只是這一次要刪除的是整個分支。也許你的客户或團隊領導吿訴你要擺脱一個特性分支,也許你自己想要進行清理。糟糕的是,有個提交(圖中的 C3 )沒有被包含在任何其他分支中,所以肯定會丟失數據:

我們來實際執行這個操作,稍後再恢復分支:

在刪除分支 feature/login 之前,需要先切換出來。(正如截圖中所示,這是當前的 HEAD 分支,不能在Git中刪除 HEAD 分支。)所以,我們要切換分支(到 master ),然後刪除 feature/login :

好吧,假設我們的客户或團隊領導改變了主意,現在又需要 feature/login 分支(包括它的提交)了,怎麼辦?

看看Git的日記:

$ git reflog
776f8ca (HEAD -> master) [email protected]{0}: checkout: moving from feature/login to master
b1c249b (feature/login) [email protected]{1}: checkout: moving from master to feature/login
[...]

我們又很幸運,最後一項顯示了從 feature/loginmaster 的切換。我們嘗試返回到之前的狀態,將哈希ID b1c249b 複製到剪貼板,接下來,基於期望的狀態創建一個名為 feature/login 的分支:

$ git branch feature/login b1c249b
$ git branch -vv
feature/login b1c249b Change Imprint page title
* master 776f8ca Change about title and delete error page

太棒了,分支死而復生,仍然包含了我們認為已經丟失的有價值的提交:

如果使用像Tower這樣的桌面GUI,可以簡單的按下 CMD+Z 來撤銷最後一個操作,就像文本編輯器或文字處理器一樣。

保持冷靜,保證記錄

Git的Reflog可以成為真正的救星!如你所見,很容易將丟失的提交甚至整個分支從墳墓中帶回來,只需要在reflog中找到正確的哈希ID,其他都是小菜一碟。

如果想更深入瞭解高級Git工具,可以免費查看“Advanced Git Kit [3] ”: 這是關於分支策略、交互式Rebase、Reflog、子模塊等主題的短視頻集合。

本文是“Git進階”系列的最後一部分,希望你喜歡這些文章。編碼快樂!

References:[1] Using the Reflog to Restore Lost Commits: http://css-tricks.com/using-the-reflog-to-restore-lost-commits/

你好,我是俞凡,在Motorola做過研發,現在在Mavenir做技術工作,對通信、網絡、後端架構、雲原生、DevOps、CICD、區塊鏈、AI等技術始終保持着濃厚的興趣,平時喜歡閲讀、思考,相信持續學習、終身成長,歡迎一起交流學習。

微信公眾號:DeepNoMind

- END -