深入理解學習Git工作流

自由的部落格發表於2015-06-23

個人在學習git工作流的過程中,從原有的 SVN 模式很難完全理解git的協作模式,直到有一天我看到了下面的文章,好多遺留在心中的困惑迎刃而解,於是我將這部分資料進行整理放到了github上,歡迎star檢視最新更新內容, https://github.com/xirong/my-git/blob/master/git-workflow-tutorial.md

  • 我們以使用SVN的工作流來使用git有什麼不妥?
  • git 方便的branch在哪裡,團隊多人如何協作?衝突了怎麼辦?如何進行釋出控制?
  • 經典的master-釋出、develop-主開發、hotfix-不過修復如何避免程式碼不經過驗證上線?
  • 如何在github上面與他人一起協作,star-fork-pull request是怎樣的流程?

我個人很感激這篇文章,所以進行了整理,希望能幫到更多的人。整篇文章由 xirong 整理自 oldratlee 的github,方便統一的學習回顧,在此感謝下面兩位的貢獻。

原文連結:Git Workflows and Tutorials
簡體中文:由 oldratlee 翻譯在 github 上 git-workflows-and-tutorials

  • 一、譯序
  • 二、Git工作流指南
    • 2.1 集中式工作流
      • 2.1.1 工作方式
      • 2.1.2 衝突解決
      • 2.1.3 示例
        • 有人先初始化好中央倉庫
        • 所有人克隆中央倉庫
        • 小明開發功能
        • 小紅開發功能
        • 小明發布功能
        • 小紅試著釋出功能
        • 小紅在小明的提交之上rebase
        • 小紅解決合併衝突
        • 小紅成功釋出功能
    • 2.2 功能分支工作流
      • 2.2.1 工作方式
      • 2.2.2 Pull Requests
      • 2.2.3 示例
        • 小紅開始開發一個新功能
        • 小紅要去吃個午飯
        • 小紅完成功能開發
        • 小黑收到Pull Request
        • 小紅再做修改
        • 小紅髮布她的功能
        • 與此同時,小明在做和小紅一樣的事
    • 2.3 Gitflow工作流
      • 2.3.1 工作方式
      • 2.3.2 歷史分支
      • 2.3.3 功能分支
      • 2.3.4 釋出分支
      • 2.3.5 維護分支
      • 2.3.6 示例
        • 建立開發分支
        • 小紅和小明開始開發新功能
        • 小紅完成功能開發
        • 小紅開始準備釋出
        • 小紅完成釋出
        • 終端使用者發現Bug
    • 2.4 Forking工作流
      • 2.4.1 工作方式
      • 2.4.2 正式倉庫
      • 2.4.3 Forking工作流的分支使用方式
      • 2.4.4 示例
        • 專案維護者初始化正式倉庫
        • 開發者fork正式倉庫
        • 開發者克隆自己fork出來的倉庫
        • 開發者開發自己的功能
        • 開發者釋出自己的功能
        • 專案維護者整合開發者的功能
        • 開發者和正式倉庫做同步
    • 2.5 Pull Requests
      • 2.5.1 解析Pull Request
      • 2.5.2 工作方式
      • 2.5.3 在功能分支工作流中使用Pull Request
      • 2.5.4 在Gitflow工作流中使用Pull Request
      • 2.5.5 在Forking工作流中使用Pull Request
      • 2.5.6 示例
        • 小紅fork正式專案
        • 小紅克隆她的Bitbucket倉庫
        • 小紅開發新功能
        • 小紅push功能到她的Bitbucket倉庫中
        • 小紅髮起Pull Request
        • 小明review Pull Request
        • 小紅補加提交
        • 小明接受Pull Request

 

一、譯序

工作流其實不是一個初級主題,背後的本質問題其實是有效的專案流程管理和高效的開發協同約定,不僅是GitSVNVCSSCM工具的使用。

這篇指南以大家在SVN中已經廣為熟悉使用的集中式工作流作為起點,循序漸進地演進到其它高效的分散式工作流,還介紹瞭如何配合使用便利的Pull Request功能,體系地講解了各種工作流的應用。

行文中實踐原則和操作示例並重,對於Git的資深玩家可以梳理思考提升,而新接觸的同學,也可以跟著step-by-step操作來操練學習並在實際工作中上手使用。

關於Git工作流主題,網上體系的中文資料不多,主要是零散的操作說明,希望這篇文章能讓你更深入理解並在工作中靈活有效地使用起來。

PS

文中Pull Request的介紹用的是Bitbucket程式碼託管服務,由於和GitHub基本一樣,如果你用的是GitHub(我自己也主要使用GitHub託管程式碼),不影響理解和操作。

PPS

本指南循序漸進地講解工作流,如果Git用的不多,可以從前面的講的工作流開始操練。操作過程去感受指南的講解:解決什麼問題、如何解決問題,這樣理解就深了,也方便活用。

Gitflow工作流是經典模型,體現了工作流的經驗和精髓。隨著專案過程複雜化,會感受到這個工作流中深思熟慮和威力!

Forking工作流是協作的(GitHub風格)可以先看看Github的Help:Fork A RepoUsing pull requests 。照著操作,給一個Github專案貢獻你的提交,有操作經驗再看指南容易意會。指南中給了自己實現Fork的方法Fork就是服務端的克隆。在指南的操練中使用程式碼託管服務(如GitHubBitbucket),可以點一下按鈕就讓開發者完成倉庫的fork操作。

:see_no_evil: 自己理解粗淺,翻譯中不足和不對之處,歡迎建議(提交Issue)和指正(Fork後提交程式碼)!

二、Git工作流指南

:point_right: 工作流有各式各樣的用法,但也正因此使得在實際工作中如何上手使用變得很頭大。這篇指南通過總覽公司團隊中最常用的幾種Git工作流讓大家可以上手使用。

在閱讀的過程中請記住,本文中的幾種工作流是作為方案指導而不是條例規定。在展示了各種工作流可能的用法後,你可以從不同的工作流中挑選或揉合出一個滿足你自己需求的工作流。

Git Workflows

2.1 集中式工作流

如果你的開發團隊成員已經很熟悉Subversion,集中式工作流讓你無需去適應一個全新流程就可以體驗Git帶來的收益。這個工作流也可以作為向更Git風格工作流遷移的友好過渡。

Git Workflows: SVN-style

轉到分散式版本控制系統看起來像個令人生畏的任務,但不改變已用的工作流你也可以用上Git帶來的收益。團隊可以用和Subversion完全不變的方式來開發專案。

但使用Git加強開發的工作流,Git有相比SVN的幾個優勢。
首先,每個開發可以有屬於自己的整個工程的本地拷貝。隔離的環境讓各個開發者的工作和專案的其他部分修改獨立開來 ——
即自由地提交到自己的本地倉庫,先完全忽略上游的開發,直到方便的時候再把修改反饋上去。

其次,Git提供了強壯的分支和合並模型。不像SVNGit的分支設計成可以做為一種用來在倉庫之間整合程式碼和分享修改的『失敗安全』的機制。

2.1.1 工作方式

Subversion一樣,集中式工作流以中央倉庫作為專案所有修改的單點實體。相比SVN預設的開發分支trunkGit叫做master,所有修改提交到這個分支上。本工作流只用到master這一個分支。

開發者開始先克隆中央倉庫。在自己的專案拷貝中像SVN一樣的編輯檔案和提交修改;但修改是存在本地的,和中央倉庫是完全隔離的。開發者可以把和上游的同步延後到一個方便時間點。

要釋出修改到正式專案中,開發者要把本地master分支的修改『推』到中央倉庫中。這相當於svn commit操作,但push操作會把所有還不在中央倉庫的本地提交都推上去。

git-workflow-svn-push-local

2.1.2 衝突解決

中央倉庫代表了正式專案,所以提交歷史應該被尊重且是穩定不變的。如果開發者本地的提交歷史和中央倉庫有分歧,Git會拒絕push提交否則會覆蓋已經在中央庫的正式提交。

git-workflow-svn-managingconflicts

在開發者提交自己功能修改到中央庫前,需要先fetch在中央庫的新增提交,rebase自己提交到中央庫提交歷史之上。
這樣做的意思是在說,『我要把自己的修改加到別人已經完成的修改上。』最終的結果是一個完美的線性歷史,就像以前的SVN的工作流中一樣。

如果本地修改和上游提交有衝突,Git會暫停rebase過程,給你手動解決衝突的機會。Git解決合併衝突,用和生成提交一樣的git statusgit add命令,很一致方便。還有一點,如果解決衝突時遇到麻煩,Git可以很簡單中止整個rebase操作,重來一次(或者讓別人來幫助解決)。

2.1.3 示例

讓我們一起逐步分解來看看一個常見的小團隊如何用這個工作流來協作的。有兩個開發者小明和小紅,看他們是如何開發自己的功能並提交到中央倉庫上的。

有人先初始化好中央倉庫

第一步,有人在伺服器上建立好中央倉庫。如果是新專案,你可以初始化一個空倉庫;否則你要匯入已有的GitSVN倉庫。

中央倉庫應該是個裸倉庫(bare repository),即沒有工作目錄(working directory)的倉庫。可以用下面的命令建立:

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

確保寫上有效的userSSH的使用者名稱),host(伺服器的域名或IP地址),/path/to/repo.git(你想存放倉庫的位置)。
注意,為了表示是一個裸倉庫,按照約定加上.git副檔名到倉庫名上。

所有人克隆中央倉庫

下一步,各個開發者建立整個專案的本地拷貝。通過git clone命令完成:

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

基於你後續會持續和克隆的倉庫做互動的假設,克隆倉庫時Git會自動新增遠端別名origin指回『父』倉庫。

小明開發功能

在小明的本地倉庫中,他使用標準的Git過程開發功能:編輯、暫存(Stage)和提交。
如果你不熟悉暫存區(Staging Area),這裡說明一下:暫存區的用來準備一個提交,但可以不用把工作目錄中所有的修改內容都包含進來。
這樣你可以建立一個高度聚焦的提交,儘管你本地修改很多內容。

git status # 檢視本地倉庫的修改狀態
git add # 暫存檔案
git commit # 提交檔案

請記住,因為這些命令生成的是本地提交,小明可以按自己需求反覆操作多次,而不用擔心中央倉庫上有了什麼操作。
對需要多個更簡單更原子分塊的大功能,這個做法是很有用的。

小紅開發功能

與此同時,小紅在自己的本地倉庫中用相同的編輯、暫存和提交過程開發功能。和小明一樣,她也不關心中央倉庫有沒有新提交;
當然更不關心小明在他的本地倉庫中的操作,因為所有本地倉庫都是私有的。

小明發布功能

一旦小明完成了他的功能開發,會發布他的本地提交到中央倉庫中,這樣其它團隊成員可以看到他的修改。他可以用下面的git push命令

git push origin master

注意,origin是在小明克隆倉庫時Git建立的遠端中央倉庫別名。master引數告訴Git推送的分支。
由於中央倉庫自從小明克隆以來還沒有被更新過,所以push操作不會有衝突,成功完成。

小紅試著釋出功能

一起來看看在小明發布修改後,小紅push修改會怎麼樣?她使用完全一樣的push命令:

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.

這避免了小紅覆寫正式的提交。她要先pull小明的更新到她的本地倉庫合併上她的本地修改後,再重試。

小紅在小明的提交之上rebase

小紅用git pull合併上游的修改到自己的倉庫中。
這條命令類似svn update——拉取所有上游提交命令到小紅的本地倉庫,並嘗試和她的本地修改合併:

git pull --rebase origin master

--rebase選項告訴Git把小紅的提交移到同步了中央倉庫修改後的master分支的頂部,如下圖所示:

如果你忘加了這個選項,pull操作仍然可以完成,但每次pull操作要同步中央倉庫中別人修改時,提交歷史會以一個多餘的『合併提交』結尾。

對於集中式工作流,最好是使用rebase而不是生成一個合併提交。

小紅解決合併衝突

rebase操作過程是把本地提交一次一個地遷移到更新了的中央倉庫master分支之上。
這意味著可能要解決在遷移某個提交時出現的合併衝突,而不是解決包含了所有提交的大型合併時所出現的衝突。
這樣的方式讓你儘可能保持每個提交的聚焦和專案歷史的整潔。反過來,簡化了哪裡引入Bug的分析,如果有必要,回滾修改也可以做到對專案影響最小。

如果小紅和小明的功能是相關的,不大可能在rebase過程中有衝突。如果有,Git在合併有衝突的提交處暫停rebase過程,輸出下面的資訊並帶上相關的指令:

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

Git很讚的一點是,任何人可以解決他自己的衝突。在這個例子中,小紅可以簡單的執行git status命令來檢視哪裡有問題。
衝突檔案列在Unmerged paths(未合併路徑)一節中:

# 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 pull --rebase命令前的樣子:

git rebase --abort

小紅成功釋出功能

小紅完成和中央倉庫的同步後,就能成功釋出她的修改了:

git push origin master

如你所見,僅使用幾個Git命令我們就可以模擬出傳統Subversion開發環境。對於要從SVN遷移過來的團隊來說這太好了,但沒有發揮出Git分散式本質的優勢。

如果你的團隊適應了集中式工作流,但想要更流暢的協作效果,絕對值得探索一下 功能分支工作流 的收益。
通過為一個功能分配一個專門的分支,能夠做到一個新增功能整合到正式專案之前對新功能進行深入討論。

2.2 功能分支工作流

功能分支工作流以集中式工作流為基礎,不同的是為各個新功能分配一個專門的分支來開發。這樣可以在把新功能整合到正式專案前,用Pull Requests的方式討論變更。

Git Workflows: Feature Branch

一旦你玩轉了集中式工作流,在開發過程中可以很簡單地加上功能分支,用來鼓勵開發者之間協作和簡化交流。

功能分支工作流背後的核心思路是所有的功能開發應該在一個專門的分支,而不是在master分支上。
這個隔離可以方便多個開發者在各自的功能上開發而不會弄亂主幹程式碼。
另外,也保證了master分支的程式碼一定不會是有問題的,極大有利於整合環境。

功能開發隔離也讓pull requests工作流成功可能,
pull requests工作流能為每個分支發起一個討論,在分支合入正式專案之前,給其它開發者有表示贊同的機會。
另外,如果你在功能開發中有問題卡住了,可以開一個pull requests來向同學們徵求建議。
這些做法的重點就是,pull requests讓團隊成員之間互相評論工作變成非常方便!

2.2.1 工作方式

功能分支工作流仍然用中央倉庫,並且master分支還是代表了正式專案的歷史。
但不是直接提交本地歷史到各自的本地master分支,開發者每次在開始新功能前先建立一個新分支。
功能分支應該有個有描述性的名字,比如animated-menu-itemsissue-#1061,這樣可以讓分支有個清楚且高聚焦的用途。

master分支和功能分支之間,Git是沒有技術上的區別,所以開發者可以用和集中式工作流中完全一樣的方式編輯、暫存和提交修改到功能分支上。

另外,功能分支也可以(且應該)push到中央倉庫中。這樣不修改正式程式碼就可以和其它開發者分享提交的功能。
由於master僅有的一個『特殊』分支,在中央倉庫上存多個功能分支不會有任何問題。當然,這樣做也可以很方便地備份各自的本地提交。

2.2.2 Pull Requests

功能分支除了可以隔離功能的開發,也使得通過Pull Requests討論變更成為可能。
一旦某個開發完成一個功能,不是立即合併到master,而是push到中央倉庫的功能分支上併發起一個Pull Request請求去合併修改到master
在修改成為主幹程式碼前,這讓其它的開發者有機會先去Review變更。

Code ReviewPull Requests的一個重要的收益,但Pull Requests目的是討論程式碼一個通用方式。
你可以把Pull Requests作為專門給某個分支的討論。這意味著可以在更早的開發過程中就可以進行Code Review
比如,一個開發者開發功能需要幫助時,要做的就是發起一個Pull Request,相關的人就會自動收到通知,在相關的提交旁邊能看到需要幫助解決的問題。

一旦Pull Request被接受了,釋出功能要做的就和集中式工作流就很像了。
首先,確定本地的master分支和上游的master分支是同步的。然後合併功能分支到本地master分支並push已經更新的本地master分支到中央倉庫。

倉庫管理的產品解決方案像BitbucketStash,可以良好地支援Pull Requests。可以看看StashPull Requests文件

2.2.3 示例

下面的示例演示瞭如何把Pull Requests作為Code Review的方式,但注意Pull Requests可以用於很多其它的目的。

小紅開始開發一個新功能

在開始開發功能前,小紅需要一個獨立的分支。使用下面的命令新建一個分支

git checkout -b marys-feature master

這個命令檢出一個基於master名為marys-feature的分支,Git-b選項表示如果分支還不存在則新建分支。
這個新分支上,小紅按老套路編輯、暫存和提交修改,按需要提交以實現功能:

git status
git add <some-file>
git commit

小紅要去吃個午飯

早上小紅為新功能新增一些提交。
去吃午飯前,push功能分支到中央倉庫是很好的做法,這樣可以方便地備份,如果和其它開發協作,也讓他們可以看到小紅的提交。

git push -u origin marys-feature

這條命令push marys-feature分支到中央倉庫(origin),-u選項設定本地分支去跟蹤遠端對應的分支。
設定好跟蹤的分支後,小紅就可以使用git push命令省去指定推送分支的引數。

小紅完成功能開發

小紅吃完午飯回來,完成整個功能的開發。在合併到master之前
她發起一個Pull Request讓團隊的其它人知道功能已經完成。但首先,她要確認中央倉庫中已經有她最近的提交:

git push

然後,在她的Git GUI客戶端中發起Pull Request,請求合併marys-featuremaster,團隊成員會自動收到通知。
Pull Request很酷的是可以在相關的提交旁邊顯示評註,所以你可以很對某個變更集提問。

小黑收到Pull Request

小黑收到了Pull Request後會檢視marys-feature的修改。決定在合併到正式專案前是否要做些修改,且通過Pull Request和小紅來回地討論。

小紅再做修改

要再做修改,小紅用和功能第一個迭代完全一樣的過程。編輯、暫存、提交併push更新到中央倉庫。小紅這些活動都會顯示在Pull Request上,小黑可以斷續做評註。

如果小黑有需要,也可以把marys-feature分支拉到本地,自己來修改,他加的提交也會一樣顯示在Pull Request上。

小紅髮布她的功能

一旦小黑可以的接受Pull Request,就可以合併功能到穩定專案程式碼中(可以由小黑或是小紅來做這個操作):

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

無論誰來做合併,首先要檢出master分支並確認是它是最新的。然後執行git pull origin marys-feature合併marys-feature分支到和已經和遠端一致的本地master分支。
你可以使用簡單git merge marys-feature命令,但前面的命令可以保證總是最新的新功能分支。
最後更新的master分支要重新push回到origin

這個過程常常會生成一個合併提交。有些開發者喜歡有合併提交,因為它像一個新功能和原來程式碼基線的連通符。
但如果你偏愛線性的提交歷史,可以在執行合併時rebase新功能到master分支的頂部,這樣生成一個快進(fast-forward)的合併。

一些GUI客戶端可以只要點一下『接受』按鈕執行好上面的命令來自動化Pull Request接受過程。
如果你的不能這樣,至少在功能合併到master分支後能自動關閉Pull Request

與此同時,小明在做和小紅一樣的事

當小紅和小黑在marys-feature上工作並討論她的Pull Request的時候,小明在自己的功能分支上做完全一樣的事。

通過隔離功能到獨立的分支上,每個人都可以自主的工作,當然必要的時候在開發者之間分享變更還是比較繁瑣的。

到了這裡,但願你發現了功能分支可以很直接地在 集中式工作流 的僅有的master分支上完成多功能的開發。
另外,功能分支還使用了Pull Request,使得可以在你的版本控制GUI客戶端中討論某個提交。

功能分支工作流是開發專案異常靈活的方式。問題是,有時候太靈活了。對於大型團隊,常常需要給不同分支分配一個更具體的角色。
Gitflow工作流是管理功能開發、釋出準備和維護的常用模式。

2.3 Gitflow工作流

Gitflow工作流通過為功能開發、釋出準備和維護分配獨立的分支,讓釋出迭代過程更流暢。嚴格的分支模型也為大型專案提供了一些非常必要的結構。

Git Workflows: Gitflow Cycle

這節介紹的Gitflow工作流借鑑自在nvieVincent Driessen

Gitflow工作流定義了一個圍繞專案釋出的嚴格分支模型。雖然比功能分支工作流複雜幾分,但提供了用於一個健壯的用於管理大型專案的框架。

Gitflow工作流沒有用超出功能分支工作流的概念和命令,而是為不同的分支分配一個很明確的角色,並定義分支之間如何和什麼時候進行互動。
除了使用功能分支,在做準備、維護和記錄釋出也使用各自的分支。
當然你可以用上功能分支工作流所有的好處:Pull Requests、隔離實驗性開發和更高效的協作。

2.3.1 工作方式

Gitflow工作流仍然用中央倉庫作為所有開發者的互動中心。和其它的工作流一樣,開發者在本地工作並push分支到要中央倉庫中。

2.3.2 歷史分支

相對使用僅有的一個master分支,Gitflow工作流使用2個分支來記錄專案的歷史。master分支儲存了正式釋出的歷史,而develop分支作為功能的整合分支。
這樣也方便master分支上的所有提交分配一個版本號。

剩下要說明的問題圍繞著這2個分支的區別展開。

2.3.3 功能分支

每個新功能位於一個自己的分支,這樣可以push到中央倉庫以備份和協作
但功能分支不是從master分支上拉出新分支,而是使用develop分支作為父分支。當新功能完成時,合併回develop分支
新功能提交應該從不直接與master分支互動。

注意,從各種含義和目的上來看,功能分支加上develop分支就是功能分支工作流的用法。但Gitflow工作流沒有在這裡止步。

2.3.4 釋出分支

一旦develop分支上有了做一次釋出(或者說快到了既定的釋出日)的足夠功能,就從develop分支上fork一個釋出分支。
新建的分支用於開始釋出迴圈,所以從這個時間點開始之後新的功能不能再加到這個分支上——
這個分支只應該做Bug修復、文件生成和其它面向釋出任務。
一旦對外發布的工作都完成了,釋出分支合併到master分支並分配一個版本號打好Tag
另外,這些從新建釋出分支以來的做的修改要合併回develop分支。

使用一個用於釋出準備的專門分支,使得一個團隊可以在完善當前的釋出版本的同時,另一個團隊可以繼續開發下個版本的功能。
這也打造定義良好的開發階段(比如,可以很輕鬆地說,『這周我們要做準備釋出版本4.0』,並且在倉庫的目錄結構中可以實際看到)。

常用的分支約定:

用於新建釋出分支的分支: develop
用於合併的分支: master
分支命名: release-* 或 release/*

2.3.5 維護分支

維護分支或說是熱修復(hotfix)分支用於生成快速給產品釋出版本(production releases)打補丁,這是唯一可以直接從master分支fork出來的分支。
修復完成,修改應該馬上合併回master分支和develop分支(當前的釋出分支),master分支應該用新的版本號打好Tag

Bug修復使用專門分支,讓團隊可以處理掉問題而不用打斷其它工作或是等待下一個釋出迴圈。
你可以把維護分支想成是一個直接在master分支上處理的臨時釋出。

2.3.6 示例

下面的示例演示本工作流如何用於管理單個釋出迴圈。假設你已經建立了一箇中央倉庫。

建立開發分支

第一步為master分支配套一個develop分支。簡單來做可以本地建立一個空的develop分支push到伺服器上:

git branch develop
git push -u origin develop

以後這個分支將會包含了專案的全部歷史,而master分支將只包含了部分歷史。其它開發者這時應該克隆中央倉庫,建好develop分支的跟蹤分支:

git clone ssh://user@host/path/to/repo.git
git checkout -b develop origin/develop

現在每個開發都有了這些歷史分支的本地拷貝。

小紅和小明開始開發新功能

這個示例中,小紅和小明開始各自的功能開發。他們需要為各自的功能建立相應的分支。新分支不是基於master分支,而是應該基於develop分支

git checkout -b some-feature develop

他們用老套路新增提交到各自功能分支上:編輯、暫存、提交:

git status
git add <some-file>
git commit

小紅完成功能開發

新增了提交後,小紅覺得她的功能OK了。如果團隊使用Pull Requests,這時候可以發起一個用於合併到develop分支。
否則她可以直接合併到她本地的develop分支後push到中央倉庫:

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

第一條命令在合併功能前確保develop分支是最新的。注意,功能決不應該直接合併到master分支。
衝突解決方法和集中式工作流一樣。

小紅開始準備釋出

這個時候小明正在實現他的功能,小紅開始準備她的第一個專案正式釋出。
像功能開發一樣,她用一個新的分支來做釋出準備。這一步也確定了釋出的版本號:

git checkout -b release-0.1 develop

這個分支是清理髮布、執行所有測試、更新文件和其它為下個釋出做準備操作的地方,像是一個專門用於改善釋出的功能分支。

只要小紅建立這個分支並push到中央倉庫,這個釋出就是功能凍結的。任何不在develop分支中的新功能都推到下個釋出迴圈中。

小紅完成釋出

一旦準備好了對外發布,小紅合併修改到master分支和develop分支上,刪除釋出分支。合併回develop分支很重要,因為在釋出分支中已經提交的更新需要在後面的新功能中也要是可用的。
另外,如果小紅的團隊要求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

釋出分支是作為功能開發(develop分支)和對外發布(master分支)間的緩衝。只要有合併到master分支,就應該打好Tag以方便跟蹤。

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

Git有提供各種勾子(hook),即倉庫有事件發生時觸發執行的指令碼。
可以配置一個勾子,在你push中央倉庫的master分支時,自動構建好對外發布。

終端使用者發現Bug

對外發布後,小紅回去和小明一起做下個釋出的新功能開發,直到有終端使用者開了一個Ticket抱怨當前版本的一個Bug
為了處理Bug,小紅(或小明)從master分支上拉出了一個維護分支,提交修改以解決問題,然後直接合並回master分支:

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

就像釋出分支,維護分支中新加這些重要修改需要包含到develop分支中,所以小紅要執行一個合併操作。然後就可以安全地刪除這個分支了:

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

到了這裡,但願你對集中式工作流功能分支工作流Gitflow工作流已經感覺很舒適了。
你應該也牢固的掌握了本地倉庫的潛能,push/pull模式和Git健壯的分支和合並模型。

記住,這裡演示的工作流只是可能用法的例子,而不是在實際工作中使用Git不可違逆的條例。
所以不要畏懼按自己需要對工作流的用法做取捨。不變的目標就是讓Git為你所用。

2.4 Forking工作流

Forking工作流是分散式工作流,充分利用了Git在分支和克隆上的優勢。可以安全可靠地管理大團隊的開發者(developer),並能接受不信任貢獻者(contributor)的提交。

Forking工作流和前面討論的幾種工作流有根本的不同,這種工作流不是使用單個服務端倉庫作為『中央』程式碼基線,而讓各個開發者都有一個服務端倉庫。這意味著各個程式碼貢獻者有2個Git倉庫而不是1個:一個本地私有的,另一個服務端公開的。

Forking工作流的一個主要優勢是,貢獻的程式碼可以被整合,而不需要所有人都能push程式碼到僅有的中央倉庫中。
開發者push到自己的服務端倉庫,而只有專案維護者才能push到正式倉庫。
這樣專案維護者可以接受任何開發者的提交,但無需給他正式程式碼庫的寫許可權。

效果就是一個分散式的工作流,能為大型、自發性的團隊(包括了不受信的第三方)提供靈活的方式來安全的協作。
也讓這個工作流成為開源專案的理想工作流。

2.4.1 工作方式

和其它的Git工作流一樣,Forking工作流要先有一個公開的正式倉庫儲存在伺服器上。
但一個新的開發者想要在專案上工作時,不是直接從正式倉庫克隆,而是fork正式專案在伺服器上建立一個拷貝。

這個倉庫拷貝作為他個人公開倉庫 ——
其它開發者不允許push到這個倉庫,但可以pull到修改(後面我們很快就會看這點很重要)。
在建立了自己服務端拷貝之後,和之前的工作流一樣,開發者執行git clone命令克隆倉庫到本地機器上,作為私有的開發環境。

要提交本地修改時,push提交到自己公開倉庫中 —— 而不是正式倉庫中。
然後,給正式倉庫發起一個pull request,讓專案維護者知道有更新已經準備好可以整合了。
對於貢獻的程式碼,pull request也可以很方便地作為一個討論的地方。

為了整合功能到正式程式碼庫,維護者pull貢獻者的變更到自己的本地倉庫中,檢查變更以確保不會讓專案出錯,
合併變更到自己本地的master分支
然後pushmaster分支到伺服器的正式倉庫中。
到此,貢獻的提交成為了專案的一部分,其它的開發者應該執行pull操作與正式倉庫同步自己本地倉庫。

2.4.2 正式倉庫

Forking工作流中,『官方』倉庫的叫法只是一個約定,理解這點很重要。
從技術上來看,各個開發者倉庫和正式倉庫在Git看來沒有任何區別。
事實上,讓正式倉庫之所以正式的唯一原因是它是專案維護者的公開倉庫。

2.4.3 Forking工作流的分支使用方式

所有的個人公開倉庫實際上只是為了方便和其它的開發者共享分支。
各個開發者應該用分支隔離各個功能,就像在功能分支工作流Gitflow工作流一樣。
唯一的區別是這些分支被共享了。在Forking工作流中這些分支會被pull到另一個開發者的本地倉庫中,而在功能分支工作流和Gitflow工作流中是直接被push到正式倉庫中。

2.4.4 示例

專案維護者初始化正式倉庫

和任何使用Git專案一樣,第一步是建立在伺服器上一個正式倉庫,讓所有團隊成員都可以訪問到。
通常這個倉庫也會作為專案維護者的公開倉庫。

公開倉庫應該是裸倉庫,不管是不是正式程式碼庫。
所以專案維護者會執行像下面的命令來搭建正式倉庫:

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

BitbucketStash提供了一個方便的GUI客戶端以完成上面命令列做的事。
這個搭建中央倉庫的過程和前面提到的工作流完全一樣。
如果有現存的程式碼庫,維護者也要push到這個倉庫中。

開發者fork正式倉庫

其它所有的開發需要fork正式倉庫。
可以用git clone命令SSH協議連通到伺服器
拷貝倉庫到伺服器另一個位置 —— 是的,fork操作基本上就只是一個服務端的克隆。
BitbucketStash上可以點一下按鈕就讓開發者完成倉庫的fork操作。

這一步完成後,每個開發都在服務端有一個自己的倉庫。和正式倉庫一樣,這些倉庫應該是裸倉庫。

開發者克隆自己fork出來的倉庫

下一步,各個開發者要克隆自己的公開倉庫,用熟悉的git clone命令。

在這個示例中,假定用Bitbucket託管了倉庫。記住,如果這樣的話各個開發者需要有各自的Bitbucket賬號,
使用下面命令克隆服務端自己的倉庫:

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

相比前面介紹的工作流只用了一個origin遠端別名指向中央倉庫,Forking工作流需要2個遠端別名 ——
一個指向正式倉庫,另一個指向開發者自己的服務端倉庫。別名的名字可以任意命名,常見的約定是使用origin作為遠端克隆的倉庫的別名
(這個別名會在執行git clone自動建立),upstream(上游)作為正式倉庫的別名。

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

需要自己用上面的命令建立upstream別名。這樣可以簡單地保持本地倉庫和正式倉庫的同步更新。
注意,如果上游倉庫需要認證(比如不是開源的),你需要提供使用者:

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

這時在克隆和pull正式倉庫時,需要提供使用者的密碼。

開發者開發自己的功能

在剛克隆的本地倉庫中,開發者可以像其它工作流一樣的編輯程式碼、提交修改新建分支

git checkout -b some-feature
# Edit some code
git commit -a -m "Add first draft of some feature"

所有的修改都是私有的直到push到自己公開倉庫中。如果正式專案已經往前走了,可以用git pull命令獲得新的提交:

git pull upstream master

由於開發者應該都在專門的功能分支上工作,pull操作結果會都是快進合併

開發者釋出自己的功能

一旦開發者準備好了分享新功能,需要做二件事。
首先,通過push他的貢獻程式碼到自己的公開倉庫中,讓其它的開發者都可以訪問到。
他的origin遠端別名應該已經有了,所以要做的就是:

git push origin feature-branch

這裡和之前的工作流的差異是,origin遠端別名指向開發者自己的服務端倉庫,而不是正式倉庫。

第二件事,開發者要通知專案維護者,想要合併他的新功能到正式庫中。
BitbucketStash提供了Pull Request按鈕,彈出表單讓你指定哪個分支要合併到正式倉庫。
一般你會想整合你的功能分支到上游遠端倉庫的master分支中。

專案維護者整合開發者的功能

當專案維護者收到pull request,他要做的是決定是否整合它到正式程式碼庫中。有二種方式來做:

  1. 直接在pull request中檢視程式碼
  2. pull程式碼到他自己的本地倉庫,再手動合併

第一種做法更簡單,維護者可以在GUI中檢視變更的差異,做評註和執行合併。
但如果出現了合併衝突,需要第二種做法來解決。這種情況下,維護者需要從開發者的服務端倉庫中fetch功能分支,
合併到他本地的master分支,解決衝突:

git fetch https://bitbucket.org/user/repo feature-branch
# 檢視變更
git checkout master
git merge FETCH_HEAD

變更整合到本地的master分支後,維護者要push變更到伺服器上的正式倉庫,這樣其它的開發者都能訪問到:

git push origin master

注意,維護者的origin是指向他自己公開倉庫的,即是專案的正式程式碼庫。到此,開發者的貢獻完全整合到了專案中。

開發者和正式倉庫做同步

由於正式程式碼庫往前走了,其它的開發需要和正式倉庫做同步:

git pull upstream master

如果你之前是使用SVNForking工作流可能看起來像是一個激進的正規化切換(paradigm shift)。
但不要害怕,這個工作流實際上就是在功能分支工作流之上引入另一個抽象層。
不是直接通過單箇中央倉庫來分享分支,而是把貢獻程式碼釋出到開發者自己的服務端倉庫中。

示例中解釋了,一個貢獻如何從一個開發者流到正式的master分支中,但同樣的方法可以把貢獻整合到任一個倉庫中。
比如,如果團隊的幾個人協作實現一個功能,可以在開發之間用相同的方法分享變更,完全不涉及正式倉庫。

這使得Forking工作流對於鬆散組織的團隊來說是個非常強大的工具。任一開發者可以方便地和另一開發者分享變更,任何分支都能有效地合併到正式程式碼庫中。

2.5 Pull Requests

Pull requestsBitbucket提供的讓開發者更方便地進行協作的功能,提供了友好的Web介面可以在提議的修改合併到正式專案之前對修改進行討論。

開發者向團隊成員通知功能開發已經完成,Pull Requests是最簡單的用法。
開發者完成功能開發後,通過Bitbucket賬號發起一個Pull Request
這樣讓涉及這個功能的所有人知道要去做Code Review和合併到master分支。

但是,Pull Request遠不止一個簡單的通知,而是為討論提交的功能的一個專門論壇。
如果變更有任何問題,團隊成員反饋在Pull Request中,甚至push新的提交微調功能。
所有的這些活動都直接跟蹤在Pull Request中。

相比其它的協作模型,這種分享提交的形式有助於打造一個更流暢的工作流。
SVNGit都能通過一個簡單的指令碼收到通知郵件;但是,討論變更時,開發者通常只能去回覆郵件。
這樣做會變得雜亂,尤其還要涉及後面的幾個提交時。
Pull Requests把所有相關功能整合到一個和Bitbucket倉庫介面整合的使用者友好Web介面中。

2.5.1 解析Pull Request

當要發起一個Pull Request,你所要做的就是請求(Request)另一個開發者(比如專案的維護者)
pull你倉庫中一個分支到他的倉庫中。這意味著你要提供4個資訊以發起Pull Request
源倉庫、源分支、目的倉庫、目的分支。

這幾值多數Bitbucket都會設定上合適的預設值。但取決你用的協作工作流,你的團隊可能會要指定不同的值。
上圖顯示了一個Pull Request請求合併一個功能分支到正式的master分支上,但可以有多種不同的Pull Request用法。

2.5.2 工作方式

Pull Request可以和功能分支工作流Gitflow工作流Forking工作流一起使用。
但一個Pull Request要求要麼分支不同要麼倉庫不同,所以不能用於集中式工作流
在不同的工作流中使用Pull Request會有一些不同,但基本的過程是這樣的:

  1. 開發者在本地倉庫中新建一個專門的分支開發功能。
  2. 開發者push分支修改到公開的Bitbucket倉庫中。
  3. 開發者通過Bitbucket發起一個Pull Request
  4. 團隊的其它成員review code,討論並修改。
  5. 專案維護者合併功能到官方倉庫中並關閉Pull Request

本文後面內容說明,Pull Request在不同協作工作流中如何應用。

2.5.3 在功能分支工作流中使用Pull Request

功能分支工作流用一個共享的Bitbucket倉庫來管理協作,開發者在專門的分支上開發功能。
但不是立即合併到master分支上,而是在合併到主程式碼庫之前開發者應該開一個Pull Request發起功能的討論。

功能分支工作流只有一個公開的倉庫,所以Pull Request的目的倉庫和源倉庫總是同一個。
通常開發者會指定他的功能分支作為源分支,master分支作為目的分支。

收到Pull Request後,專案維護者要決定如何做。如果功能沒問題,就簡單地合併到master分支,關閉Pull Request
但如果提交的變更有問題,他可以在Pull Request中反饋。之後新加的提交也會評論之後接著顯示出來。

在功能還沒有完全開發完的時候,也可能發起一個Pull Request
比如開發者在實現某個需求時碰到了麻煩,他可以發一個包含正在進行中工作的Pull Request
其它的開發者可以在Pull Request提供建議,或者甚至直接新增提交來解決問題。

2.5.4 在Gitflow工作流中使用Pull Request

Gitflow工作流和功能分支工作流類似,但圍繞專案釋出定義一個嚴格的分支模型。
Gitflow工作流中使用Pull Request讓開發者在釋出分支或是維護分支上工作時,
可以有個方便的地方對關於釋出分支或是維護分支的問題進行交流。

Gitflow工作流中Pull Request的使用過程和上一節中完全一致:
當一個功能、釋出或是熱修復分支需要Review時,開發者簡單發起一個Pull Request
團隊的其它成員會通過Bitbucket收到通知。

新功能一般合併到develop分支,而釋出和熱修復則要同時合併到develop分支和master分支上。
Pull Request可能用做所有合併的正式管理。

2.5.5 在Forking工作流中使用Pull Request

Forking工作流中,開發者push完成的功能到他自己的倉庫中,而不是共享倉庫。
然後,他發起一個Pull Request,讓專案維護者知道他的功能已經可以Review了。

在這個工作流,Pull Request的通知功能非常有用,
因為專案維護者不可能知道其它開發者在他們自己的倉庫新增了提交。

由於各個開發有自己的公開倉庫,Pull Request的源倉庫和目標倉庫不是同一個。
源倉庫是開發者的公開倉庫,源分支是包含了修改的分支。
如果開發者要合併修改到正式程式碼庫中,那麼目標倉庫是正式倉庫,目標分支是master分支。

Pull Request也可以用於正式專案之外的其它開發者之間的協作。
比如,如果一個開發者和一個團隊成員一起開發一個功能,他們可以發起一個Pull Request
用團隊成員的Bitbucket倉庫作為目標,而不是正式專案的倉庫。
然後使用相同的功能分支作為源和目標分支。

2個開發者之間可以在Pull Request中討論和開發功能。
完成開發後,他們可以發起另一個Pull Request,請求合併功能到正式的master分支。
Forking工作流中,這樣的靈活性讓Pull Request成為一個強有力的協作工具。

2.5.6 示例

下面的示例演示了Pull Request如何在在Forking工作流中使用。
也同樣適用於小團隊的開發協作和第三方開發者向開源專案的貢獻。

在示例中,小紅是個開發,小明是專案維護者。他們各自有一個公開的Bitbucket倉庫,而小明的倉庫包含了正式工程。

小紅fork正式專案

小紅先要fork小明的Bitbucket倉庫,開始專案的開發。她登陸Bitbucket,瀏覽到小明的倉庫頁面,
Fork按鈕。

然後為fork出來的倉庫填寫名字和描述,這樣小紅就有了服務端的專案拷貝了。

小紅克隆她的Bitbucket倉庫

下一步,小紅克隆自己剛才fork出來的Bitbucket倉庫,以在本機上準備出工作拷貝。命令如下:

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

請記住,git clone會自動建立origin遠端別名,是指向小紅fork出來的倉庫。

小紅開發新功能

在開始改程式碼前,小紅要為新功能先新建一個新分支。她會用這個分支作為Pull Request的源分支。

git checkout -b some-feature
# 編輯程式碼
git commit -a -m "Add first draft of some feature"

在新功能分支上,小紅按需要新增提交。甚至如果小紅覺得功能分支上的提交歷史太亂了,她可以用互動式rebase來刪除或壓制提交。
對於大型專案,整理功能分支的歷史可以讓專案維護者更容易看出在Pull Request中做了什麼內容。

小紅push功能到她的Bitbucket倉庫中

小紅完成了功能後,push功能到她自己的Bitbucket倉庫中(不是正式倉庫),用下面簡單的命令:

git push origin some-branch

這時她的變更可以讓專案維護者看到了(或者任何想要看的協作者)。

小紅髮起Pull Request

Bitbucket上有了她的功能分支後,小紅可以用她的Bitbucket賬號瀏覽到她的fork出來的倉庫頁面,
點右上角的【Pull Request】按鈕,發起一個Pull Request
彈出的表單自動設定小紅的倉庫為源倉庫,詢問小紅以指定源分支、目標倉庫和目標分支。

小紅想要合併功能到正式倉庫,所以源分支是她的功能分支,目標倉庫是小明的公開倉庫,
而目標分支是master分支。另外,小紅需要提供Pull Request的標題和描述資訊。
如果需要小明以外的人稽核批准程式碼,她可以把這些人填在【Reviewers】文字框中。

建立好了Pull Request,通知會通過Bitbucket系統訊息或郵件(可選)發給小明。

小明review Pull Request

在小明的Bitbucket倉庫頁面的【Pull Request】Tab可以看到所有人發起的Pull Request
點選小紅的Pull Request會顯示出Pull Request的描述、功能的提交歷史和每個變更的差異(diff)。

如果小明想要合併到專案中,只要點一下【Merge】按鈕,就可以同意Pull Request併合併到master分支。

但如果像這個示例中一樣小明發現了在小紅的程式碼中的一個小Bug,要小紅在合併前修復。
小明可以在整個Pull Request上加上評註,或是選擇歷史中的某個提交加上評註。

小紅補加提交

如果小紅對反饋有任何疑問,可以在Pull Request中響應,把Pull Request當作是她功能討論的論壇。

小紅在她的功能分支新加提交以解決程式碼問題,並push到她的Bitbucket倉庫中,就像前一輪中的做法一樣。
這些提交會進入的Pull Request,小明在原來的評註旁邊可以再次review變更。

小明接受Pull Request

最終,小明接受變更,合併功能分支到master分支,並關閉Pull Request
至此,功能整合到專案中,其它的專案開發者可以用標準的git pull命令pull這些變更到自己的本地倉庫中。

到了這裡,你應該有了所有需要的工具來整合Pull Request到你自己的工作流。
請記住,Pull Request並不是為了替代任何 基於Git的協作工作流
而是它們的一個便利的補充,讓團隊成員間的協作更輕鬆方便。

文章第一時間更新在github上,歡迎大家閱讀檢視 https://github.com/xirong/my-git/blob/master/git-workflow-tutorial.md

相關文章