代碼管理是整個(gè)項(xiàng)目管理周期中重要的一環(huán),而代碼管理是始終圍繞版本發(fā)布流程而制定的,今天討論的Gitflow就是一種版本發(fā)布方案。
Gitflow簡(jiǎn)介
Gitflow是一個(gè)基于feature分支管理的版本發(fā)布方案。它是由荷蘭程序猿Vincent Driessen設(shè)計(jì)研發(fā),開(kāi)源項(xiàng)目地址gitflow-avh。
大致流程是:
- 不同feature在不同feature分支上單獨(dú)開(kāi)發(fā)(或測(cè)試)。
- 確定版本號(hào)和此版本將要發(fā)布的功能后,將相應(yīng)featustre分支統(tǒng)一向develop分支合并,然后拉出新的release預(yù)發(fā)布分支。
- release分支作為持續(xù)集成關(guān)注的分支,修復(fù)bug。
- 待release分支測(cè)試驗(yàn)收通過(guò)后,統(tǒng)一向master分支和develop分支合并,并在master分支打tag。
- 根據(jù)tag發(fā)布apk版本。
若線上發(fā)現(xiàn)嚴(yán)重bug,需走h(yuǎn)otfix流程。
- 基于master分支拉出hotfix分支修復(fù)線上問(wèn)題。
- bug修復(fù)完成統(tǒng)一向master和develop分支合并。
- master分支打上新的tag,發(fā)布新版本。
下圖是Gitflow發(fā)布的經(jīng)典圖片,直觀反映了Gitflow發(fā)布的全流程。
它的特點(diǎn)是能靈活的根據(jù)實(shí)際需求發(fā)布相應(yīng)版本,較好的支持并行開(kāi)發(fā),歷史版本用tag進(jìn)行維護(hù)。
下面將介紹如何使用Gitflow命令完成上述版本發(fā)布,一條Gitflow指令可能對(duì)應(yīng)了一系列g(shù)it命令,為的是規(guī)范化開(kāi)發(fā)流程,提高代碼管理效率。
Mac平臺(tái)安裝
brew install git-flow
brew表示Homebrew,它是mac平臺(tái)常用的包管理器,沒(méi)有安裝則需先安裝,安裝可參考Mac OS下brew的安裝和使用。
初始化
先將遠(yuǎn)程倉(cāng)庫(kù)克隆到本地。
git clone <project_url>
各種初始化Gitflow配置。
git flow init
命令行會(huì)提示你是否修改Gitflow提供的默認(rèn)分支前綴。不同場(chǎng)景的分支前綴不同,默認(rèn)情況下分支前綴是這樣的:
場(chǎng)景 | 分支前綴 |
---|---|
新功能 | feature |
預(yù)發(fā)布 | release |
熱修復(fù) | hotfix |
打tag | 空 |
分支前綴的作用是區(qū)分不同分支的使用場(chǎng)景,同時(shí)當(dāng)你使用Gitflow命令時(shí)就不需手動(dòng)添加分支前綴了,Gitflow會(huì)幫你加上。
比如開(kāi)發(fā)新功能需創(chuàng)建一個(gè)feature分支,名為gitworkflow,使用下面的代碼將會(huì)創(chuàng)建一個(gè)名為feature/gitworkflow本地分支。
git flow feature start gitworkflow
通常情況下不需要修改默認(rèn)的命名前綴,只需加上-d就可跳過(guò)修改命名階段。
git flow init -d
行為/Action
通常來(lái)說(shuō),一種場(chǎng)景的完整生命周期應(yīng)至少包含以下幾種行為:
- start 開(kāi)始開(kāi)發(fā)
- publish 發(fā)布到遠(yuǎn)程分支
- finish 完成開(kāi)發(fā)、合并到主分支
我們首先以feature場(chǎng)景為例,看看如何完成工作流。
start
新功能開(kāi)始開(kāi)發(fā)前,需準(zhǔn)備好開(kāi)發(fā)分支。
git flow feature start <feature_name>
執(zhí)行上面的命令將會(huì)在本地創(chuàng)建名為<feature_name>的分支,并切換到該分支,而且不論當(dāng)前所處哪個(gè)分支都是基于develop分支創(chuàng)建,相當(dāng)于執(zhí)行了下面的git的命令。
git checkout -b feature/<feature_name> develop
需要注意基于的是本地的develop分支,執(zhí)行此命令前一般需要拉取最新的遠(yuǎn)程代碼。
publish
在本地開(kāi)發(fā)完成新功能并commit后,需要將本地代碼push到遠(yuǎn)程倉(cāng)庫(kù)。
git flow feature publish <feature_name>
這行指令做了三件事。
- 創(chuàng)建一個(gè)名為feature/<feature_name>的遠(yuǎn)程分支。
- 本地分支track上述遠(yuǎn)程分支。
- 如果本地有未push代碼,則執(zhí)行push。
轉(zhuǎn)換成git命令就是下面的樣子:
git push origin feature/<feature_name>
git push --set-upstream origin feature/<feature_name>
git push origin
注意:
如果已經(jīng)執(zhí)行過(guò)publish后又有新的代碼需push,再次執(zhí)行將報(bào)錯(cuò),因?yàn)樗冀K會(huì)試圖創(chuàng)建一個(gè)遠(yuǎn)程分支。此時(shí)需執(zhí)行正常的push命令git push origin
。
finish
當(dāng)功能開(kāi)發(fā)完畢后就將進(jìn)入測(cè)試階段,此時(shí)需將一個(gè)或多個(gè)feature分支統(tǒng)一合并到develop分支。
git flow feature finish <feature_name>
這行指令也做了三件事。
- 切換到develop分支。
- 合并代碼到develop分支
- 刪除本地feature/<feature_name>分支。
等價(jià)于執(zhí)行下面的git命令:
git checkout develop
git merge feature/<feature_name>
git branch -d feature/<feature_name>
說(shuō)到merge,就不得不提merge采用的策略,我們使用git merge命令時(shí)默認(rèn)(主分支沒(méi)有新的提交、沒(méi)有沖突等情況)使用的是fast-forward模式(以下簡(jiǎn)稱ff),即只移動(dòng)HEAD指針而不會(huì)生成提交記錄。
而Gitflow稍有不同,默認(rèn)情況下它會(huì)檢查本次merge有多少次commit記錄,如果僅有一條采用ff模式,如果超過(guò)一條則采用no-ff模式,no-ff模式下會(huì)多生成一條merge的commit記錄。
這樣做的好處是當(dāng)只有一條提交記錄時(shí)如果生成一條merge記錄實(shí)際上會(huì)復(fù)雜化代碼記錄的管理;當(dāng)有多條commit記錄時(shí)生成的一個(gè)merge記錄,可以方便的進(jìn)行代碼回退和記錄檢查。
回到finish主題,如果merge時(shí)發(fā)生了沖突,則在第二步merge時(shí)終止流程,即不會(huì)再刪除本地分支。但當(dāng)前已處于develop分支,待本地沖突解決并commit后,重新執(zhí)行git flow feature finish <feature_name>
即可完成finish流程。
細(xì)心的同學(xué)可以已經(jīng)發(fā)現(xiàn)finish還有兩件事沒(méi)做。
- develop分支代碼還未push。
- 未刪除遠(yuǎn)程分支feature/<feature_name>。
也就是還需執(zhí)行
git push origin develop
git push origin :feature/<feature_name>
另外,finish指令支持三個(gè)附加參數(shù)。
- -r 即merge前先執(zhí)行rebase(但即使rebase后符合ff條件也不一定會(huì)用ff)。
- -F 即合并完成后連同遠(yuǎn)程分支一并刪除。
- -k 保留本地feature分支,即不執(zhí)行delete動(dòng)作。
所以如果想連同遠(yuǎn)程分支一并刪除可使用。
git flow feature finish -F <feature_name>
如果你對(duì)feature指令感興趣,下面是其支持的所有指令。
git flow feature [list] [-v]
git flow feature start [-F] <name> [<base>]
git flow feature finish [-rFk] <name|nameprefix>
git flow feature publish <name>
git flow feature track <name>
git flow feature diff [<name|nameprefix>]
git flow feature rebase [-i] [<name|nameprefix>]
git flow feature checkout [<name|nameprefix>]
git flow feature pull <remote> [<name>]
release場(chǎng)景
上面的內(nèi)容以feature場(chǎng)景為例,詳細(xì)的分析了整個(gè)新功能執(zhí)行流程。
下面我們?cè)賮?lái)看release場(chǎng)景,連同之前的feature場(chǎng)景,整個(gè)流程如下。
當(dāng)新功能開(kāi)發(fā)完畢,將進(jìn)入測(cè)試階段,此時(shí)需要基于develop分支拉出release分支進(jìn)行集成測(cè)試,也有將release場(chǎng)景作為預(yù)發(fā)布環(huán)境進(jìn)行測(cè)試的,即feature場(chǎng)景已完成常規(guī)測(cè)試,在這種情況下,一般而言release只有少數(shù)改動(dòng)。在這里我們先不討論項(xiàng)目流程問(wèn)題。
使用start指令開(kāi)啟一個(gè)release場(chǎng)景,通常以版本號(hào)命令,我們以v2.0為例:
git flow release start v2.0
此命令會(huì)基于本地的develop分支創(chuàng)建一個(gè)release/v2.0分支,并切換到這個(gè)分支上。
為了讓其他協(xié)同人員也能看到此分支,需要將其發(fā)布出去。
git flow release publish v2.0
以上和feature場(chǎng)景十分類似。
待測(cè)試通過(guò)需要發(fā)布正式版:
git flow release finish v2.0
這一步做的動(dòng)作有點(diǎn)多,大致是:
- git fetch
- release/v2.0分支代碼向master合并。
- 生成名為v2.0的tag。
- release/v2.0分支代碼向develop合并。
- 刪除本地release/v2.0分支。
- 切換回develop分支。
如果merge產(chǎn)生沖突不會(huì)終止流程,只是不會(huì)將本地的release分支刪除,待解決完沖突后需再次執(zhí)行finish指令。
另外需要注意的是,如果本地還有未finish的release分支,將不允許使用start指令開(kāi)啟新的release分支,這一點(diǎn)是對(duì)并行發(fā)布的一個(gè)限制。
release finish只是完成了本地代碼的一系列操作,還需要同步到遠(yuǎn)程倉(cāng)庫(kù)。
git push origin develop
git push origin master
git push origin v2.0
或者使用下面的命令推送所有的分支和tag。
git push origin --all
git push origin --tags
hotfix場(chǎng)景
當(dāng)tag打完,也表示正式版本發(fā)布出去了,如果此時(shí)在線上發(fā)現(xiàn)了嚴(yán)重的bug,需要進(jìn)行緊急修復(fù),流程如下:
此時(shí)我們假設(shè)版本號(hào)變?yōu)関2.0-patch。
git flow hotfix start v2.0-patch
這將創(chuàng)建一個(gè)hotfix/v2.0本地分支并切換到該分支。
hotfix沒(méi)有publish指令,認(rèn)為hotfix應(yīng)該是個(gè)小范圍改動(dòng),不需要其他協(xié)同人員參與。
待本地修改結(jié)束commit后,執(zhí)行finish指令。
git flow hotfix finish v2.0-patch
按照Gitflow工作流,它會(huì)執(zhí)行下面的任務(wù),與release基本一致。
- git fetch
- hotfix/v2.0-patch分支代碼向master合并。
- 生成名為v2.0-patch的tag。
- hotfix/v2.0-patch分支代碼向develop合并。
- 刪除本地hotfix/v2.0-patch分支。
- 切換回develop分支。
快捷腳本
在實(shí)際開(kāi)發(fā)中發(fā)現(xiàn),Gitflow命令有點(diǎn)長(zhǎng),容易敲錯(cuò)。比如git flow feature start <name>
,因此我寫(xiě)了一個(gè)簡(jiǎn)化的指令,通過(guò)它可以這樣寫(xiě)./gitflow -fs <name>
。也就是取Gitflow指令關(guān)鍵字的首字母組合,為新的指令集。
詳情參見(jiàn)Github gitflow.sh。
Sourcetree支持
Sourcetree完全支持Gitflow的所有操作,如果你習(xí)慣于使用圖形化工具管理你的代碼,可以直接使用Sourcetree的Gitflow功能。
了解了上面的命令,這個(gè)圖形化界面基本就都能見(jiàn)名知意了。
其他代碼工作流
與Gitflow相似的代碼管理工作流還有,Github flow和Gitlab flow。
Github flow是簡(jiǎn)化版的Gitflow,它使用master和feature來(lái)管理代碼,它只有一個(gè)長(zhǎng)期分支,就是master。Gitlab flow更關(guān)注代碼的持續(xù)集成,一個(gè)版本需要?jiǎng)?chuàng)建測(cè)試環(huán)境、預(yù)覽環(huán)境、生產(chǎn)環(huán)境的分支,最終匯總到stable branch用于發(fā)布,感興趣的小伙伴可以自行了解。
下表是筆者對(duì)三者特點(diǎn)的理解:
項(xiàng)目 | Gitflow | Github flow | Gitlab flow |
---|---|---|---|
特點(diǎn) | 基于master和develop分支 | 基于master分支和PR | 基于master分支,使用“上游優(yōu)先”原則 |
易用性 | 復(fù)雜,需結(jié)合Gitflow工具 | 簡(jiǎn)單 | 一般 |
并行開(kāi)發(fā) | 簡(jiǎn)單 | 難 | 一般 |
側(cè)重點(diǎn) | 靈活性、多feature管理/版本管理 | 分支維護(hù)效率 | 持續(xù)集成 |
歷史版本維護(hù) | tag | tag或stable branch | stable branch |
實(shí)際上Gitflow的發(fā)布流程一直飽受爭(zhēng)議,反對(duì)者認(rèn)為feature開(kāi)發(fā)周期一般較長(zhǎng),也就是可能在較長(zhǎng)時(shí)間內(nèi)不能合并到主干分支,這不利于持續(xù)集成,且在合并到主干時(shí)可能產(chǎn)生大量沖突,參看這篇Gitflow有害論。
但筆者認(rèn)為如果你正在參與一個(gè)企業(yè)級(jí)的項(xiàng)目,一個(gè)版本將要上線的features可能因各種原因變動(dòng)或下線,此時(shí)如果沒(méi)有獨(dú)立的feature分支而全部在主干分支,將極難處理代碼的回滾和保護(hù);換個(gè)角度說(shuō),merge沖突也可以通過(guò)其他方式規(guī)避,比如良好的代碼分層、事先約定好跨模塊通信的接口等。
從持續(xù)集成的角度來(lái)說(shuō),由于Gitflow管理的分支類型較多,不應(yīng)所有分支都參與持續(xù)集成,可結(jié)合實(shí)際場(chǎng)景選擇開(kāi)啟持續(xù)集成的分支類型,比如master和release。
最后,沒(méi)有最好的方案,只有最適合的方案,實(shí)際的項(xiàng)目管理過(guò)程中應(yīng)根據(jù)不同工作流的特點(diǎn)選擇適合的方案即可。
如果你還有什么疑問(wèn),歡迎留言探討。