Git是最流行的代碼版本控制系統(tǒng),這一系列文章介紹了一些Git的高階使用方式,從而幫助我們可以更好的利用Git的能力。本系列一共8篇文章,這是最后一篇。原文:Using the Reflog to Restore Lost Commits[1]
“Reflog”是Git不太為人所知的特性之一,但可能非常有用。有些人把它稱為“安全網”,而我喜歡把它看作Git的“日記”,因為Git用它來記錄HEAD指針的每次移動(例如,每次提交、合并、rebase、cherry-pick、reset等)。Git會將操作記錄在Reflog中,使它成為一個有價值的日志,當出現(xiàn)問題時,這是一個很好的起點。
在“Git進階”系列的最后一部分,我將解釋git log和git reflog之間的區(qū)別,并展示如何使用reflog來恢復已刪除的提交和已刪除的分支。
Git進階系列:
git log和git reflog有什么區(qū)別?
在之前的文章中,我建議使用git log命令檢查事件并查看提交歷史,這正是它的工作。它可以顯示當前的HEAD及其祖先,即父提交,下一個父提交,等等。git log通過遞歸打印每個提交的父節(jié)點來回溯提交歷史,這是代碼庫的一部分,這意味著在push、fetch、pull之后這些信息都會被復制。
另一方面,git reflog是一個私有的、與工作空間相關的記錄。它不遍歷祖先列表。相反,它顯示一個有序列表,包含HEAD過去所指向的所有提交。這就是為什么可以把它看作某種“撤銷歷史(undo history)”,就像在文字處理器、文本編輯器等中看到的那樣。
技術上來說,這個本地記錄不是代碼庫的一部分,它與提交分開存儲。Reflog是.git/logs/refs/heads/中的一個文件,用來跟蹤每個分支的本地提交。Git的日志通常會在90天后被清理(這是默認設置),但是可以輕松調整Reflog的過期日期。要將過期時間更改為180天,只需輸入以下命令:
$ git config gc.reflogExpire 180.days.ago

或者可以設置Reflog永不過期:
$ git config gc.reflogExpire never
提示: 記住,Git區(qū)分了代碼庫的配置文件(
.git /config)、每個用戶的全局配置($HOME/.gitconfig)和系統(tǒng)全局設置(/etc/gitconfig)。要為用戶或系統(tǒng)調整Reflog的過期時間,請在上面所示的命令后面添加--system或--global參數(shù)。
好了,現(xiàn)在我們有了足夠的理論背景知識,接下來可以展示如何使用git reflog來糾正錯誤。
恢復刪除的提交
想象一下下面這個場景: 在查看了提交歷史之后,我們決定刪除最后兩次提交。勇敢的執(zhí)行了一次git reset后,兩個提交從提交歷史中消失了……過了一會兒,我們發(fā)現(xiàn)犯了個錯誤,我們丟失了有價值的更改,完蛋了!

真的要從頭再來嗎?不。換句話說,保持冷靜,利用git reflog!
所以,讓我們嘗試把事情搞砸,在現(xiàn)實生活中真的犯一下錯。下圖展示了我們最初在Tower中的提交歷史:

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

瞧。提交已經消失?,F(xiàn)在,我們假設這是一個錯誤,并查看Reflog來恢復丟失的數(shù)據(jù)。在終端中輸入git reflog查看日志:

有沒有注意到所有條目都是按時間順序排列的,這意味著頂部是最近的(也就是最新的)提交。如果仔細看,會注意到幾分鐘前致命的git reset操作就在頂部。
日記似乎起作用了,這是個好消息。因此,我們用它來撤銷最后一個操作,并在執(zhí)行reset命令之前恢復狀態(tài)。與前面一樣,將哈希ID(在這個特定示例中為e5b19e4)復制到剪貼板,再次使用git reset,這完全有效。但在本例中,我將基于舊狀態(tài)創(chuàng)建一個新分支:
$ git branch happy-ending e5b19e4
再看看圖形化Git客戶端:

如你所見,已經創(chuàng)建了新的happy-ending分支,包含了之前刪除的提交。太棒了,什么都沒丟!
接下來看看另一個示例,用Reflog來恢復整個分支。
恢復刪除的分支
下面的示例和第一個場景類似,我們要刪除一些東西,只是這一次要刪除的是整個分支。也許你的客戶或團隊領導告訴你要擺脫一個特性分支,也許你自己想要進行清理。糟糕的是,有個提交(圖中的C3)沒有被包含在任何其他分支中,所以肯定會丟失數(shù)據(jù):

我們來實際執(zhí)行這個操作,稍后再恢復分支:

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

好吧,假設我們的客戶或團隊領導改變了主意,現(xiàn)在又需要feature/login分支(包括它的提交)了,怎么辦?
看看Git的日記:
$ git reflog
776f8ca (HEAD -> master) HEAD@{0}: checkout: moving from feature/login to master
b1c249b (feature/login) HEAD@{1}: checkout: moving from master to feature/login
[...]
我們又很幸運,最后一項顯示了從feature/login到master的切換。我們嘗試返回到之前的狀態(tài),將哈希ID b1c249b復制到剪貼板,接下來,基于期望的狀態(tài)創(chuàng)建一個名為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: https://css-tricks.com/using-the-reflog-to-restore-lost-commits/
你好,我是俞凡,在Motorola做過研發(fā),現(xiàn)在在Mavenir做技術工作,對通信、網絡、后端架構、云原生、DevOps、CICD、區(qū)塊鏈、AI等技術始終保持著濃厚的興趣,平時喜歡閱讀、思考,相信持續(xù)學習、終身成長,歡迎一起交流學習。
微信公眾號:DeepNoMind