實用:Git 中的一些常見錯誤
無論是資料科學家、演算法工程師還是普通開發人員,在每個團隊協作開發任務中,Git 都是必不可少的版本控制工具,因此掌握它的基本操作十分有必要。但即便是教程滿天飛的今天,開發人員在使用 Git 時也還是會犯一些不應該犯的錯誤。本文總結了其中的幾種常見錯誤,希望能對新手有所幫助。
force push
有時,我們會需要用 force push 把 commit 推送到遠端倉庫。
-
假設有 2 名開發人員正在合作開發一個分支
-
之前開發人員1已經完成更改,把程式碼 push 到了遠端倉庫
-
現在,開發人員 2 也完成了更改,正當他準備提交時,他卻發現自己無法將程式碼推送到遠端倉庫
-
由於開發人員 2 是個初學者,他 Google 了一下,發現了一個神奇的命令 git push -f,於是進行了強制 push
-
之後開發人員 1 在檢查遠端倉庫時,發現自己編寫的程式碼全消失了
出現這個問題的原因是 force push 會覆蓋遠端倉庫中的程式碼,使現有程式碼全部丟失。
如果開發人員 2 想避免這個問題,一種理想方法是他先把開發人員 1 的更新從遠端倉庫 pull 到本地,然後把自己的程式碼 rebase 一下,再進行 push。這裡我們討論的是在同一分支中從遠端到本地倉庫的 rebase。
git push -f 這個命令非常不安全,除非有絕對的必要,大家最好還是不要用它。它會把本地分支的提交覆蓋遠端推送分支的提交,給協作的同伴帶去不少麻煩,即便是上面的解決方案,它也可能存在一個時間差的問題,因為你不可能時刻掌握同伴的工作進展。
所以如果大家都用正確的 git 工作流,讓每個開發人員都擁有自己的功能分支,這種情況根本不會發生。
Rebase
如果你想把一個分支的修改合併到當前分支,你可以用 git rebase。它和 git merge 的區別是 merge 有一個合併 commit 的步驟,而 rebase 是把所有 commit 都串聯在一起,讓你本地的分支歷史看起來像沒有經過任何合併一樣。
-
假設有 2 名開發人員正在合作開發一個功能分支
-
開發人員 1 率先完成了一系列 commit,並將其推送到遠端功能分支
-
之後,開發人員 2 把遠端功能分支的最新更改 pull 到本地
-
開發人員 2 向本地功能分支添加了一堆 commit
-
這時,他想把本地倉庫的更新重新 rebase 到遠端倉庫中,於是他把整個預發分支(release branch)在本地功能分支上 rebase 了一下。這裡我們討論的是在不同分支中從遠端到本地倉庫的 rebase
-
現在,開發人員 2 試著把程式碼 push 到遠端功能分支上,由於提交歷史記錄已更改,這個操作不被允許,他只能又開始用 git push -f
-
最後,當開發人員 1 想從遠端倉庫提取最新程式碼時,由於提交記錄已更改,他被迫需要處理大量程式碼衝突問題
如上圖所示,rebase遠端倉庫會改變提交歷史記錄,並在其他開發人員嘗試從遠端倉庫中提取最新程式碼時產生問題。處理這種情況的理想方法是始終只rebase本地倉庫,本地倉庫中的任何commit都不應該被push到遠端倉庫。
如果別人事先已經把commit推送到遠端功能分支,那麼你最好先用pull命令把更新拉到本地,用merge和你的修改合併,因為merge不會改變提交歷史,而rebase會。
此外,和上個問題一樣,如果使用正確的git工作流,每個開發人員都會有自己的功能分支,這時,開發者在自己的功能分支上進行更新並且在遠端功能分支上做rebase是不會報錯的,因為沒有其他開發人員從同一個遠端功能分支中提取程式碼。無論如何,儘量避免重新定義遠端倉庫。
Rebase是一個非常強大的功能,使用時也需謹慎。
amend
git amend 命令的作用是修復最近一次 commit,讓你合併你快取區的修改和上一次 commit,而不是提交一個新的快照。這裡需要注意一點,它不是修改最近一次 commit,而是整個替換掉原 commit,所以對 Git 來說這是一個新的 commit。此外,它還可以用來編輯上一次的 commit 描述。
-
假設有 2 名開發人員正在合作開發一個功能分支
-
開發人員 1 率先完成了 commit,並將其推送到遠端功能分支,我們把它稱為“old commit”
-
之後,開發人員 2 把最新程式碼從遠端功能分支 pull 到本地功能分支
-
然後他開始處理本地倉庫中的程式碼,在這個過程中,他沒有向遠端倉庫 push 任何 commit
-
這時開發人員 1 突然發現之前的 commit 中存在 bug,他用 amend 命令修復了本地倉庫裡的最近一次 commit,我們把它稱為“new commit”
-
開發人員 1 嘗試把這個新 commit 重新 push 到遠端功能分支,由於提交歷史記錄發生了變化,這個操作報錯了,於是他呼叫了 git push -f
-
現在,當開發人員 2 想從遠端功能分支中提取最新程式碼時,git 會注意到提交歷史記錄的變化並建立合併的 commit。因此當他 pull 到本地後,他會在本地倉庫裡發現“commit old”和“commit new”,這就破壞了 amend 這個操作的意義。
-
最後,即便開發人員 2 從遠端分支到本地分支執行 rebase,這個“commit old”還是會出現在本地倉庫中,它仍然會作為歷史提交的一部分。
amend commit 會更改提交歷史記錄,所以當其他開發人員嘗試從遠端倉庫提取最新程式碼時,修改遠端倉庫中的 commit 會產生混淆。
為了避免這個錯誤,最好的方法是隻在本地倉庫裡修改 commit,不要對遠端庫裡的 commit 做任何修改。當然,一人一個分支也不會出現這個問題。
Hard reset
git reset 命令是用來將當前 branch 重置到另外一個 commit 的。它不會產生 commit,而是隻更新一個 branch(branch 本身就是一個指向一個 commit 的指標)指向另外一個 commit。
-
Hard reset 的命令是 git reset --hard
-
此外,git reset 還有 --soft 和 --mixed,只不過它們都沒有 Hard reset 那麼不安全
-
假設開發人員 1 正在開發一個功能分支,並在本地倉庫中完成了 5 次 commit
-
與此同時,他還正在處理尚未提交的兩個檔案
-
這時,如果他運行了 git reset --hard <commit4hash>
-
那麼功能分支中的最新 commit 會變成是 commit4,commit5 丟失
-
同時他正在處理的那兩個未提交檔案也會丟失
這時 commit5 還在 git 內部,只不過對它的引用丟失了,我們可以用 git reflog 把它恢復,但總體來說,hard reset 還是很不安全。在 git 中使用 reset 命令時要非常小心,如果必須得用,確保你已經完全評估所有情況。
小結
綜上所述,為了避免使用 git 時出錯,我們可以牢記這幾條教訓:
-
避免多人在同一分支上協作。上述四個例子中有三個都是在說明這個問題,在日常工作中,遵守正確的工作流非常重要,要確保只有一個人在一個功能分支上工作,這是技術主管、高階開發人員尤其需要注意的。
-
不要到處實用 force push。
-
如果萬不得已必須使用 force push,先評估其他方案,把它作為最後的手段。
-
不要試圖修改遠端倉庫裡的 commit,要只在本地倉庫中更改 commit 歷史記錄。但即便是在本地倉庫裡,用 rebase 還是要謹慎。
原文地址:adityasridhar.com/posts/how-you-can-go-wrong-with-git