你根本不懂rebase-使用rebase打造可读的git graph
git graph 可讀指什么?
這里的可讀,主要指的是能夠通過看git graph了解每一次版本更迭,每一次hotfix的修改記錄.反映到分支上面,有兩個要求:
- 每個分支的歷史修改可讀(單個分支的層面)
- 每個分支的分叉合并可讀(多個分支的層面)
rebase是什么,它是更優(yōu)雅的merge嗎?
rebase翻譯做變(re)基(base).
講rebase的文章經(jīng)常會引用三張圖:
原本的兩個分支 通過merge的結(jié)果 通過rebase的結(jié)果用來說明git rebase和git merge的區(qū)別的時候確實是足夠了,但是 git rabase的用途并非是合并分支,它與merge根本不是同樣的性質(zhì).(注意,這里的說法是并非是,不是并非只是,因為雖然有時rebase替代了merge的工作,但其原理和性質(zhì)完全不一樣.)
rebase還有以下幾種用處:
- git pull —-rebase處理同一分支上的沖突(如果你能理解其實這是git fetch&&git rebase兩個操作,并且理解遠程分支和本地分支的區(qū)分的話,那么其實他跟單純的rebase用法沒什么區(qū)別,但是因為其場景不一樣,所以單獨拆分出來講)
- git rebase -i修改commit記錄
實質(zhì)上:
- merge是對目前分叉的兩條分支的合并
- rebase是對當(dāng)前分支記錄基于任何commit節(jié)點(不限于當(dāng)前分支上的節(jié)點)的變更.
rebase的base不能理解為分叉的基點,而是整個git庫中存在的所有commit節(jié)點:
- 在git pull —-rebase的時候,這個當(dāng)前分支是本地分支,commit節(jié)點是遠程分支的head
- 在git rebase master的時候,這個當(dāng)前分支是feature分支,commit節(jié)點是master分支的head
- 在git rebase -i的時候,這個當(dāng)前分支就是當(dāng)前工作分支,commit節(jié)點是在 -i后注明的commit
rebase是怎么工作的?
上面我們已經(jīng)說到了:
rebase是對當(dāng)前分支記錄基于任何commit節(jié)點(不限于當(dāng)前分支上的節(jié)點)的變更.
怎么做到呢?我沒有深入研究它真的是如何實現(xiàn)的,以下步驟一定是不對的,但足夠讓你理解rebase干了什么.
我們標(biāo)注出了兩個重點,當(dāng)前分支和commit節(jié)點.
- 把當(dāng)前分支branch-A從頭到尾列出來,從數(shù)據(jù)結(jié)構(gòu)的角度來說這是一個鏈表
- 把commit節(jié)點所在的分支branch-B從頭到尾列出來,同樣是一個鏈表
- 找到這兩個鏈表最近相同的節(jié)點n
- 把A在n之后的所有節(jié)點拆下來構(gòu)成L
- 把B在n之后的所有節(jié)點中存在的diff信息都匯總起來構(gòu)成d
- 對于L中的每一個節(jié)點,把他的diff信息拿出來,看看d中有沒有沖突,如果有沒法自動處理的沖突拋出錯誤,等待用戶自己處理
- 可選地,對于rebase -i來說,還可以一次取多個節(jié)點或者按照不同順序取,你有更大的處理自由
- 沒沖突和處理完沖突的節(jié)點,改一個hash放到branch-B的commit節(jié)點之后 你可以把之前我們說到的三種rebase用處套在以上步驟看看,是否能夠理解.
rebase很危險對嗎?
對,很危險.
不過就像小馬過河一樣,光聽別人說是沒用的,我們需要明白為什么有人說危險,有人說不危險.我看到很多文章說rebase有問題,但他們的說法其實并不讓人信服,很多時候只是他們不會用.
很多人聽說過一個golden rule,在文末有鏈接,但是很少有人會明白真正的原因.讓我們一層層地剖析:
- 其他人git push的時候會對比較本地分支和遠程分支的區(qū)別,把不同的地方推上去
- 如果遠程分支被修改了,那么其他人的本地分支和遠程分支就會出現(xiàn)分叉(另外還可能造成其他人之前已經(jīng)推送的工作被覆蓋)
- 當(dāng)出現(xiàn)分叉的時候,意味著其他人需要處理沖突,也就是說,你對于遠程歷史記錄的修改使得沖突擴散到了其他人身上
- 所以我們盡量不能修改遠程分支,不能把別人fetch回去的改掉,因為他們的工作就是基于fetch回去的分支開展的(往前推進是必須的,其實也修改了遠程分支,所以才會merge產(chǎn)生沖突,但是這個沖突是無法避免的)
- 針對上面說的這一條,git也做了限制,如果你觸犯了上面的原則,會在push的時候被阻擋,但是通過加一個-f可以強推
實際上不止rebase這樣,任何修改遠程分支歷史的操作都會造成沖突,并且這個沖突需要所有人都解決一遍.
但是分析還是太長了,記不住怎么辦?
只需要記住-f,只要你不使用-f,那么就是安全的.
不過僅是安全,并不能保證優(yōu)雅,如果要使git graph可讀,那你還得多想想:
- 怎么讓自己的commit歷史清晰(每個commit反應(yīng)了一個單位的工作,前后順序合理)
- 怎么讓每次hotfix和feature所做的工作和順序清晰
rebase如何讓git graph可讀?
我們還是說回之前提到的三個用法:
git rebase master
在把分支合并回master的時候,用git rebase master代替git merge master.(注意,只在合并之前使用,否則多人協(xié)作會遇到?jīng)_突)
這樣的好處有兩個:
- log里不會出現(xiàn)一個Merge branch 'master' into hotfix/xxx的節(jié)點
- master分支上在這次merge之前已經(jīng)被提交的上一次工作和這一次工作的順序更清晰,因為rebase會讓這次feature的分叉節(jié)點改到上一次工作后.對于master分支來說,我們并不關(guān)心checkout新的feature的順序,我們更關(guān)心merge新的feature的順序.
比如這里,使用merge master導(dǎo)致的紫色的分叉在提交之前與master多了一次連接,而且主線上在紫色分叉合并之前還經(jīng)歷了一次合并,這個時間順序并不清晰.
那么在master分支上合并也用rebase嗎?不是.因為我們需要master上的分叉讓我們更明白master上的改變(所以使用-no-ff).實際上,不管你采用任何git flow模型,我都建議你對不太重要的分支合并采用rebase,對重要的分支合并采用merge.這樣會讓主干的更改更清晰,而分支不會擴散地太遠.
git pull —-rebase
多人在同一分支上工作的時候(包含master分支和多人合作的feature等分支),在git pull的時候會遇到?jīng)_突,git pull的默認(rèn)行為是git fetch&git merge,merge的對象是遠程分支和本地分支.
它的好處基本上與上一條無異,還多了一條:
- 使用merge行為的pull會將其他人的工作作為外來的分叉,從而在graph上產(chǎn)生一個新的分叉, 并且其他人這一段時間所做的所有的工作都會在graph上被抬升出去,如果這段時間其他人做的工作很多,graph的主線會變得喪失了主線的意義(因為它太單薄了,很多工作根本沒反應(yīng)上來).
比如這里,本來左數(shù)第二條玫紅色的才是主線,因為不規(guī)范地在master上直接提交了一次commit并且采用merge方式的pull做了合并導(dǎo)致主線被抬升到了外層.而這次不規(guī)范的commit卻成了主線.
git rebase -i
使用這條命令可以修改分支的記錄,比如覺得之前的commit修改內(nèi)容不夠單元化,像是修改了文案1為文案2,修改了文案2為文案3,這種記錄對于master分支來說是沒必要關(guān)注的信息,最好通過git commit --amend或者rebase的方式修改掉.
不過并不推薦在提交之前手動做一次整個分支的squash,如果是rebase方式合并的話,也許更有意義.工蜂(騰訊內(nèi)部的code平臺)提供了merge request的標(biāo)題和內(nèi)容功能,所以沒必要做squash,完全可以不必太聚合,以便反應(yīng)真實的信息.
為了不影響別人,只用它修改未push的commit,或者如果一條分支只有一個人,你也可以修改已經(jīng)push的commit.
對于這條命令的更多功能,可以再去查閱其他文章.
可讀的graph應(yīng)該長什么樣?
先說一個原則,看graph要先看主線,主線要清晰,再看分叉上信息,這與我們的工作流程是一致的.
綠色的hotfix或者feature分支每次不是只允許提交一次commit,只是這一段都是一些小更改.
這看起來有點可笑,一點都不高級.說了這么多做了這么多難道只是為了得到這么簡單的圖?
沒錯,為了讓東西變簡單,本來就要付出很多代價,我們所做的就是要讓東西變簡單,比如努力工作是為了讓賺錢變簡單,努力提升是為了讓工作變簡單.讓事情變復(fù)雜只會讓事情不可控.
當(dāng)然具體如何還是要取決于你采用的git flow,但是原則很簡單:
- 每個分叉的子分叉盡量是一個串聯(lián)一個,內(nèi)部盡量不要再有自己的提交.
為什么我認(rèn)為這樣的git graph可讀性好,因為它把我們的工作也拍平了,不在乎每個工作的開始時間和持續(xù)時間,只關(guān)心這個工作的完成時間.
假如一個項目需求1是1月1號啟動,2月1號上線,需求2是1月20號啟動,2月10號上線.1月10號修了一個bug,2月3號修了一個bug. 聽起來是不是很繞?
如果你的git graph顯示的也是這樣的信息,可讀性一定不好,所以我們要做的git graph應(yīng)該反應(yīng)的是如下信息:
- 1月10號修補bug
- 2月1號上線需求1
- 2月3號修補bug
- 2月10號上線需求2
rebase的缺點是什么?
(這里并不討論rebase可能帶來的沖突問題,有很多文章都會講,上面也已經(jīng)提到了rebase的危險性,這里只討論rebase對于git graph的缺點.實際上,沖突只是rebase不恰當(dāng)使用導(dǎo)致的問題,而非rebase本身的問題.)
當(dāng)然也有人會說,工作的開始時間也很重要呀,因為它反映了當(dāng)時工作開展的基礎(chǔ)條件.對,這是rebase master的弊端.他讓記錄清晰,也讓記錄丟失了一些信息.記錄的加工讓可讀性變得更好,也讓信息量變少了.
git rebase 讓git graph發(fā)生了變化,每次分叉的檢出和并入之間不會再有任何節(jié)點.(因為合并到master采取的是merge行為.否則根本沒有分叉)
也就是這種情況不會再出現(xiàn).因為每次總是rebase master,把自己的起點抬了上去.git rebase實際上讓檢出信息沒有意義,換取了主分支分叉的清晰.
如果rebase沒有缺點,那么也就沒有爭議.是否使用rebase也要看真實的需求是什么.
這篇文章要干什么?
通過rebase讓git graph更可讀.目的和原則我們都已經(jīng)說過了,沒必要再重新說一遍.
多有謬誤之處,還望不吝賜教!
總結(jié)
以上是生活随笔為你收集整理的你根本不懂rebase-使用rebase打造可读的git graph的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 异形方向盘有隐患吗?汽车博主:既不方便也
- 下一篇: 判断两条链表是否交叉,若有交叉,返回交叉