整理 | 包包
Git分支和工作流
分支本質(zhì)是一個指向提交對象的可變指針。Git 保存的不是文件的變化或者差異,而是一系列不同時刻的文件快照。在進行提交操作時,會保存一個提交對象(commit object),在多次提交后,commit對象形成連續(xù)的快照鏈,分支指針自動指向最新一次提交。Git 的默認分支名字是 master。如下圖:
branch命令可以輕松創(chuàng)建一個新分支,就像這樣:
$gitbranchnew_branch
這一命令實際是為當前提交對象添加了一個新的指針。這種分支形式比大多數(shù)版本控制系統(tǒng)更為輕量,無論是創(chuàng)建還是切換都幾乎可以在瞬間完成。Git 鼓勵在工作流程中頻繁地使用分支與合并,這完全不會增加倉庫負擔(dān),并且可以基于這一特性創(chuàng)建更自由和更可靠的合作開發(fā)流程。
許多使用Git 的開發(fā)者都喜歡使用這種方式來工作:僅在master分支上保留完全穩(wěn)定的代碼,這些代碼通常處于已發(fā)布或等待發(fā)布的狀態(tài)。此外使用一些短期分支,比如用develop分支開發(fā)新特性,使用test分支修復(fù)bug,測試穩(wěn)定性,直到代碼質(zhì)量達到發(fā)布要求,再合并到master分支,完成一個版本的開發(fā)。
不同的開發(fā)者團隊可以自由創(chuàng)造適合自己組織形式的分支策略。社區(qū)中也存在許多深受歡迎的流程范例,比如經(jīng)典的gitflow工作流、PR工作流、集中式工作流等等,它們通常適用于不同的合作方式,并不是某種強制規(guī)范。有興趣的讀者可以繼續(xù)深入探索,此處不再過多介紹。
merge
假設(shè)我們基于master分支創(chuàng)建了feature分支用來開發(fā)新功能,經(jīng)過一段時間開發(fā)之后,需要把feature的分支代碼合并回到master,通常執(zhí)行的操作是先檢出master分支,然后執(zhí)行g(shù)it merge feature。
一般來說,在單人開發(fā)的情況下,merge通常會產(chǎn)生快進(fast-forward)方式的合并。如果在子分支(feature)被創(chuàng)建之后,父分支(master)未產(chǎn)生新的修改和提交,此時把feature合并回master,Git會在提交鏈上把master指針簡單的前移,使兩個分支進度同步,并形成無分支記錄的提交鏈。執(zhí)行時在控制臺輸出Fast-forward標識。這種merge方式下不會產(chǎn)生沖突,git log命令會看到如下記錄:
但在團隊合作開發(fā)時,通常會多人修改同一遠程分支。其中使用的pull和push命令實際包含了merge操作。這時git使用另外一種方式來進行分支合并。目前只有一方修改的情況下,也可以使用 —no-ff 參數(shù)來模擬這種方式。
這里使用了git最基礎(chǔ)的三路遞歸合并(recursive three-way merge),輸出Merge made by the 'recursive' strategy.標明合并方式。這種合并會形成帶分支歷史的提交鏈:
從圖中可以看出,這種merge方式實際在發(fā)起合并的分支生成了一個帶有Merge 標識的新提交。如果合并時存在沖突,解決沖突后的最終內(nèi)容也會包含在這個新的提交中。
看到這里,可能有人會有疑問,工作空間中自始至終只出現(xiàn)了兩個分支,為什么會是三路合并。從git 源碼中可以找到merge執(zhí)行的入口,它有這樣的方法簽名:
可以看出,除了含義明顯的ours和theirs,還有一個待合并的文件叫做ancestor。根據(jù)文檔和源碼注釋,這個版本實際是兩個待合并分支的公共部分。在我們的例子中就是創(chuàng)建新分支的那個提交對象。
大體的流程是這樣的,git merge會找出兩個分支指向的最新commit,找到他們最近的公共祖先,然后對每個待合并的文件調(diào)用ll_merge,這個方法會比較各分支和祖先節(jié)點的差異。然后把這些差異整合成一個Merge提交,應(yīng)用到當前分支上,生成最終的合并結(jié)果。
如果兩個分支之間有多個公共祖先,git會選出最合適的祖先節(jié)點依照同樣規(guī)則進行遞歸合并。可以使用git merge-base —all命令列出所有的備選祖先節(jié)點。
Git還可以一次性合并多個分支,只需要簡單的把分支名當做merge的參數(shù)依次列出:
這種策略被稱為octopus,其中核心邏輯與three-way merge相同,不再詳述,可以通過閱讀github上的源碼和文檔繼續(xù)深入了解。
three-way merge機制有一定的隱患。如果其中一個待合并分支,比如ours,和ancestor版本的某一部分代碼相同,但另一個待合并分支theirs中有不同的修改,合并的結(jié)果就會采用theirs分支不同的那部分,并不會依照修改的時間順序來決定最終內(nèi)容。在實際項目中可能會反復(fù)修改同一段代碼來響應(yīng)需求變更,就有幾率發(fā)生這種合并結(jié)果與預(yù)計不符的情況,需要特別留意。
rebase
Git rebase,通常被稱作變基或衍合, 可以理解為另外一種合并的方式,與merge 會保留分支結(jié)構(gòu)和原始提交記錄不同,rebase 是在公共祖先的基礎(chǔ)上,把新的提交鏈截取下來,在目標分支上進行重放,逐個應(yīng)用選中的提交來完成合并。
為了形象理解rebase的過程,可以看下面例子:
使用 merge 合并后:
下面使用rebase方式達到同樣效果:
除了原本的多分支記錄變?yōu)榱酥本€提交鏈,還可以注意到,其中原本在feature分支上的提交,rebase后的SHA編碼發(fā)生了變化。rebase消除了真實歷史,重新生成了新的提交。
和merge類似,rebase在遇到?jīng)_突時也會暫停,需要手動修復(fù)后才可以繼續(xù)。但是rebase的處理要相對繁瑣一些,merge 如果發(fā)生 conflict,只需要在最終的Merge 提交上解決一次。而 rebase 的 conflict 可能發(fā)生在每一次提交的重新應(yīng)用上,所以需要依次解決。
為了避免這種情況,可以在與另一分支合并之前,提前把所有需要提交合并為一個提交。同樣需要用到rebase命令。
執(zhí)行這樣一個命令來合并當前最新的3個提交:
這條命令將打開一個編輯頁面,我們可以修改前面的命令來合并或丟棄單個提交。
pick 表示將會應(yīng)用這個提交。
squash 表示把當前提交合并到前一個提交,它的前面必須至少有一個被pick的提交存在。
把某條提交注釋或刪除表示丟棄這條記錄。
這里選擇合并第一個和第三個,丟棄第二個提交。
保存退出后進入新的編輯頁面,提示編輯提交信息,這里選擇不做改動。
再次保存退出后成功合并完成,形成這樣的log:
git還有一個可愛的命令cherry-pick,通常譯作揀選。它的參數(shù)是提交對象的SHA編碼,可以視為針對單個提交的rebase操作。示例如下:
總結(jié)
merge 和 rebase 的差異在于最終的歷史記錄,可以發(fā)現(xiàn) merge 保持了所有分支的原始修改記錄,可能會包含很多不必要的信息;而 rebase相當于對歷史記錄做出修剪,可以維持一條簡單清晰的提交路線。
通常我們會在基于一個過時的版本進行了本地修改的情況下使用rebase,在實際開發(fā)中經(jīng)常會出現(xiàn)這種情況,當你在本地分支上工作了幾天,突然想起應(yīng)該push到遠程倉庫時,遠程分支已經(jīng)被別人更新過了。此時你會得到一個reject信息。
有些人會選擇用pull命令合并遠程和本地的同名分支,但pull實際執(zhí)行了fetch和merge兩個操作,會生成復(fù)雜的分支歷史和一個多余的merge提交。你也可以選擇用fetch和rebase代替pull,始終生成一個美觀的提交鏈。
rebase的另一個重要應(yīng)用是合并過多的本地提交。因為防止修改內(nèi)容丟失,經(jīng)常commit到本地倉庫是一個很好的開發(fā)習(xí)慣。但是當需要提交到公共分支時,大量無明確意義的提交信息對歷史記錄造成不必要的干擾。此時你可以用rebase命令把本地記錄規(guī)范化,再進行推送。
使用rebase的時候需要遵循一條重要原則:不要對在你的本地倉庫外有副本的提交記錄進行變基。rebase的實質(zhì)是丟棄一些現(xiàn)有的提交,然后相應(yīng)地新建一些內(nèi)容一樣但實際上不同的提交。 如果其他人已經(jīng)在這些提交上做出過大量修改、沖突合并等工作,那么你的rebase將成為他們的惡夢。
對于使用rebase還是merge來合并代碼,實際并沒有什么固定的模式,取決于開發(fā)者如何看待倉庫的歷史記錄。一些人認為歷史記錄應(yīng)該反映全部真實變更細節(jié),而另一些人認為歷史記錄應(yīng)該是精心維護的變更目錄。具體如何使用取決于項目合作者的一致共識。無論是merge還是rebase,都應(yīng)該了解其中原理,避免危險操作,才能享受到Git諸多特性帶來的便利。
發(fā)布評論請先 登錄
NVMe控制器之完成信息解析模塊
【教程】Chirpstack V3服務(wù)器簡介和搭建示例

Linux ip命令常用操作
交換機常用配置命令
常用linux命令
飛凌嵌入式ElfBoard ELF 1板卡-git管理源碼之git安裝和使用
Linux系統(tǒng)中shell命令解析
SD的命令和響應(yīng)

SDRAM中的active命令介紹

評論