使用 Git 解決錯誤的指南(第 2 部分)
已發表: 2022-03-10在我們“用 Git 解決錯誤”系列的第二部分中,我們將再次勇敢地直視危險:我準備了四個新的世界末日場景——當然,包括一些拯救我們脖子的聰明方法! 但在我們深入研究之前:查看之前關於 Git 的文章,了解更多幫助您消除 Git 錯誤的自救方法!
我們走吧!
使用 Reflog 恢復已刪除的分支
你有沒有刪除過一個分支,不久之後,你意識到你不應該這樣做? 萬一您不知道這種感覺,我可以告訴您,這不是一種好感覺。 當你想到那個分支提交的所有辛勤工作,你現在丟失的所有有價值的代碼時,悲傷和憤怒的混合物在你身上蔓延。
幸運的是,有一種方法可以使該分支起死回生——借助名為“Reflog”的 Git 工具。 我們在本系列的第一部分中使用了這個工具,但這裡有一點更新:Reflog 就像一個日誌,Git 記錄了本地存儲庫中 HEAD 指針的每一次移動。 換句話說,不那麼討厭的話:任何時候你結帳、提交、合併、變基、挑選等,都會創建一個日記條目。 這使得 Reflog 在出現問題時成為完美的安全網!
我們來看一個具體的例子:
$ git branch * feature/login master
我們可以看到我們當前已經簽出了我們的分支feature/login
。 假設這是我們要刪除的分支(無意中)。 然而,在我們這樣做之前,我們需要切換到不同的分支,因為我們無法刪除當前的 HEAD 分支!
$ git checkout master $ git branch -d feature/login
我們有價值的特性分支現在已經消失了——我會給你一點時間來 (a) 了解我們錯誤的嚴重性和 (b) 哀悼一下。 等你擦乾眼淚,我們得想辦法把這根樹枝帶回來! 讓我們打開 Reflog(只需鍵入git reflog
)並查看它為我們存儲的內容:
這裡有一些註釋可以幫助您理解輸出:
- 首先,您需要知道 Reflog 按時間順序對其條目進行排序:最新的條目位於列表頂部。
- 最頂層(因此也是最新的)項目是我們在刪除分支之前執行的
git checkout
命令。 它被記錄在 Reflog 中,因為它是 Reflog 忠實記錄的這些“HEAD 指針移動”之一。 - 為了挽回我們的嚴重錯誤,我們可以簡單地回到之前的狀態——這也清楚地記錄在 Reflog 中!
所以讓我們嘗試一下,通過創建一個新分支(名稱為“丟失”分支),該分支從這個“之前”狀態 SHA-1 哈希開始:
$ git branch feature/login 776f8ca
瞧! 您會很高興看到我們現在已經恢復了我們看似丟失的分支!
如果您使用的是“Tower”之類的 Git 桌面 GUI,您可以使用一個不錯的快捷方式:只需按鍵盤上的CMD + Z即可撤消上一個命令——即使您剛剛暴力刪除了一個分支!
將提交移動到不同的分支
在許多團隊中,有一個協議是不提交長期運行的分支,如main
或develop
:像這樣的分支應該只通過集成(例如合併或變基)接收新的提交。 然而,當然,錯誤是不可避免的:儘管如此,我們有時會忘記並提交這些分支! 那麼我們如何才能清理我們製造的爛攤子呢?
幸運的是,這些類型的問題很容易得到糾正。 讓我們捲起袖子開始工作吧。
第一步是切換到正確的目標分支,然後過度使用cherry-pick
命令移動提交:
$ git checkout feature/login $ git cherry-pick 776f8caf
您現在將在所需的分支上進行提交,它應該首先出現在該位置。 驚人的!
但是還有一件事要做:我們需要清理一開始它意外降落的分支! 可以這麼說, cherry-pick
命令創建了提交的副本——但原始版本仍然存在於我們長期運行的分支中:
這意味著我們必須切換回我們長期運行的分支並使用git reset
來刪除它:
$ git checkout main $ git reset --hard HEAD~1
如您所見,我們在這裡使用git reset
命令擦除錯誤的提交。 HEAD~1
參數告訴 Git “返回 HEAD 之後的 1 個修訂版”,有效地從該分支的歷史記錄中刪除最頂層(在我們的例子中:不需要的)提交。
瞧:提交現在應該放在最開始的地方,我們長期運行的分支是乾淨的——就好像我們的錯誤從未發生過一樣!
編輯舊提交的消息
將拼寫錯誤偷偷帶入提交消息太容易了——直到很久以後才發現。 在這種情況下, git commit
的舊--amend
選項不能用來解決這個問題,因為它只適用於最後一次提交。 為了更正任何比這更早的提交,我們必須求助於一個名為“Interactive Rebase”的 Git 工具。
首先,我們必須告訴 Interactive Rebase 我們要編輯提交歷史的哪一部分。 這是通過給它提供一個提交哈希來完成的:我們想要操作的那個的父提交。
$ git rebase -i 6bcf266b
然後將打開一個編輯器窗口。 它包含我們在命令中作為交互式 Rebase 基礎提供的提交之後的所有提交的列表:
在這裡,重要的是不要跟隨你的第一個衝動:在這一步中,我們還沒有編輯提交消息。 相反,我們只告訴 Git 我們想要對哪些提交進行什麼樣的操作。 非常方便的是,在此窗口底部的註釋中有一個動作關鍵字列表。 對於我們的例子,我們用reword
標記第 1 行(從而替換標準的pick
)。
在此步驟中剩下要做的就是保存並關閉編輯器窗口。 作為回報,將打開一個新的編輯器窗口,其中包含我們標記的提交的當前消息。 現在終於到了進行編輯的時候了!
整個過程一目了然:
糾正一個破碎的提交(以一種非常優雅的方式)
最後,我們將看一下fixup
,這是撤消工具的瑞士軍刀。 簡而言之,它允許您在事後修復損壞/不完整/不正確的提交。 它確實是一個很棒的工具,原因有兩個:
- 問題是什麼並不重要。
您可能忘記了添加文件、應該刪除了某些內容、進行了不正確的更改,或者只是拼寫錯誤。fixup
適用於所有這些情況! - 它非常優雅。
我們對提交中的錯誤的正常、本能反應是生成一個新的提交來解決問題。 這種工作方式,無論看起來多麼直觀,都會讓你的提交歷史看起來非常混亂,很快。 你有“原始”提交,然後這些小的“創可貼”提交修復了原始提交中出現的問題。 你的歷史中充斥著小的、毫無意義的創可貼提交,這使得你很難理解你的代碼庫中發生了什麼。
這就是fixup
的用武之地。它允許您仍然進行這種更正的創可貼提交。 但是神奇的地方來了:然後它將它應用於原始的、損壞的提交(以這種方式修復它),然後完全丟棄醜陋的創可貼提交!
我們可以一起看一個實際的例子! 假設這裡選擇的提交被破壞了。
假設我已經在一個名為error.html
的文件中準備了可以解決問題的更改。 這是我們需要做的第一步:
$ git add error.html $ git commit --fixup 2b504bee
我們正在創建一個新的提交,但我們告訴 Git 這是一個特殊的提交:它是對具有指定 SHA-1 哈希(在本例中為2b504bee
)的舊提交的修復。
現在,第二步是啟動 Interactive Rebase 會話——因為fixup
屬於 Interactive Rebase 的大工具集。
$ git rebase -i --autosquash 0023cddd
關於這個命令有兩件事值得解釋。 首先,為什麼我在這裡提供0023cddd
作為修訂哈希? 因為我們需要在我們破碎的伙伴的父提交處開始我們的交互式 Rebase 會話。
其次, --autosquash
選項有什麼用? 這需要我們肩上的大量工作! 在現在打開的編輯器窗口中,一切都已經為我們準備好了:
多虧了--autosquash
選項,Git 已經為我們完成了繁重的工作:
- 它用
fixup
action 關鍵字標記了我們的小創可貼提交。 這樣,Git 會將它與上面的提交直接結合,然後丟棄它。 - 它還相應地重新排序了行,將我們的創可貼提交直接移動到我們想要修復的提交下方(再次:
fixup
通過將標記的提交與上面的提交結合起來工作!)。
簡而言之:除了關上窗戶,我們無事可做!
讓我們來看看最終的結果。
- 以前損壞的提交已修復:它現在包含我們在創可貼提交中準備的更改。
- 醜陋的創可貼提交本身已被丟棄:提交歷史清晰且易於閱讀——就好像根本沒有發生任何錯誤一樣。
知道如何消除錯誤是一種超能力
恭喜! 你現在可以在許多困難的情況下挽救你的脖子了! 我們無法真正避免這些情況:無論我們作為開發人員的經驗如何,錯誤只是工作的一部分。 但既然您知道如何應對它們,您就可以用悠閒的心率來面對它們。
如果你想了解更多關於使用 Git 撤消錯誤的信息,我可以推薦免費的“Git 急救包”,這是一系列關於這個主題的短視頻。
享受犯錯的樂趣——當然,輕鬆地撤消它們!