背景
Git工作流
在我們的一個專案中,有3-4個人在同時開發,目前採用的是AoneFlow的變體進行分支管理,簡單來說,分為四種型別的分支:主幹分支(master)、特性分支(feature)、釋出分支(release)、開發分支(develop/test)。
每當新功能需求過來後,會從master切出feature分支進行開發,開發完成後合併到test分支中進行測試。多個功能需求測試完成後,將對應的feature分支合併到release分支,再合併到master分支進行釋出。如圖:
這種開發模式下的特點:- test分支是一個“大雜燴”,所有開發完成(包括未測試完成)的feature分支都會合併到test分支。
- master分支根據多個feature測試結果來進行選擇性合併到release,作為當前版本最終需要釋出的分支,合併到master進行上線釋出。
問題
在最近一次專案程式碼合併的時候發現了兩個問題:
- 一些新開發中的feature分支無法被合併到master分支,提示已經up-to-date。
- 把master分支往開發分支develop合併時發現部分程式碼被刪除了。
回顧
錯誤的合併
在發現問題後, 經過一個小時的排查,終於發現在某次提交過程中,我們的一位開發不小心把test分支合併到了自己的featureA分支上。
這意味著,許多開發中的功能被合併到了featureA上。為了解決這個問題,這位同學通過對比,手動刪除了多餘的程式碼,保持了featureA分支只有自己開發的那部分程式碼,也經過測試通過了。
於是分支變成了這個樣子:
釋出上線
在上線程式碼合併到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記錄,導致:
- test分支上只有新增featureB的程式碼,基於master的新feature分支卻帶上了刪除featureB的commit且領先於test,合併到test的時候會刪除掉featureB的程式碼。
- featureB分支再也無法合併到master分支,因為master上最新的commit已經刪除了featureB的程式碼。
如何解決
方案一:在featureA 分支上revert 撤銷那次合併test的Commit
git checkout featureA
git revert commit_id
複製程式碼
提示失敗了
原因是這個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(注:該區間指定的是一個前開後閉的區間)。
操作方式為:
-
首先根據git log,找到出錯前的一個commit
-
執行
git rebase -i commit_id
合併之前的提交記錄 -
會彈出一個vi編輯器如下:
將出錯的那兩次提交,pick
改為drop
,即丟棄。 -
Esc :wq
儲存 -
如果出現衝突
需要手動解決衝突後,執行git add xxx git rebase --continue 複製程式碼
總結:
- 在多人合作中,儘量不要把test分支往feature分支上合併
- 多人協作的分支上出現已經提交到遠端的錯誤的合併時,優先使用
revert
來進行錯誤回退,並注意後續分支再次合併是否有問題。 reset
和rebase
儘量在私人分支或者未提交到遠端的時候使用,如果過去的分支已經提交到遠端,會對其他人的本地分支造成影響。