GIT團隊合作探討之四--不同工作流優缺辨析

世有因果知因求果發表於2016-03-28

由於git非常強大,它可以支援非常多的協作模式,而可能正因為選擇太多反而有時候對於我們如何開始開展團隊協作無從下手。本文試圖闡述企業團隊中應用最為廣泛的git 工作流,為大家理清思路,最大限度發揮git的威力起到借鑑作用。

在你閱讀本文中,需要記住的是這些工作流本身只是一個參考,並不是實際的規則。我只是告訴你哪些是可能的,這樣你可以從不同工作流中取其長處切合到你們自己團隊的日常工作中去。

Centralized Workflow

對於習慣於類似SVN這樣的中央庫版本系統的團隊來說,切換到一個分散式版本控制系統可能有些令人生畏。但是實際上,你們完全可以不用更改你們現在的工作流,也可以利用到GIT提供的強大額外好處。你的團隊甚至可以和SVN一樣的開發模式來工作。

然而,即便這樣,你仍然可以使用GIT來支援你的開發工作流,相比SVN你可以得到以下幾個好處: 首先,你的每個團隊成員都有一份專案的整個copy,這個隔絕的環境可以讓開發人員獨立完成他們自己的工作,他們甚至可以完全不顧別人的改動,直到他們認為需要分享後者需要拉入別的團隊成員的工作時,才需要和別人打交道。

其次:git可以讓你和團隊用到git branch/merge帶來的好處。

如何工作的:

就像SVN一樣,這種集中式的工作流模式使用同一個中央repo庫作為所有對專案變更的唯一入口。

和trunk不同的是,預設開發分支在git中叫做master,而所有改動都commit到這個分支上。這種工作流除了master分支不需要任何其他的分支。

開發人員首先clone 中央庫,在他們本地專案庫中和svn一樣編輯,commit程式碼。然而,這些commit僅僅儲存在本地,而本地庫和中央庫是完全隔離的。這個特性使得開發人員推遲和upstream的同步直到他們認為已經到了一個合適的時機。

為了將本地變更釋出到official project,開發人員通過Push本地master branch 到中央庫的master branch.這和svn commit是一樣的,除了把本地的所有歷史都加到中央庫中。

管理衝突 Managing Conflicts

中央庫代表了官方的專案庫,所以官方庫中的程式碼遞交歷史應該被視為神聖地,不可莫名其妙地丟失。如果一個開發人員的本地commit和中央庫分叉了的話,git將拒絕將他們的變更上傳因為這會覆蓋官方的commits.

在這種情況下,開發人員在publish他們的feature到中央庫之前,他們需要從中央庫中fetch到最新的中央庫commits並且做好rebase,以便你的本地變更實際上執行於中央庫最新版本之上。這個工作好像在說:”我希望確保我的改動是要建立在別人的工作基礎之上“。 這個fetch,rebase的結果是一個線性的歷史記錄,就像在傳統的SVN工作流一樣。

如果本地變更和upstream commit相沖突了,git會停止rebase流程並且給你機會手工解決這些衝突。比較棒的一點是:git 使用git status/git add來完成generating commits和resolving merge conflicts.這種特點使得開發人員可以非常方便地管理他們的merge。並且,如果他們在合併過程中出現了問題,git可以非常方便地讓開發人員放棄整個rebase過程並且隨時可以重新開始。

例子:

讓我們以一個典型案例來看看一個典型的小團隊是如何使用這種工作流來協同工作的。我們假設有兩個開發人員,John和Mary,他們工作在不同的feature上並且通過中央庫來分享他們自己的改動。

首先,他們兩個人中的一人比方說John在伺服器上建立an一箇中央的repo庫。如果是一個全新的專案,你可以建立一個全空的repo,否則你可能需要import一個已經存在的git或者svn repo。

中央庫應該是一個裸庫(裸庫不能有工作目錄),可以這樣來建立:

ssh user@host git init --bare /path/to/repo.git

注意一定要使用一個在host上合法的user資訊來建立裸庫。也請注意一般地我們會給裸庫增加.git字尾,這也是一種裸庫的指示。

下一步,每個開發人員通過git clone來建立他們自己的專案拷貝。

git clone ssh://user@host/path/to/repo.git

當你clone一個repo時,git會自動地增加一個被稱為origin的remote,這個remote指向clone時的那個庫,這往往意味著後面你可以很方便地push/pull origin來實現和中央庫的互動。

再下一步,John開發他自己的功能:

在john自己本地repo中,John可以使用標準的git commit流程來開發功能:edit/stage,commit。

記住:John的所有git add /git commit命令都會建立本地commits,John可以任意遞交他的工作而不用擔心在中央庫上到底發生了什麼。這對於一個大的可以分解成更小的,更加獨立和原子性的小塊的feature開發來說非常適合。

同時,Mary開發他自己的功能:

在上面John本地開發他自己的feature的同時,Mary也在她自己的本地環境中向John一樣開發她自己的feature(edit/stage/commit)。和John一樣,Mary也不用關心這時在中央庫上發生了什麼,她甚至根本不關心John到底在做什麼,因為他們都是在本地repo中開發,是完全隔絕的。

John釋出他的feature

一旦John完成了他的feature開發,他希望將自己的本地commit釋出到中央庫,這樣其他成員就可以訪問他的貢獻了。John通過:

git push origin master

git push命令來完成。記住origin就是當John clone 中央庫時git自動建立的指向這個中央庫的remote connection! 上面命令中master引數告訴git需要將本地master branch和origin/master保持一致(也就是和中央庫的master保持一致,因為origin/master實際上就是中央庫master分支的代表)。既然自從John clone中央庫後,中央庫從未發生改變,這也就意味著John在push時是不會有任何衝突的,只會產生一個fast-forward merge.

Mary隨後試圖釋出她的feature

我們看看在John成功釋出feature後Mary再試圖釋出她的local change到中央庫時會發生什麼。

她也使用相同的命令。 git push origin master

但是由於她的本地歷史和中央庫的歷史已經分叉(原因是John已經做了push到中央庫master),那麼她會得到git拒絕的如下訊息:

error: failed to push some refs to '/path/to/repo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

這將阻止Mary覆蓋正式的commits.她需要拉取同步John的變更到她自己的repo,做好整合合並工作,然後再重試。

Mary rebase on top of John's commit(s)

Mary可以使用git pull命令來拉取upstream變更到她自己的repo中。這條命令很像svn update.

這條命令將upsteam的commit history整個拉取下來並且試圖和她的本地commit整合:

git pull --rebase origin master

上面的--rebase引數告訴git將Mary的所有commit放在John的變更之後,就像下面的圖示一樣:

Mary解決一個merge衝突

Rebase是通過將local commit一個一個地在更新後的master分支上(也就是將origin/master的tip作為自己本地改動的的master起點)執行來完成的。這就意味著你將會一個commit一個commit地捕獲到這個合併過程中的衝突,而不是一團糟糅在一起。這將使得你的commit儘可能地聚焦,從而獲得一個乾淨清潔的專案歷史。同樣地,通過這種方式來執行,我們可以非常方便地指出bug是從哪裡引入的,並且如果需要的話,可以非常方便地回退回去。

如果Mary和John工作在完全不相關的feature上,那麼在rebase過程中很有可能並不會產生任何衝突。而如果確實發生了衝突,則GIT就會暫停在當前commit上的rebase工作,並且輸出下面的錯誤資訊:

CONFLICT (content): Merge conflict in <some-file>

在我們的例子中,Mary可以執行git status命令檢視問題在哪裡。所有衝突的檔案都在Unmerged path部分中展示出來:

# Unmerged paths:
# (use "git reset HEAD <some-file>..." to unstage)
# (use "git add/rm <some-file>..." as appropriate to mark resolution)
#
# both modified: <some-file>

隨後,Mary可以根據實際情況來確定她如何處理這些conflicts。一旦她認為完美解決了這個衝突,她可以將已經合併衝突的檔案stage起來,隨後繼續rebase工作,注意每次解決完衝突之後,原來的那個commit hash就將廢棄變換為新的commit hash

git add <conflicted-files>
git rebase --continue

這就是衝突解決的過程,git隨後會繼續下一個commit的rebase過程,如果發現了衝突,則會繼續上面描述的過程來解決衝突。

注意在解決衝突git add命令執行之後,不要再執行git commit命令,否則會進入detached head狀態,當然你可以隨時使用git rebase --abort來取消push

如果你發現你暫時已經不清楚你現在到底在rebase的什麼狀態下了,也不用恐慌,僅僅通過執行以下的命令你就可以完全回退到你執行git pull --rebase命令之前的狀態,以便你可以再次執行這條命令。

git rebase --abort

Mary成功地釋出了她的feature

在Mary成功和中央庫同步之後(rebase並且解決John的變更衝突),Mary將通過下面的命令完成釋出

git push origin master

正如上面這一段描述的,我們僅僅通過幾條簡單的git命令就能完全實現傳統的svn開發環境。這對於傳統的SVN team轉型到GIT是很便利的,但是這種工作模式並未應用到git真正的分散式開發強大功能。

如果你的團隊已經非常頑固依賴於中央庫的工作模式(SVN技術控),但是又希望簡化這種模型的合作effort,那麼非常適合研究"Feature Branch Workflow"這種工作模式。 通過為每一個feature都設計一條獨立的分支,那麼就可以在完全合入到官方專案分支前做充分的討論,測試和驗證。

Feature Branch workflow

一旦你發現了Centralized Workflow中的合作衝突問題,那麼在開發流程中引入feature branch是一個鼓勵合作和便利化溝通的簡單的方法。

Feature branch workflow的核心想法或原則是:所有的feature開發都必須在特定的branch下而不是直接在master分之下開發。這種模式將使得幾個開發人員同時開發同一個feature的開發活動相對於主分支完全隔離開來。這同時也意味著master分支永遠不會存在未經測試的程式碼,這對於持續整合的開發模式帶來巨大的好處。

封裝隔離一個feature的開發過程也使得充分利用pull request變得方便,而pull request本身就是一個對分支上的程式碼改動啟動討論的最佳實踐。這也給其他開發人員在feature程式碼落地到主分支之前review確認程式碼的機會。或者,當你還在feature開發的中間階段,你也可以發起pull request來獲取其他同事的建議。

如何工作的?

Feature branch workflow仍然使用一箇中央庫,而master分支依然代表這官方的Project history.但是和centralized workflow不同的是,我們不在直接在master分支上做edit/stage/commit,開發人員每次在開發一個新的feature前都建立一個新的feature分支並且開始在這個分支上工作。feature branch應該有語意,比如:animated-menu-items或者issue-1123.這裡主要的想法就是要給每一個分支明確的,高聚焦的目的!

Git在master分支和feature分支兩者之間技術上並未做任何區分,所以開發人員可以像centralized workflow中一樣向feature分支做edit/stage/commit開發工作。

而且,feature branch可以也應該被push到中央庫中。只有這樣,一個feature才方便讓多個開發人員共同開發。

Pull requests

除了隔離feature開發,分支也使得通過pull request來討論變更成為可能。一旦某些人完成了一個feature,他們並不會立即merge到master上去。相反,他們將feature branch push到中央庫中並且發起一個pull request,而請求專案經理來評審並且merge程式碼到master。這種機制給團隊成員在程式碼落地前實現評審。

code review是pull request的一個最大好處,但是pr實際上是一種討論程式碼的同樣基站。你可以將Pull request看作對一個特定分支討論的地方。這也意味著他們可以在開發的早期就加以應用。例如,如果一個開發人員就一個特定feature需要幫助,他們要做的就是啟動一個pull requet.相干人會被通知到,他們會看到相關commit對應的問題。

一旦一個PR被批准,實際的釋出feature的方法是和centralized workflow是完全一樣的。首先你需要確認local master是和upsteam master完全同步的。然後,你merge feature branch到master上,並且將已經更新的master分支push回中央庫。

Pull request可以通過product repository management solutions比如bitbucket cloud/bitbucket server來實現。

例子

下面的例子演示了作為code review機制的pull request是如何工作的:

Mary開始一個新的功能開發

在她開始開發feature之前,mary需要一個隔離的分支來工作,她通過git checkout -b命令來建立分支:

git checkout -b marys-feature master//在master分支最新commit基礎上建立分支。

隨後mary在她的分支上edit/stage/commit.

git status
git add <some-file>
git commit

 

Mary去吃午飯

Mary在早上提交了幾個commits到她的feature分支上去了,在她離開吃無法之前,將她的feature branch push到中央庫上是一個好的注意。因為這將作為一個方便的備份機制,並且如果Mary在和其他開發人員協同的話,這也使他們能訪問到她今早的commit。

git push -u origin marys-feature//將今天早上的工作放到中央庫的feature分支分享給其他成員

上面這條命令將marys-feature push到中央庫(origin)上,注意-u flag則增加這個marys-feature分支作為remote tracking branch.在建立了tracking branch後,mary就可以直接使用git push命令而不用每次都輸入她的marys-feature分支作為引數了。

Mary完成了她的feature開發

當Mary吃完午飯,她完成了全部feature開發。在merge到master之前,她需要啟動一個pull request讓團隊成員知道她已經完成了工作。但是首先,她得確認中央庫已經包含了她的所有最新commits

git push //注意由於上面用了-u引數,這裡就不用再次輸入branch引數了

然後,她發起一個pull request,希望能夠merge她的marys-feature到master分支上去。而team成員則會被自動通知到。

Bill接收到Pull request請求

Bill接收到這個PR請求,並且看了看marys-feature分支的改動。他決定在整合程式碼到official master之前需要做一些變更。Bill和Mary就可以通過這個系統完成幾輪的互動了。

這個階段中所有Mary或者Bill(bill也可以pull這個分支到本地,自己做修改隨後push)的commits都將作為這個pr的follow up,清晰記錄這個過程。一旦Bill決定接受這個pull request,要麼mary,要麼bill需要完成feature落地的工作。

git checkout master
git pull
git pull origin marys-feature
git merge marys-feature git push

首先,執行merge動作的人Mary(或者Bill)在做feature分支向develop分支落地的過程中,需要確保base分支develop或者master要是up-to-date的。需要checkout master分支並且確保是最新的。然後git pull origin marys-feature則merge中央庫的marys-feature分支。最後更新後的master分支需要再次push回到origin上去。

這個流程會產生一個merge commit.有些開發人員喜歡這樣因為這個commit就是主分支合入某一個feature的標誌!但是如果你希望獲得線性的專案歷史,也可以在執行merge之前通過rebase mary-feature分支到master分支之上來實現一個fast-forward merge.

在Mary和Bill開發feature的同時,John也在做同樣的事情

在Mary和Bill在marys-feature分支上開發的同時,John也在他自己的feature分支上做著同樣的事情。通過不同的分支將不同的feature互相隔離,使得每個人都能獨立地工作,然而這個工作流在必要時和別人分享工作仍然顯得有點複雜和繁瑣。

下一步怎樣?

到現在,希望你能看到feature branch實際上倍增了在centralized workflow工作流中的master分支,而且feature branch可以應用pull request作為記錄團隊協作的媒介。

feature branch workflow是一種非常靈活地開發專案的方式。問題是,有時這種方式顯得太靈活。對於大型團隊來說,通常如果給一些特定分支更加明確的角色要好很多。那麼Gitflow workflow就是一個通用的用於管理feature development, release preparation以及維護的工作模式

Gitflow workflow

gitflow workflow是由http://nvie.com/ 唱到並且得到業內大量實踐的版本分支策略模型。

Gitflow workflow圍繞專案release展開定義了一個嚴格的分支模型,雖然看起來這個模型比feature branch workflow要複雜一些,但是卻為管理大型專案提供了一個健壯的框架。

這個工作流並未比feature branch workflow提出了任何更多的概念或者命令,它要做的是:

對每一個不同的分支定義了特定的角色,並且對這些分支之間在什麼時間需要interact互動以及如何做這個互動。除了feature branch外,它使用特定的分支用於準備,維護以及記錄不同的release(preparing,maintaining and recording releases).當然,你也能夠用到feature branch workflow的所有好處: pull request,隔離實現性開發和更高效的合作模型。

如何工作?

Gitflow workflow依然使用一箇中央庫作為所有開發人員溝通的橋樑。同樣地和其他workflow一樣,開發人員本地開發並且push branches到中央庫。唯一的區別是專案的分支結構有所不同。

Historical Branches

gitflow工作流除了單一的master branch外,這個工作流使用兩個分支來記錄專案的歷史(history of project). master分支儲存著official release history,而develop分支則作為各feature分支的整合分支(integration branch)。在master branch上面經常打一個tag也是很必要的。

The rest of this workflow revolves around the distinction between these two branches.

Feature branches

每一個新的feature應該呆在自己的feature branch中,並且該feature branch可以被push到中央庫來做備份或者用於協作的橋樑。注意一點的是:該模型中,feature branch並不是從master分支上拉出,而是從develop分支上拉出來的!也就是說develop分支是feature branch分支的parent branch.當一個feature開發完畢,這個feature分支會被merged back到develop分支。feature永遠不要直接和master分支來打交道。

注意的是feature分支以及develop分支實際上就是上面介紹的feature branch workflow,但是Gitflow工作流並不僅僅是這些。

Release branches

一旦develop分支為一個計劃的release(或者說快到了一個預定義好的release date時)落地了足夠多的feature時,你需要基於develop分支建立生成一個release分支。建立這個分支的同時也意味著下一個release週期的開始 所以從這一刻開始任何新的feature都不允許加到這個release分支中,唯一能做的是在這個release分支上進行bug fix,文件生成,以及其他面向release的任務可以在這個分支上繼續。一旦這個release足夠成熟可以用於釋出和上線部署,release分支就會被merge到master分支並且在master分支打上相應的標籤tag.而且,release分支需要同時merge back到develop分支,以便包含在release準備到釋出的這段時間內的bugfix(注意merge到develop時你可能發現develop分支可能已經向前走過了(相對於release分支拉創的時刻))。

使用一個專用的branch來專門用於準備release使得下面的工作場景成為可能:一個team(維護團隊)來打磨產品當前的release版本,而另一個team(新專案團隊)則繼續為下一個release計劃開發新的feature(在develop分支上)。這也建立了定義良好專案開發不同phase,使得專案更具有可觀性,(比如,非常容易地就說,“這周我們準備version 4.0",並且在repo的結構中能清晰反映出來)。

基本的共識或者說rule:

  • 從develop上branch off
  • 向master/develop上merge into
  • 命名規範: release-*

Maintenance/hotfix Branches

Maintenance或者"hotfix"分支用於快速解決production release執行中發現的bug。這也是唯一一個需要從master分支來branch off的分支。只要fix完成了,這個hotfix分支就應該同時merged到master和develop(或者當前release branch)分支,而master應該被tagged一個新的version number。

有一個專門用於bugfix的分支使得你的團隊在解決問題時不會打斷或者打工作流的其他部分或者等待到下一個release週期。你可以把maintenace/hotfix分支看作和master分支直接聯絡的特設分支。

例子:

下面的例子演示這個gitflow workflow是如何來管理單個release cyle的。我們假設你已經建立了一個central repo.

建立一個develop分支

第一步是在預設的master分支上建立一個develop分支。一個簡單的實現方式是專案經理本地建立一個空的develop分支並且push到server上:

git branch develop
git push -u origin develop

這個develop分支將會包含專案的整個開發歷史,然而master則包含一個刪減版的產品開發歷史。其他的開發人員現在可以clone這個中央庫並且建立一個remote tracking develop branch來開始工作:

git clone ssh://user@host/path/to/repo.git
git checkout -b develop origin/develop //建立一個remote tracking branch for develop

現在每個人都有了一個本地的開發環境。

Mary和John開始開發新的feature

我們的實際例子從John和Mary各自開始在他們自己的feature branch上獨立開發各自的feature開始。他們各自建立一個獨立的feature分支。需要注意的是feature branch並不是從master分支上拉出來,相反,他們應該從develop分支上拉出來。

git checkout -b some-feature develop

他們各自在自己的feature branch上做edit/stage/commit開發活動

git status
git add <some-file>
git commit
...

Mary結束她的feature開發

在建立了一些commit之後,Mary決定她的feature已經完工可以分享了。如果她的team使用pull request,那麼現在將會是一個啟動一個pull request以便申請專案經理feature落地到develop分支的最佳時機。否則如果不用pull request流程的話,她可以本地merge她的feature branch到develop分支並且push develop分支到中央庫(注意:如果Mary是一個feature的team leader她則可以將她建立的feature分支Push到中央庫以便和feature team member協同工作)。

git checkout develop
git pull origin develop
git merge some-feature
git push
git branch -d some-feature

第一個命令確保本地的develop分支是up to date的,要注意的是feature永遠不要直接merged到master.衝突解決的過程和centralized workflow是相同的。

Mary準備計劃釋出一個release

當John依然在他的feature上奮戰時,Mary準備計劃專案的第一個官方release。就像feature開發一樣,她需要使用一個獨立隔絕的分支來做這個release釋出工作。在這個步驟中我們也應該給一個版本號:

git checkout -b release-0.1 develop

這個分支是用於清理release,測試,更新文件以及任何為了本版本釋出的工作開展的地方,release分支就像專門用於打磨這個release的feature branch一樣。

一旦Mary建立並且push了這個branch到中央庫,那麼該release就應該處於feature-frozen狀態。任何還未落地在develop分支上的feature都會被推遲到相愛一個release cycle中。

Mary完成了這個release的準備

一旦這個release可以釋出了,Mary就將release分支merge到master和develop分支,因為在打磨過程中個,可能在該分支上已經發現並且解決了部分critical issue,這些bugfix必須能夠被後續開發或者新的feature所使用。再一次說明一下,如果Mary的組織強調code review,這時候則是啟動給一個pull request的最佳時機。

git checkout master
git merge release-0.1
git push
git checkout develop
git merge release-0.1
git push
git branch -d release-0.1

release branch就像是feature development(develop)和public release(master)之間的一個buffer,任何時候你merge了一些東西到master,你應該tag這個commit:

git tag -a 0.1 -m "Initial public release" master
git push --tags

git內建了一些hooks,這些hooks就是一些在repo中當特定事件發生時可以執行的指令碼。我們可以配置一個hook,當任何人向master分支做了push操作或者push了一個tag之後自動執行一個git archive構建一個public deployment release.

終端使用者發現了一個bug

在釋出了一個release版本後,Mary又開始和John一起持續工作為了下一個release開發新的feature了,直到有一天終端使用者開了一個tickets抱怨說在當前release中存在一個bug.為了解決這個bug,Mary或者John從master分支上那個release tag處建立一個hotfix/maintenance分支,最終經過多次commit解決這個問題,然後直接merge回master.

git checkout -b issue-#001 master-tag-0.2
#Fix the bug
git checkout master
git merge issue-#001
git push

就像release branch一樣,maintenace/hotfix branch包含了重要的bugfix改動,這些改動必須被包含到develop分支上去(如果有running release分支,則也應該merge到這裡)。所以Mary需要執行這個merge工作,完成後就可以刪除這個hotfix/maintenance branch了。

git checkout develop
git merge issue-#001
git push
git branch -d issue-#001

下一步?

記住上面所列的workflow僅僅介紹了可以怎麼用git來匹配組織的工作流程和習慣,他們並不是hard-and-fast rules。所以重要的是一定要取其精華,去其糟粕。我們研究這些工作流模型的目標總是使得git能夠為我所用。

Forking workflow

Forking workflow和前面講到的三種模型有些區別。不同於使用唯一一個server-side repo作為中央庫,這種工作流給每一個開發人員都定義分配一個server-side repo.這意味著每一個contributor都有兩個git repo:一個local one,一個public server-side one;

forking workflow的優點是變更整合時沒有必要讓每個人都push到唯一一箇中央庫。開發人員push到他們自己的server-side repo中。而只有專案經理可以push到official repo中。這允許專案經理在不給每個開發人員都開放寫許可權的前提下來獲取開發人員的commits.

這個工作流的結果是一個分散式的workflow,這個workflow提供了一個對於大型的組織,包括非信任的第三方開發團隊來安全有效地展開合作。這種模式對於open source project也非常適合。

如何工作的:

和其他的git workflow一樣,forking workflow起始於一個儲存於public server上的public repo,但是當一個新的開發人員希望加入一個專案開發,他們要做的第一步並不是從official repo去做clone。相反,他們對official repo做fork動作以便建立一個他們有寫許可權的public server.這個repo作為他們自己的public repo---其他開發人員無權向這個repo做push操作,但是他們可以從這個repo中pull changes到他們的本地。在他們建立了他們自己的server-side copy後,開發人員需要執行一個git clone操作獲取本地repo。這個本地repo作為他們本地的私有開發環境。

當他們本地開發告一段落後,他們將commits push到他們自己的public repo中(注意:並不是official的那個repo)。然後,他們發起一個pull request申請專案經理將他們的程式碼落地。pull request也作為他們討論他的程式碼的最佳場所。

為了將pr中所涉及的程式碼合入official repo中,專案經理pull the contributor's changes到他們本地repo,檢查這段程式碼是否會有問題,如果沒有問題,他們就直接merge到專案經理的local master branch上去,然後專案經理執行push動作將master分支放到官方的repo庫中。到現在,那個PR中的程式碼改動就成了專案程式碼的一部分,其他的開發人員應該從這個官方庫中pull去同步到他們的本地repo中。

官方repo

有一點非常重要:那就是所謂official repo這個概念在forking workflow中僅僅是個約定而已。從技術的角度來說,git並沒有看到official repo和開發人員的public repo有任何不同。事實上,之所以我們稱之為official repo是因為這個repo是來自於專案經理的public repo而已。

Forking workflow中的分支模型

所有上面提到的個人的public repo實際上是一種和其他開發人員分享分支的便利手段。每個人應該繼續使用分支來隔離各自的feature,就像在feature branch workflow和gitflow一樣。唯一的區別是:這些分支的分享方式是不同的。在Forking workflow中,他們被pull到另外一個開發人員的local repo中,而在feature branch和gitflow workflow中,他們被pushed到official repo中。

例子:

專案經理初始化"official repo"

對於git-based 專案,第一步要做的就是在server端建立一個可以被所有團隊成員訪問的official repo。典型地,這個repo會作為專案經理的Public repo

需要注意的是,public repo永遠應該是裸庫,無論他們是否官方庫。所以,專案經理應該這樣建立official庫:

ssh user@host
git init --bare /path/to/repo.git

bitbucket也提供一些方便的gui來建立上述命令。專案經理也應該將code base匯入到這個庫中。

開發人員從official repo來做fork

下一步,所有其他的開發人員需要從這個official repo做fork.

你可以這樣操作:1.ssh到伺服器上,2.執行git clone 來copy到你自己的目錄中;

注意:從上面你可以看到:fork就是server-side的clone操作而已。當然對於bitbucket,允許你點選一兩次滑鼠來完成這個fork過程。

在這個步驟後,每一個開發人員應該有了他們自己的server-side repo,就像official repo一樣,所有的這些repo必須是裸庫。

開發人員從他們forked repo來做clone到本地

這時每個開發人員需要做的是從他們自己的public repo(server-side上)clone到本地。

我們的例子假設使用bitbucket來host這些public repo。記住,在這個條件下,每個開發人員應該有他們自己的bitbucket帳號並且他們應該用下面的命令clone他們自己的server-side repo:

git clone https://user@bitbucket.org/user/repo.git

雖然這篇文章中的其他workflow中都使用一個單一的origin來指向中央庫,而在forking workflow中卻需要兩個remote,一個指向offiicial repo,另一個指向開發人員自己forked的public repo.在這裡,一個通用的命名慣例是:使用origin來指向你forked public repo,而使用upstream來指向official repo

git remote add upstream https://bitbucket.org/maintainer/repo.git

你需要自己建立這個upstream remote,這樣將使得你能夠非常容易地保持你的local repo和官方repo保持一致(up to date).注意:如果你的upstream repo本身需要認證(也就是說非開源專案),那麼你可能需要提供一個username:

git remote add upstream https://user@bitbucket.org/maintainer/repo.git

這樣的話在使用者clone或者pull之前,需要提供密碼

開發人員在他們自己的feature上進行開發工作

在他們剛剛cloned的本地repo中,開發人員可以edit/stage/commit/create branch等等常規操作。

git checkout -b some-feature
#edit some code
git commit -am"add first draft of some feature"

開發人員的所有改動在他們push到他們自己的public repo之前一直是私有的。並且,如果official project向前走了,他們可以通過git pull來獲取到新的專案成果:

git pull upstream master

既然開發人員應該在自己的特定feature branch上展開開發工作,這就應該會產生一個fast-forward merge.

開發人員publish他們的feature

一旦一個開發人員認為他們的feature已經就緒,他們需要做兩件事:

首先,他們把他們的commit釋出到他們自己的public repo中以便別人都能看到,通過下面的命令來完成:

git push origin feature-branch

在這點上,是和其他三種workflow不一樣的,因為在這裡origin是指向開發人員自己私有的server-side repo,而不是official repo(他們應該不具有official repo的寫的許可權)。

第二,他們需要通知專案經理他們需要merge他們的程式碼到official庫中。Bitbucket提供了pull request來允許你發起這樣一個請求過程。典型地,你需要整合你自己的feature branch到upstream的master分支。

專案經理整合開發人員申請落地的feature

當專案經理收到這個pull request,他們的工作就是要決定是否要整合到正式程式碼庫中。他們可以以以下兩種方法中的一種:

1. 直接在pr中檢視程式碼;

2. 將程式碼拉到本地repo並且手工merge

第一種option比較簡單,因為他直接讓專案經理看到具體的變更,並且可以在這些變更上提交comments,通過一個視覺化的介面來執行merge動作。然而,第二種方法在pr可能產生merge衝突時是必須的。在這種情況下,專案經理需要做的是:從開發人員的public repo的feature branch來fetch到程式碼, merge到本地的master branch並且解決所有的衝突:

git fetch https://bitbucket.org/user/repo.git feature-branch
# Inspect the changes
git checkout master
git merge FETCH_HEAD

一旦專案經理將變更整合進了local master,專案經理需要做的是push到official repo中以便其他人可以訪問這些新落地的feature:

git push origin master

記住專案經理的origin指向的是official repo,而到現在開發人員的貢獻就都在official repo中了。

開發人員和official repo保持同步更新

既然official repo已經包含了新的落地程式碼,其他開發人員需要和official庫做好同步更新:

git pull upstream master

 

終於寫完了,自己給自己一個打賞

 

相關文章