多種多樣的工作流使得在項目中實施Git時變得難以選擇。這份教程提供了一個出發點,調查企業團隊最常見的Git工作流。
閱讀的時候,請記住工作流應該是一種規范而不是金科玉律。我們希望向你展示所有工作流,讓你融會貫通,因地制宜。
這份教程討論了下面四種工作流:
1.中心化的工作流
2.基于功能分支的工作流
3.Gitflow工作流
4.Fork工作流
中心化的工作流
過渡到分布式分版本控制系統看起來是個令人恐懼的任務,但你不必為了利用Git的優點而改變你現有的工作流。你的團隊仍然可以用以前SVN的方式開發項目。
然而,使用Git來驅動你的開發工作流顯示出了一些SVN沒有的優點。首先,它讓每個開發者都有了自己 本地 的完整項目副本。隔離的環境使得每個開發者的工作獨立于項目的其它修改——他們可以在自己的本地倉庫中添加提交,完全無視上游的開發,直到需要的時候。
第二,它讓你接觸到了Git魯棒的分支和合并模型。和SVN不同,Git分支被設計為一種故障安全的機制,用來在倉庫之間整合代碼和共享更改。
如何工作
和Subversion一樣,中心化的工作流將中央倉庫作為項目中所有修改的唯一入口。和trunk
不同,默認的開發分支叫做master,所有更改都被提交到這個分支。這種工作流不需要master之外的其它分支。
開發者將中央倉庫克隆到本地后開始工作。在他們的本地項目副本中,他們可以像SVN一樣修改文件和提交更改;不過,這些新的提交被保存在 本地 ——它們和中央倉庫完全隔離。這使得開發者可以將和上游的同步推遲到他們方便的時候。
為了向官方項目發布修改,開發者將他們的本地master分支“推送”到中央倉庫。這一步等同于svn commit,除了Git添加的是所有不在中央master分支上的本地提交。
管理沖突
中央倉庫代表官方項目,因此它的提交歷史應該被視作神圣不可更改的。如果開發者的本地提交和中央倉庫分叉了,Git會拒絕將他們的修改推送上去,因為這會覆蓋官方提交。
在開發者發布他們的功能之前,他們需要fetch更新的中央提交,在它們之上rebase自己的更改。這就像是:“我想要在其他人的工作進展之上添加我的修改?!彼鼤a生完美的線性歷史,就像和傳統的SVN工作流一樣。
如果本地修改和上游提交沖突時,Git會暫停rebase流程,給你機會手動解決這些沖突。Git很贊的一點是,它將git status
和git add
命令同時用來生成提交和解決合并沖突。這使得開發者能夠輕而易舉地管理他們的合并。另外,如果他們改錯了什么,Git讓他們輕易地退出rebase過程,然后重試(或者找人幫忙)。
栗子
讓我們一步步觀察一個普通的小團隊是如何使用這種工作流協作的。我們有兩位開發者,John和Mary,分別在開發兩個功能,他們通過中心化的倉庫共享代碼。
一人初始化了中央倉庫
首先,需要有人在服務器上創建中央倉庫。如果這是一個新項目,你可以初始化一個空的倉庫。不然,你需要導入一個已經存在的Git或SVN項目。
中央倉庫應該永遠是裸倉庫(沒有工作目錄),可以這樣創建:
ssh user@host git init --bare /path/to/repo.git
但確保你使用的SSH用戶名user、服務器host的域名或IP地址、儲存倉庫的地址/path/to/repo.git是有效的。注意.git約定俗成地出現在倉庫名的后面,表明這是一個裸倉庫。
所有人將倉庫克隆到本地
接下來,每個開發者在本地創建一份完整項目的副本。使用git clone
命令:
git clone ssh://user@host/path/to/repo.git
John在開發他的功能
在他的本地倉庫中,John可以用標準的Git提交流程開發功能:編輯、緩存、提交。如果你對緩存區還不熟悉,你也可以不用記錄工作目錄中每次的變化。于是你創建了一個高度集中的提交,即使你已經在本地做了很多修改。
git status # 查看倉庫狀態
git add <some-file> # 緩存一個文件
git commit # 提交一個文件</some-file>
記住,這些命令創建的是本地提交,John可以周而復始地重復這個過程,而不用考慮中央倉庫。對于龐大的功能,需要切成更簡單、原子化的片段時,這個特性就很有用。
Mary在開發她的功能
同時,Mary在她自己的本地倉庫用相同的編輯/緩存/提交流程開發她的功能。和John一樣,她不需要關心中央倉庫的進展,她也 完全 不關心John在他自己倉庫中做的事,因為所有本地倉庫都是私有的。
John發布了他的功能
一旦John完成了他的功能,他應該將本地提交發布到中央倉庫,這樣其他項目成員就可以訪問了。他可以使用git push命令,就像:
git push origin master
記住,origin是John克隆中央倉庫時指向它的遠程連接。master參數告訴Git試著將origin的master分支變得和他本地的master分支一樣。中央倉庫在John克隆之后還沒有進展,因此這個推送如他所愿,沒有產生沖突。
Mary試圖發布她的功能
John已經成功地將他的更改發布到了中央倉庫上,看看當Mary試著將她的功能推送到上面時會發生什么。她可以使用同一個推送命令:
John已經成功地將他的更改發布到了中央倉庫上,看看當Mary試著將她的功能推送到上面時會發生什么。她可以使用同一個推送命令:
git push origin 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.
Git防止Mary覆蓋官方的修改。她需要將John的更新拉取到她的倉庫,和她的本地修改整合后,然后重試。
Mary在John的提交之上rebase
Mary可以使用git pull
來將上游修改并入她的倉庫。這個命令和svn update
很像——它拉取整個上游提交歷史到Mary的本地倉庫,并和她的本地提交一起整合:
git pull --rebase origin master
--rebase選項告訴Git,在同步了中央倉庫的修改之后,將Mary所有的提交移到master分支的頂端,如下圖所示(圖略)。
如果你忽略這個選項拉取同樣會成功,只不過你每次和中央倉庫同步時都會多出一個“合并提交”。在這種工作流中,rebase和生成一個合并提交相比,總是一個更好的選擇。
Mary解決了合并沖突
Rebase的工作是將每個本地提交一個個轉移到更新后的master分支。也就是說,你可以一個個提交分別解決合并沖突,而不是在一個龐大的合并提交中解決。它會讓你的每個提交保持專注,并獲得一個干凈的項目歷史。另一方面,你更容易發現bug是在哪引入的,如果有必要的話,用最小的代價回滾這些修改。
如果Mary和John開發的功能沒有關聯,rebase的過程不太可能出現沖突。但如果出現沖突時,Git在當前提交會暫停rebase,輸出下面的信息,和一些相關的指令:
CONFLICT (content): Merge conflict in <some-file>
Git的優點在于 每個人 都能解決他們自己的合并沖突。在這個例子中,Mary只需運行一下git status
就可以發現問題是什么。沖突的文件會出現在未合并路徑中:
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>
接下來,修改這些文件。如果她對結果滿意了,和往常一樣緩存這些文件,然后讓git rebase
完成接下來的工作:
git add <some-file>
git rebase --continue
就是這樣。Git會繼續檢查下個提交,對沖突的提交重復這個流程。
如果你這時候發現不知道自己做了什么,不要驚慌。只要運行下面的命令,你就會回到開始之前的狀態:
git rebase --abort
Mary成功發布了她的分支
在她和中央倉庫同步之后,Mary可以成功地發布她的修改:
git push origin master
接下來該怎么做
正如你所見,使用一丟丟Git命令來復制一套傳統的Subversion開發環境也是可行的。這對于從SVN轉變而來的團隊來說很棒,但這樣沒有利用到Git分布式的本質。
如果你的團隊已經習慣了中心化的工作流,但希望提高協作效率,那么探索Feature分支工作流的好處是完全值當的。每個功能在專門的獨立分支上進行,在代碼并入官方項目之前就可以啟動圍繞新修改的深度討論。
Feature分支的工作流
一旦你掌握了中心化工作流的使用姿勢,在你的開發流程中添加功能分支是一個簡單的方式,來促進協作和開發者之間的交流。這種封裝使得多個開發者專注自己的功能而不會打擾主代碼庫。它還保證master分支永遠不會包含損壞的代碼,給持續集成環境帶來了是很大的好處。
封裝功能的開發使得pull request的使用成為可能,用來啟動圍繞一個分支的討論。它給了其他開發者在功能并入主項目之前參與決策的機會?;蛘?,如果你開發功能時卡在一半,你可以發起一個pull request,向同事尋求建議。重點是,pull request使得你的團隊在評論其他人的工作時變得非常簡單。
如何工作
Feature分支工作流同樣使用中央倉庫,master同樣代表官方的項目歷史。但是,與其直接提交在本地的master分支,開發者每次進行新的工作時創建一個新的分支。Feature分支應該包含描述性的名稱,比如animated-menu-items(菜單項動畫)或issue-#1061。每個分支都應該有一個清晰、高度集中的目的。
Git在技術上無法區別master和功能分支,所以開發者可以在feature分支上編輯、緩存、提交,就和中心化工作流中一樣。
此外,feature分支可以(也應該)被推送到中央倉庫。這使得你和其他開發者共享這個功能,而又不改變官方代碼。既然master只是一個“特殊”的分支,在中央倉庫中儲存多個feature分支不會引出什么問題。當然了,這也是備份每個開發者本地提交的好辦法。
Pull Request
除了隔離功能開發之外,分支使得通過pull request討論修改成為可能。一旦有人完成了一個功能,他們不會立即將它并入master。他們將feature分支推送到中央服務器上,發布一個pull request,請求將他們的修改并入master。這給了其他開發者在修改并入主代碼庫之前審查的機會。
代碼審查是pull request的主要好處,但他們事實上被設計為成為討論代碼的一般場所。你可以把pull request看作是專注某個分支的討論版。也就是說他們可以用于開發流程之前。比如,一個開發者在某個功能上需要幫助,他只需發起一個pull request。感興趣的小伙伴會自動收到通知,看到相關提交中的問題。
一旦pull request被接受了,發布功能的行為和中心化的工作流是一樣的。首先,確定你本地的master和上游的master已經同步。然后,將feature分支并入master,將更新的master推送回中央倉庫。
栗子
下面這個演示了代碼審查使用到的pull request,但記住pull request有多種用途。
Mary開始了一個新功能
在她開始開發一個功能之前,Mary需要一個獨立的分支。她可以用下面的命令創建新分支:
git checkout -b marys-feature master
一個基于master、名為marys-feature的分支將會被checkout,-b標記告訴Git在分支不存在時創建它。在這個分支上,Mary和往常一樣編輯、緩存、提交更改,用足夠多的提交來構建這個功能:
git statusgit add <some-file>git commit
Mary去吃飯了
Mary在早上給她的功能添加了一些提交。在她去吃午飯前,將她的分支推送到中央倉庫是個不錯的想法。這是一種方便的備份,但如果Mary和其他開發者一起協作,他們也可以看到她的初始提交了。
git push -u origin marys-feature
這個命令將marys-feature推送到中央倉庫(origin),-u標記將它添加為遠程跟蹤的分支。在設置完跟蹤的分支之后,Mary調用不帶任何參數的git push來推送她的功能。
Mary完成了她的工作
當Mary吃完午飯回來,她完成了她的功能。在并入master之前,她需要發布一個pull request,讓其他的團隊成員知道她所做的工作。但首先,她應該保證中央倉庫包含了她最新的提交:
git push
然后,她在她的Git界面上發起了一個pull request,請求將marys-feature合并進master,團隊成員會收到自動的通知。Pull request的好處是,評論顯示在相關的提交正下方,方便討論特定的修改。
Bill收到了pull request
Bill收到了pull request,并且查看了marys-feature。他決定在并入官方項目之前做一些小修改,通過pull request和Mary進行了溝通。
Mary作了修改
為了做這些更改,Mary重復了之前創建功能時相同的流程,她編輯、緩存、提交、將更新推送到中央倉庫。她所有的活動顯示在pull request中,Bill可以一直評論。
如果Bill想要的話,也可以將marys-featurepull到他自己的本地倉庫,繼續工作。后續的任何提交都會顯示在pull request上。
Mary發布了她的功能
一旦Bill準備接受這個pull request,某個人(Bill或者Mary都可)需要將功能并入穩定的項目:
git checkout mastergit pullgit pull origin marys-featuregit push
首先,不管是誰在執行合并,都要保證他們的master分支是最新的。然后,運行git pull origin marys-feature合并中央倉庫的marys-feature副本。你也可以使用簡單的git merge marys-feature,但之前的命令保證你拉取下來的一定是功能分支最新的版本。最后,更新的master需要被推送回origin。
這個過程導致了一個合并提交。一些開發者喜歡它,因為它是功能和其余代碼合并的標志。但,如果你希望得到線性的歷史,你可以在執行merge之前將功能rebase到master分支的頂端,產生一個快速向前的合并。
一些界面會自動化接受pull request的流程,只需點擊一下“Merge Pull Request”。如果你的沒有的話,它至少在合并之后應該可以自動地關閉pull request。
同時,John以同樣的方式工作著
Mary和Bill一起開發marys-feature,在pull request上討論的同時,John還在開發他自己的feature分支。通過將功能用不同分支隔離開來,每個人可以獨立地工作,但很容易和其他開發者共享修改。
接下來該怎么做
為了徹底了解Github上的功能分支,你應該查看使用分支一章。現在,你應該已經看到了功能分支極大地增強了中心化工作流中單一master分支的作用。除此之外,功能分支還便利了pull request的使用,在版本控制界面上直接討論特定的提交。Gitflow工作流是管理功能開發、發布準備、維護的常見模式。
Gitflow工作流
下面的Gitflow工作流一節源于nvie網站上的作者Vincent Driessen。
Gitflow工作流圍繞項目發布定義了一個嚴格的分支模型。有些地方比功能分支工作流更復雜,為管理大型項目提供了魯棒的框架。
和功能分支工作流相比,這種工作流沒有增加任何新的概念或命令。它給不同的分支指定了特定的角色,定義它們應該如何、什么時候交流。除了功能分支之外,它還為準備發布、維護發布、記錄發布分別使用了單獨的分支。當然,你還能享受到功能分支工作流帶來的所有好處:pull request、隔離實驗和更高效的協作。
如何工作
Gitflow工作流仍然使用中央倉庫作為開發者溝通的中心。和其他工作流一樣,開發者在本地工作,將分支推送到中央倉庫。唯一的區別在于項目的分支結構。
歷史分支
和單獨的master分支不同,這種工作流使用兩個分支來記錄項目歷史。master分支儲存官方發布歷史,develop分支用來整合功能分支。同時,這還方便了在master分支上給所有提交打上版本號標簽。
工作流剩下的部分圍繞這兩個分支的差別展開。
功能分支
每個新功能都放置在自己的分支中,可以在備份/協作時推送到中央倉庫。但是,與其合并到master,功能分支將開發分支作為父分支。當一個功能完成時,它將被合并回develop。功能永遠不應該直接在master上交互。
注意,功能分支加上develop分支就是我們之前所說的功能分支工作流。但是,Gitflow工作流不止于此。
發布分支
一旦develop分支的新功能足夠發布(或者預先確定的發布日期即將到來),你可以從develop分支fork一個發布分支。這個分支的創建開始了下個發布周期,只有和發布相關的任務應該在這個分支進行,如修復bug、生成文檔等。一旦準備好了發布,發布分支將合并進master,打上版本號的標簽。另外,它也應該合并回develop,后者可能在發布啟動之后有了新的進展。
使用一個專門的分支來準備發布確保一個團隊完善當前的發布,其他團隊可以繼續開發下一個發布的功能。它還建立了清晰的開發階段(比如說,“這周我們準備4.0版本的發布”,而我們在倉庫的結構中也能看到這個階段)。
通常我們約定:
從develop創建分支
合并進master分支
命名規范release-* or release/*
維護分支
維護或者“緊急修復”分支用來快速給產品的發布打上補丁。這是唯一可以從master上fork的分支。一旦修復完成了,它應該被并入master和develop分支(或者當前的發布分支),master應該打上更新的版本號的標簽。
有一個專門的bug修復開發線使得你的團隊能夠處理issues,而不打斷其他工作流或是要等到下一個發布周期。你可以將維護分支看作在master分支上工作的臨時發布分支。
栗子
下面的栗子演示了這種工作流如何用來管理發布周期。假設你已經創建了中央倉庫。
創建一個開發分支
你要做的第一步是為默認的master分支創建一個互補的develop分支。最簡單的辦法是在本地創建一個空的develop分支,將它推送到服務器上:
git branch developgit push -u origin develop
這個分支將會包含項目中所有的歷史,而master將包含不完全的版本。其他開發者應該將中央倉庫克隆到本地,創建一個分支來追蹤develop分支:
git clone ssh://user@host/path/to/repo.gitgit checkout -b develop origin/develop
現在所有人都有了一份歷史分支的本地副本。
Mary和John開始了新功能
我們的栗子從John和Mary在不同分支上工作開始。他們都要為自己的功能創建單獨的分支。他們的功能分支都應該基于develop,而不是master:
git checkout -b some-feature develop
他們都使用“編輯、緩存、提交”的一般約定來向功能分支添加提交:
git statusgit add <some-file>git commit
Mary完成了她的功能
在添加了一些提交之后,Mary確信她的功能以及準備好了。如果她的團隊使用pull request,現在正是發起pull request的好時候,請求將她的功能并入develop分支。否則,她可以向下面一樣,將它并入本地的develop分支,推送到中央倉庫:
git pull origin developgit checkout developgit merge some-featuregit pushgit branch -d some-feature
第一個命令在嘗試并入功能分支之前確保develop分支已是最新。注意,功能絕不該被直接并入master。沖突的處理方式和中心化工作流相同。
Mary開始準備發布
當John仍然在他的功能分支上工作時,Mary開始準備項目的第一個官方發布。和開發功能一樣,她新建了一個分支來封裝發布的準備工作。這也正是發布的版本號創建的一步:
git checkout -b release-0.1 develop
這個分支用來整理提交,充分測試,更新文檔,為即將到來的發布做各種準備。它就像是一個專門用來完善發布的功能分支。
一旦Mary創建了這個分支,推送到中央倉庫,這次發布的功能便被鎖定了。不在develop分支中的功能將被推遲到下個發布周期。
Mary完成了她的發布
一旦發布準備穩妥,Mary將它并入master和develop,然后刪除發布分支。合并回develop很重要,因為可能已經有關鍵的更新添加到了發布分支上,而開發新功能需要用到它們。同樣的,如果Mary的團隊重視代碼審查,現在將是發起pull request的完美時機。
git checkout mastergit merge release-0.1git pushgit checkout developgit merge release-0.1git pushgit branch -d release-0.1
發布分支是功能開發(develop)和公開發布(master)之間的過渡階段。不論什么時候將提交并入master時,你應該為提交打上方便引用的標簽:
git tag -a 0.1 -m "Initial public release" mastergit push --tags
Git提供了許多鉤子,即倉庫中特定事件發生時被執行的腳本。當你向中央倉庫推送master分支或者標簽時,你可以配置一個鉤子來自動化構建公開發布。
終端用戶發現了一個bug
正式發布之后,Mary回過頭來和John一起為下一個發布開發功能。這時,一個終端用戶開了一個issue抱怨說當前發布中存在一個bug。為了解決這個bug,Mary(或John)從master創建了一個維護分支,用幾個提交修復這個issue,然后直接合并回master。
git checkout -b issue-#001 master# Fix the buggit checkout mastergit merge issue-#001git push
和發布分支一樣,維護分支包含了develop中需要的重要更新,因此Mary同樣需要執行這個合并。接下來,她可以刪除這個分支了:
git checkout developgit merge issue-#001git pushgit branch -d issue-#001
接下來該怎么做
現在,希望你已經很熟悉中心化的工作流、功能分支工作流和Gitflow工作流。你應該已經可以抓住本地倉庫、推送/拉取模式,和Git魯棒的分支和合并模型的無限潛力。
請記住,教程中呈現的工作流只是可行的實踐——而非工作中使用Git的金科玉律。因此,盡情地取其精華,去其糟粕吧。不變的是要讓Git為你所用,而不是相反。
Fork工作流
Fork工作流和教程中討論的其它工作流截然不同。與其使用唯一的服務端倉庫作為”中央“代碼庫,它給予 每個 開發者一個服務端倉庫。也就是說每個貢獻者都有兩個Git倉庫,而不是一個:一個私有的本地倉庫和一個公開的服務端倉庫。
Fork工作流的主要優點在于貢獻可以輕易地整合進項目,而不需要每個人都推送到單一的中央倉庫。開發者推送到他們 自己的 服務端倉庫,只有項目管理者可以推送到官方倉庫。這使得管理者可以接受任何開發者的提交,卻不需要給他們中央倉庫的權限。
結論是,這種分布式的工作流為大型、組織性強的團隊(包括不可信的第三方)提供了安全的協作方式。它同時也是開源項目理想的工作流。
如何工作
和其它Git工作流一樣,Fork工作流以一個儲存在服務端的官方公開項目開場。但新的開發者想參與項目時,他們不直接克隆官方項目。
取而代之地,他們fork一份官方項目,在服務端創建一份副本。這份新建的副本作為他們私有的公開倉庫——沒有其他開發者可以在上面推送,但他們可以從上面拉取修改(在后面我們會討論為什么這一點很重要)。在他們創建了服務端副本之后,開發者執行git clone操作,在他們的本地機器上復制一份。這是他們私有的開發環境,正如其他工作流中一樣。
當他們準備好發布本地提交時,他們將提交推送到自己的公開倉庫——而非官方倉庫。然后,他們向主倉庫發起一個pull request,讓項目維護者知道一個更新做好了合并的準備。如果貢獻的代碼有什么問題的話,Pull request可以作為一個方便的討論版。
我為了將功能并入官方代碼庫,維護者將貢獻者的修改拉取到他們的本地倉庫,確保修改不會破壞項目,將它合并到本地的master分支,然后將master分支推送到服務端的官方倉庫。貢獻現在已是項目的一部分,其他開發者應該從官方倉庫拉取并同步他們的本地倉庫。
中央倉庫
“官方”倉庫這個概念在Fork工作流中只是一個約定,理解這一點很重要。從技術的角度,Git并看不出每個開發者和官方的公開倉庫有什么區別。事實上,官方倉庫唯一官方的原因是,它是項目維護者的倉庫。
Fork工作流中的分支
所有這些個人的公開倉庫只是一個在開發者之間共享分支的約定。每個人仍然可以使用分支來隔離功能,就像在功能分支工作流和Gitflow工作流中一樣。唯一的區別在于這些分支是如何開始的。在Fork工作流中,它們從另一個開發者的本地倉庫拉取而來,而在功能分支和Gitflow分支它們被推送到官方倉庫。
栗子
項目維護者初始化了中央倉庫
和任何基于Git的項目一樣,第一步是在服務端創建一個可以被所有項目成員訪問到的官方倉庫。一般來說,這個倉庫同時還是項目維護者的公開倉庫。
公開的倉庫應該永遠是裸的,不管它們是否代表官方代碼庫。所以項目維護者應該運行下面這樣的命令來設置官方倉庫:
ssh user@hostgit init --bare /path/to/repo.git
Github同時提供了一個圖形化界面來替代上面的操作。這和教程中其它工作流設置中央倉庫的流程完全一致。如果有必要的話,項目維護者應該將已有的代碼庫推送到這個倉庫中。
開發者fork倉庫
接下來,所有開發者需要fork官方倉庫。你可以用SSH到服務器,運行git clone將它復制到服務器的另一個地址——fork其實只是服務端的clone。但同樣地,Github上開發者只需點一點按鈕就可以fork倉庫。
在這步之后,每個開發者應該都有了自己的服務端倉庫。像官方倉庫一樣,所有這些倉庫都應該是裸倉庫。
開發者將fork的倉庫克隆到本地
接下來開發者需要克隆他們自己的公開倉庫。他們可以用熟悉的git clone命令來完成這一步。
我們的栗子假設使用他們使用Github來托管倉庫。記住,在這種情況下,每個開發者應該有他們自己的Github賬號,應該用下面的命令克隆服務端倉庫:
git clone https://user@github.com/user/repo.git
而教程中的其他工作流使用單一的origin遠程連接,指向中央倉庫,Fork工作流需要兩個遠程連接,一個是中央倉庫,另一個是開發者個人的服務端倉庫。你可以給這些遠端取任何名字,約定的做法是將origin作為你fork后的倉庫的遠端(運行git clone是會自動創建)和upstream作為官方項目。
git remote add upstream https://github.com/maintainer/repo
你需要使用上面的命令來創建上游倉庫的遠程連接。它使得你輕易地保持本地倉庫和官方倉庫的進展同步。注意如果你的上游倉庫開啟了認證(比如它沒有開源),你需要提供一個用戶名,就像這樣:
git remote add upstream https://user@bitbucket.org/maintainer/repo.git
它需要用戶從官方代碼庫克隆或拉取之前提供有效的密碼。
開發者進行自己的開發
在他們剛克隆的本地倉庫中,開發者可以編輯代碼、提交更改,和其它分支中一樣######創建分支:
git checkout -b some-feature# 編輯代碼git commit -a -m "Add first draft of some feature"
他們所有的更改在推送到公開倉庫之前都是完全私有的。而且,如果官方項目已經向前進展了,他們可以用git pull獲取新的提交:
git pull upstream master
因為開發者應該在專門的功能分支開發,這一般會產生一個快速向前的合并。
開發者發布他們的功能
一旦開發者準備好共享他們的新功能,他們需要做兩件事情。第一,他們必須將貢獻的代碼推送到自己的公開倉庫,讓其他開發者能夠訪問到。他們的origin遠端應該已經設置好了,所以他們只需要:
git push origin feature-branch
這和其他工作流不同之處在于,origin遠端指向開發者個人的服務端倉庫,而不是主代碼庫。
第二,他們需要通知項目維護者,他們想要將功能并入官方代碼庫。Github提供了一個“New Pull Request”按鈕,跳轉到一個網頁,讓你指明想要并入主倉庫的分支。一般來說,你希望將功能分支并入上游遠端的master分支。
項目維護者整合他們的功能
當項目維護者收到pull request時,他們的工作是決定是否將它并入官方的代碼庫。他們可以使用下面兩種方式之一:
直接檢查pull request中檢查代碼
將代碼拉取到本地倉庫然后手動合并
第一個選項更簡單,讓維護者查看修改前后的差異,在上面評論,然后通過圖形界面執行合并。然而,如果pull request會導致合并沖突,第二個選項就有了必要。在這個情況中,維護者需要從開發者的服務端倉庫fetch功能分支,合并到他們本地的master分支,然后解決沖突:
git fetch https://bitbucket.org/user/repo feature-branch# 檢查修改git checkout mastergit merge FETCH_HEAD
一旦修改被整合進本地的master,維護者需要將它推送到服務器上的官方倉庫,這樣其他開發者也可以訪問它:
git push origin master
記住,維護者的origin指向他們的公開倉庫,也就是項目的官方代碼庫。開發者的貢獻現在完全并入了項目。
開發者和中央倉庫保持同步
因為主代碼庫已經取得了新的進展,其他開發者應該和官方倉庫同步:
git pull upstream master
接下來該怎么做
如果你從SVN遷移而來,Fork工作流看上去是一個比較大的轉變。但不要害怕——它只是在Feature分支工作流之上引入了一層抽象。貢獻的代碼發布到開發者在服務端自己的倉庫,而不是在唯一的中央倉庫中直接共享分支。
這篇文章解釋了一次代碼貢獻是如何從一個開發者流入官方的master分支的,但相同的方法可以用在將代碼貢獻整合進任何倉庫。比如,如果你團隊的一部分成員在一個特定功能上協作,他們可以用自己約定的行為共享修改——而不改變主倉庫。
這使得Fork工作流對于松散的團隊來說是個非常強大的工具。任何開發者都可以輕而易舉地和其他開發者共享修改,任何分支都能高效地并入主代碼庫。