記一次Git分支合併引起的問題和修復

ccxryan發表於2019-12-30

背景

Git工作流

在我們的一個專案中,有3-4個人在同時開發,目前採用的是AoneFlow的變體進行分支管理,簡單來說,分為四種型別的分支:主幹分支(master)、特性分支(feature)、釋出分支(release)、開發分支(develop/test)。

每當新功能需求過來後,會從master切出feature分支進行開發,開發完成後合併到test分支中進行測試。多個功能需求測試完成後,將對應的feature分支合併到release分支,再合併到master分支進行釋出。如圖:

記一次Git分支合併引起的問題和修復
這種開發模式下的特點:

  1. test分支是一個“大雜燴”,所有開發完成(包括未測試完成)的feature分支都會合併到test分支。
  2. master分支根據多個feature測試結果來進行選擇性合併到release,作為當前版本最終需要釋出的分支,合併到master進行上線釋出。

問題

在最近一次專案程式碼合併的時候發現了兩個問題:

  1. 一些新開發中的feature分支無法被合併到master分支,提示已經up-to-date。
  2. 把master分支往開發分支develop合併時發現部分程式碼被刪除了。

回顧

錯誤的合併

在發現問題後, 經過一個小時的排查,終於發現在某次提交過程中,我們的一位開發不小心把test分支合併到了自己的featureA分支上。

這意味著,許多開發中的功能被合併到了featureA上。為了解決這個問題,這位同學通過對比,手動刪除了多餘的程式碼,保持了featureA分支只有自己開發的那部分程式碼,也經過測試通過了。

於是分支變成了這個樣子:

記一次Git分支合併引起的問題和修復

釋出上線

在上線程式碼合併到master的時候,我檢查merge request進行例行的code-review,發現確實featureA 功能的提交,於是將featureA 合併到了master準備上線,上線後也沒有任何問題。後續的功能也一個一個都提交上去了。

發現問題

幾天後,一個同學像我反映,在一個新功能的開發中,基於master切出了featureD分支,開發完成後合併到test分支進行測試的時候,出現了很多不是自己開發的程式碼,我當時就很納悶,這種模式下test分支永遠是領先於master分支的,基於master的分支開發完成後提交到test應該只會有包含自己開發功能的那部分程式碼才對呀,最多是產生一些衝突,不會有太多的檔案變更。

最初懷疑是不是拉錯了分支,一看commit記錄,發現了問題的根本原因:

在上次featureA 合併到master的時候,雖然沒有多餘的檔案變更,但是featureA合併過test程式碼,即featureA包含了新增featureB/C, 刪除featureB/C的兩次commit記錄

所以在後續基於Master拉出來的feature分支都會帶上這個錯誤的commit記錄,導致:

  1. test分支上只有新增featureB的程式碼,基於master的新feature分支卻帶上了刪除featureB的commit且領先於test,合併到test的時候會刪除掉featureB的程式碼。
  2. featureB分支再也無法合併到master分支,因為master上最新的commit已經刪除了featureB的程式碼。

如何解決

方案一:在featureA 分支上revert 撤銷那次合併test的Commit

git checkout featureA
git revert commit_id
複製程式碼

提示失敗了

記一次Git分支合併引起的問題和修復
原因是這個commit是一個merge commit,這次commit包含兩個父節點,需要指定你要revert哪一個,即如果A,B合併到C,需要告訴git你要revert的是A提交還是B提交。 這裡使用 —m引數就可以了,1和2代表你要revert哪一個父提交,大多數情況是1,即撤銷“剛才merge進來的那一個提交”

git revert commit_id -m 1
複製程式碼

但是這裡有個需要注意的地方,如果revert掉某次修改後,如果需要再次merge該分支, 需要再做一次反向revert。 即Revert 掉之前的Revert,否則該分支之前的修改無法被提交,因為已經是“落後”於主幹分支了。

方案二:在featureA 分支上reset掉那一次提交記錄

reset和revert不同的是:

  • revert是根據某次的變更記錄,生成一個新的commit,反向刪除/新增對應之前某一次commit的程式碼。
  • reset是“消除”自己的commit記錄重新提交,這種情況下如果reset提交到遠端的分支,需要push -f強制更新,因此強烈不建議在多人協作的分支上這麼做

這裡要注意的是,如果在出錯的commit版本後還有若干次正確的提交,reset後也會一併消失,需要重新加回來,不然後續正確的程式碼也會丟失,按照如下操作進行cherry-pick:

git reset --hard commit_id #退回到出錯前的一個commit

git cherry-pick commit_id_right #將出錯後的commit提交重新加回當前的時間軸

git push -f #強制更新
複製程式碼

方案三: 在featureA分支上進行rebase操作丟棄錯誤的提交

rebase即變基 使用git rebase -i 命令可以壓縮合並多次提交

格式:git rebase -i [startpoint] [endpoint]

其中-i的意思是interactive,即彈出互動式的介面讓使用者編輯完成合並操作,[startpoint] [endpoint]則指定了一個編輯區間,如果不指定[endpoint],則該區間的終點預設是當前分支HEAD所指向的commit(注:該區間指定的是一個前開後閉的區間)。

操作方式為:

  1. 首先根據git log,找到出錯前的一個commit

  2. 執行git rebase -i commit_id 合併之前的提交記錄

  3. 會彈出一個vi編輯器如下:

    記一次Git分支合併引起的問題和修復
    將出錯的那兩次提交,pick改為drop,即丟棄。

  4. Esc :wq儲存

  5. 如果出現衝突

    記一次Git分支合併引起的問題和修復
    需要手動解決衝突後,執行

    git add xxx
    git rebase --continue
    複製程式碼

總結:

  • 在多人合作中,儘量不要把test分支往feature分支上合併
  • 多人協作的分支上出現已經提交到遠端的錯誤的合併時,優先使用revert來進行錯誤回退,並注意後續分支再次合併是否有問題。
  • resetrebase儘量在私人分支或者未提交到遠端的時候使用,如果過去的分支已經提交到遠端,會對其他人的本地分支造成影響。

相關文章