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