前言
今天照常開發(fā),在日常部署測試的時候進(jìn)行g(shù)it merge 竟然出現(xiàn)了"代碼丟失"的情況,相當(dāng)詭異,特此記錄烤惊。
問題由來
首先介紹下公司的日常發(fā)布測試的策略,公司使用git進(jìn)行代碼管理吁朦。如果某應(yīng)用同時有多人開發(fā),采用的方式是A會基于master新建feature_a分支渡贾,B基于master新建feature_b分支逗宜,然后在日常測試部署的時候通常會新建一個新的tag分支,如tag/20160711_test_xxxx空骚,然后將要測試部署的分支feature_a和feature_b合并到tag分支上去纺讲,然后使用tag分支部署并且測試。這樣做是完全沒有問題的囤屹,但是詭異的事情發(fā)生了熬甚,今天這么操作的時候合并出的tag分支丟了一行import,mavan編譯一直出錯
問題描述與分析
最開始碰到這個問題的時候我一直以為是發(fā)布構(gòu)建系統(tǒng)的問題(公司內(nèi)部系統(tǒng))肋坚,后來又懷疑是我在解決merge沖突的時候手賤刪除了這行乡括,重新merge還是出現(xiàn)這個問題。后來由于還得開發(fā)其他功能智厌,想著要不先讓feature_b分支先單獨(dú)發(fā)布測試好了诲泌。一直到下午手里的事情忙完了準(zhǔn)備搞定這個問題,發(fā)現(xiàn)同事feature_b分支還是沒有測試好铣鹏,想著不能等他了敷扫,開始著手解決這個問題。
問題描述如下,feature_a和feature_b同時修改了某個文件xx.java诚卸,feature_a上xx.java大概類似于:
package xxx.xx
import com.xx.xx.A
import com.xx.xx.Common
class xx {
public void commonMethod(Common common){
}
public void aSpecMethod(A a){
}
}
feature_b上的xx.java大概類似于:
package xxx.xx
import com.xx.xx.B
import com.xx.xx.Common
class xx {
public void commonMethod(Common common){
}
public void bSpecMethod(B b){
}
}
按理說合并出來的代碼應(yīng)該類似于:
package xxx.xx
import com.xx.xx.A
import com.xx.xx.B
import com.xx.xx.Common
class xx {
public void commonMethod(Common common){
}
public void aSpecMethod(A a){
}
public void bSpecMethod(B b){
}
}
但是最終合并出來的代碼卻是類似于這樣:
package xxx.xx
import com.xx.xx.B
import com.xx.xx.Common
class xx {
public void commonMethod(Common common){
}
public void aSpecMethod(A a){
}
public void bSpecMethod(B b){
}
}
筒子們發(fā)現(xiàn)問題了嗎葵第,NMmerge之后丟了import com.xx.xx.A這樣绘迁,maven編譯一直報解析不了A的錯誤,A都沒引進(jìn)來卒密,能解析就怪了...
我也是年輕呀缀台,開始的一個小時一直糾結(jié)在是不是這個發(fā)布系統(tǒng)的問題(哈哈,對不起栅受,讓你背鍋了)将硝,后來開始仔細(xì)研究git merge的原理,期間看到了How-does-Git-merge-work這篇文章講得比較通透屏镊。git merge的原理簡單來說就是x+y-w的過程依疼,其中w是x+y的merge base(也就是最近公共祖先),也即是說把y-w(y分支對w分支的改動)patch到x分支上而芥,或者說是把x-w(x分支對w分支的改動)patch到y(tǒng)分支上律罢,具體的做法就是:
在知道原理之后就開始查看feature_b和feature_a分支分別對于xx.java文件的改動,發(fā)現(xiàn)feature_b(同事分支)將import com.xx.xx.A這句刪掉了(看到這我就應(yīng)該反應(yīng)過來的棍丐,但是我還是太連清...)误辑,我當(dāng)時想你刪了就刪了唄,我在feature_a分支引用進(jìn)來了呀歌逢,最后合并肯定還是存在的嘛(我承認(rèn)自己沒仔細(xì)想)巾钉,我接著checkout 到feature_a看了下,有import com.xx.xx.A這句呀秘案,最后合并的時候肯定應(yīng)該有的呀砰苍,git merge肯定不能因?yàn)槟承┓种h了這行就不新增其他分支的代碼,心中一千匹草泥馬在奔騰呀Z甯摺W肌!赤惊!
但是...重要的是但是吼旧,我知道git merge首先會找他們的merge base,會基于這個合并未舟,在該場景里應(yīng)該是master(feature_a和feature_b都是基于master新建的)圈暗,我就checkout到了master上查看了下,發(fā)現(xiàn)master上赫然存在import com.xx.xx.A這行(當(dāng)時xx.java沒有并沒有引用到A.java類裕膀,這個import是多余的)厂置,我瞬間反應(yīng)過來問題了!魂角!
問題解決-真相大白
問題就在于x+y-w時和w是強(qiáng)相關(guān)的昵济,而在這個案例里面,w(也就是master)的代碼為:
package xxx.xx
import com.xx.xx.A
import com.xx.xx.Common
class xx {
public void commonMethod(Common common){
}
master中雖然沒有引用A,但是卻把A引進(jìn)來了(應(yīng)該是某次刪除代碼的時候忘了把Import刪掉了)
先分析下xx.java的合并過程吧
x:代表feature_a
w:master
y:代表feature_b
合并過程為x+(y-w)
y-w在這個過程中表現(xiàn)
增加
-import com.xx.xx.A
+import com.xx.xx.B
+public void bSpecMethod(B b){
+}
x為
package xxx.xx
import com.xx.xx.A
import com.xx.xx.Common
class xx {
public void commonMethod(Common common){
}
public void aSpecMethod(A a){
}
}
合并出來的代碼應(yīng)該為
package xxx.xx
import com.xx.xx.B
import com.xx.xx.Common
class xx {
public void commonMethod(Common common){
}
public void aSpecMethod(A a){
}
public void bSpecMethod(B b){
}
}
哈哈访忿,import com.xx.xx.A就這么丟了瞧栗,而且還就應(yīng)該丟,丟才是正確的海铆!
筒子們迹恐,看出來問題了嗎,問題就在于同事B看到xx.java中A并沒有被引用的時候把A 的import干掉了卧斟,而我在xx.java中加入的方法剛好用到了A(我以為A是我import進(jìn)去的殴边,沒想到竟然master上竟然存在),造成結(jié)果就是git 記住的改動是feature_b把A的import干掉了珍语,feature_a對這個import沒有修改锤岸,結(jié)果就是...
知道了原因,解決就很簡單了板乙,故意將Import A挪個位置并Push讓git意識到change是偷,這樣下次merge的時候git既會知道feature_b的改動也會知道feature_a的改動會觸發(fā)一次merge conflict,然后手動解決就好了
警示
在對類進(jìn)行修改刪除了某些方法時募逞,一定要將無用的import清理掉蛋铆,保持import區(qū)的干凈!7沤印刺啦!