天天看點

深入了解學習Git工作流

我們以使用svn的工作流來使用git有什麼不妥?

git 友善的branch在哪裡,團隊多人如何協作?沖突了怎麼辦?如何進行釋出控制?

經典的master-釋出、develop-主開發、hotfix-不過修複如何避免代碼不經過驗證上線?

如何在github上面與他人一起協作,star-fork-pull request是怎樣的流程?

一、譯序

二、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

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

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

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

ps:

文中<code>pull request</code>的介紹用的是<code>bitbucket</code>代碼托管服務,由于和<code>github</code>基本一樣,如果你用的是<code>github</code>(我自己也主要使用<code>github</code>托管代碼),不影響了解和操作。

pps:

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

<code>gitflow</code>工作流是經典模型,展現了工作流的經驗和精髓。随着項目過程複雜化,會感受到這個工作流中深思熟慮和威力!

二、<code>git</code>工作流指南

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

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

深入了解學習Git工作流

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

深入了解學習Git工作流

轉到分布式版本控制系統看起來像個令人生畏的任務,但不改變已用的工作流你也可以用上<code>git</code>帶來的收益。團隊可以用和<code>subversion</code>完全不變的方式來開發項目。

但使用<code>git</code>加強開發的工作流,<code>git</code>有相比<code>svn</code>的幾個優勢。

首先,每個開發可以有屬于自己的整個工程的本地拷貝。隔離的環境讓各個開發者的工作和項目的其他部分修改獨立開來 ——

即自由地送出到自己的本地倉庫,先完全忽略上遊的開發,直到友善的時候再把修改回報上去。

其次,<code>git</code>提供了強壯的分支和合并模型。不像<code>svn</code>,<code>git</code>的分支設計成可以做為一種用來在倉庫之間內建代碼和分享修改的『失敗安全』的機制。

像<code>subversion</code>一樣,集中式工作流以中央倉庫作為項目所有修改的單點實體。相比<code>svn</code>預設的開發分支<code>trunk</code>,<code>git</code>叫做<code>master</code>,所有修改送出到這個分支上。本工作流隻用到<code>master</code>這一個分支。

開發者開始先克隆中央倉庫。在自己的項目拷貝中像<code>svn</code>一樣的編輯檔案和送出修改;但修改是存在本地的,和中央倉庫是完全隔離的。開發者可以把和上遊的同步延後到一個友善時間點。

要釋出修改到正式項目中,開發者要把本地<code>master</code>分支的修改『推』到中央倉庫中。這相當于<code>svn commit</code>操作,但<code>push</code>操作會把所有還不在中央倉庫的本地送出都推上去。

深入了解學習Git工作流

中央倉庫代表了正式項目,是以送出曆史應該被尊重且是穩定不變的。如果開發者本地的送出曆史和中央倉庫有分歧,<code>git</code>會拒絕<code>push</code>送出否則會覆寫已經在中央庫的正式送出。

深入了解學習Git工作流

在開發者送出自己功能修改到中央庫前,需要先<code>fetch</code>在中央庫的新增送出,<code>rebase</code>自己送出到中央庫送出曆史之上。

這樣做的意思是在說,『我要把自己的修改加到别人已經完成的修改上。』最終的結果是一個完美的線性曆史,就像以前的<code>svn</code>的工作流中一樣。

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

深入了解學習Git工作流

第一步,有人在伺服器上建立好中央倉庫。如果是新項目,你可以初始化一個空倉庫;否則你要導入已有的<code>git</code>或<code>svn</code>倉庫。

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

ssh user@host

git init --bare /path/to/repo.git

確定寫上有效的<code>user</code>(<code>ssh</code>的使用者名),<code>host</code>(伺服器的域名或ip位址),<code>/path/to/repo.git</code>(你想存放倉庫的位置)。

注意,為了表示是一個裸倉庫,按照約定加上<code>.git</code>擴充名到倉庫名上。

深入了解學習Git工作流

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

基于你後續會持續和克隆的倉庫做互動的假設,克隆倉庫時<code>git</code>會自動添加遠端别名<code>origin</code>指回『父』倉庫。

深入了解學習Git工作流

在小明的本地倉庫中,他使用标準的<code>git</code>過程開發功能:編輯、暫存(<code>stage</code>)和送出。

如果你不熟悉暫存區(<code>staging area</code>),這裡說明一下:暫存區的用來準備一個送出,但可以不用把工作目錄中所有的修改内容都包含進來。

這樣你可以建立一個高度聚焦的送出,盡管你本地修改很多内容。

git status # 檢視本地倉庫的修改狀态

git add # 暫存檔案

git commit # 送出檔案

請記住,因為這些指令生成的是本地送出,小明可以按自己需求反複操作多次,而不用擔心中央倉庫上有了什麼操作。

對需要多個更簡單更原子分塊的大功能,這個做法是很有用的。

深入了解學習Git工作流

與此同時,小紅在自己的本地倉庫中用相同的編輯、暫存和送出過程開發功能。和小明一樣,她也不關心中央倉庫有沒有新送出;

當然更不關心小明在他的本地倉庫中的操作,因為所有本地倉庫都是私有的。

深入了解學習Git工作流

git push origin master

注意,<code>origin</code>是在小明克隆倉庫時<code>git</code>建立的遠端中央倉庫别名。<code>master</code>參數告訴<code>git</code>推送的分支。

由于中央倉庫自從小明克隆以來還沒有被更新過,是以<code>push</code>操作不會有沖突,成功完成。

深入了解學習Git工作流

一起來看看在小明釋出修改後,小紅<code>push</code>修改會怎麼樣?她使用完全一樣的<code>push</code>指令:

但她的本地曆史已經和中央倉庫有分岐了,<code>git</code>拒絕操作并給出下面很長的出錯消息:

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.

這避免了小紅覆寫正式的送出。她要先<code>pull</code>小明的更新到她的本地倉庫合并上她的本地修改後,再重試。

小紅在小明的送出之上<code>rebase</code>

深入了解學習Git工作流

這條指令類似<code>svn update</code>——拉取所有上遊送出指令到小紅的本地倉庫,并嘗試和她的本地修改合并:

git pull --rebase origin master

<code>--rebase</code>選項告訴<code>git</code>把小紅的送出移到同步了中央倉庫修改後的<code>master</code>分支的頂部,如下圖所示:

深入了解學習Git工作流

如果你忘加了這個選項,<code>pull</code>操作仍然可以完成,但每次<code>pull</code>操作要同步中央倉庫中别人修改時,送出曆史會以一個多餘的『合并送出』結尾。

對于集中式工作流,最好是使用<code>rebase</code>而不是生成一個合并送出。

深入了解學習Git工作流

<code>rebase</code>操作過程是把本地送出一次一個地遷移到更新了的中央倉庫<code>master</code>分支之上。

這意味着可能要解決在遷移某個送出時出現的合并沖突,而不是解決包含了所有送出的大型合并時所出現的沖突。

這樣的方式讓你盡可能保持每個送出的聚焦和項目曆史的整潔。反過來,簡化了哪裡引入<code>bug</code>的分析,如果有必要,復原修改也可以做到對項目影響最小。

如果小紅和小明的功能是相關的,不大可能在<code>rebase</code>過程中有沖突。如果有,<code>git</code>在合并有沖突的送出處暫停<code>rebase</code>過程,輸出下面的資訊并帶上相關的指令:

conflict (content): merge conflict in &lt;some-file&gt;

深入了解學習Git工作流

沖突檔案列在<code>unmerged paths</code>(未合并路徑)一節中:

# unmerged paths:

# (use "git reset head &lt;some-file&gt;..." to unstage)

# (use "git add/rm &lt;some-file&gt;..." as appropriate to mark resolution)

#

# both modified: &lt;some-file&gt;

git add &lt;some-file&gt;

git rebase --continue

要做的就這些了。<code>git</code>會繼續一個一個地合并後面的送出,如其它的送出有沖突就重複這個過程。

git rebase --abort

深入了解學習Git工作流

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

如你所見,僅使用幾個<code>git</code>指令我們就可以模拟出傳統<code>subversion</code>開發環境。對于要從<code>svn</code>遷移過來的團隊來說這太好了,但沒有發揮出<code>git</code>分布式本質的優勢。

如果你的團隊适應了集中式工作流,但想要更流暢的協作效果,絕對值得探索一下 <code>功能分支工作流</code> 的收益。

通過為一個功能配置設定一個專門的分支,能夠做到一個新增功能內建到正式項目之前對新功能進行深入讨論。

功能分支工作流以集中式工作流為基礎,不同的是為各個新功能配置設定一個專門的分支來開發。這樣可以在把新功能內建到正式項目前,用<code>pull requests</code>的方式讨論變更。

深入了解學習Git工作流
深入了解學習Git工作流

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

功能分支工作流背後的核心思路是所有的功能開發應該在一個專門的分支,而不是在<code>master</code>分支上。

這個隔離可以友善多個開發者在各自的功能上開發而不會弄亂主幹代碼。

另外,也保證了<code>master</code>分支的代碼一定不會是有問題的,極大有利于內建環境。

<code>pull requests</code>工作流能為每個分支發起一個讨論,在分支合入正式項目之前,給其它開發者有表示贊同的機會。

另外,如果你在功能開發中有問題卡住了,可以開一個<code>pull requests</code>來向同學們征求建議。

這些做法的重點就是,<code>pull requests</code>讓團隊成員之間互相評論工作變成非常友善!

功能分支工作流仍然用中央倉庫,并且<code>master</code>分支還是代表了正式項目的曆史。

但不是直接送出本地曆史到各自的本地<code>master</code>分支,開發者每次在開始新功能前先建立一個新分支。

功能分支應該有個有描述性的名字,比如<code>animated-menu-items</code>或<code>issue-#1061</code>,這樣可以讓分支有個清楚且高聚焦的用途。

在<code>master</code>分支和功能分支之間,<code>git</code>是沒有技術上的差別,是以開發者可以用和集中式工作流中完全一樣的方式編輯、暫存和送出修改到功能分支上。

另外,功能分支也可以(且應該)<code>push</code>到中央倉庫中。這樣不修改正式代碼就可以和其它開發者分享送出的功能。

由于<code>master</code>僅有的一個『特殊』分支,在中央倉庫上存多個功能分支不會有任何問題。當然,這樣做也可以很友善地備份各自的本地送出。

2.2.2 <code>pull requests</code>

一旦某個開發完成一個功能,不是立即合并到<code>master</code>,而是<code>push</code>到中央倉庫的功能分支上并發起一個<code>pull request</code>請求去合并修改到<code>master</code>。

在修改成為主幹代碼前,這讓其它的開發者有機會先去<code>review</code>變更。

<code>code review</code>是<code>pull requests</code>的一個重要的收益,但<code>pull requests</code>目的是讨論代碼一個通用方式。

你可以把<code>pull requests</code>作為專門給某個分支的讨論。這意味着可以在更早的開發過程中就可以進行<code>code review</code>。

比如,一個開發者開發功能需要幫助時,要做的就是發起一個<code>pull request</code>,相關的人就會自動收到通知,在相關的送出旁邊能看到需要幫助解決的問題。

一旦<code>pull request</code>被接受了,釋出功能要做的就和集中式工作流就很像了。

首先,确定本地的<code>master</code>分支和上遊的<code>master</code>分支是同步的。然後合并功能分支到本地<code>master</code>分支并<code>push</code>已經更新的本地<code>master</code>分支到中央倉庫。

下面的示例示範了如何把<code>pull requests</code>作為<code>code review</code>的方式,但注意<code>pull requests</code>可以用于很多其它的目的。

深入了解學習Git工作流

git checkout -b marys-feature master

這個指令檢出一個基于<code>master</code>名為<code>marys-feature</code>的分支,<code>git</code>的<code>-b</code>選項表示如果分支還不存在則建立分支。

這個新分支上,小紅按老套路編輯、暫存和送出修改,按需要送出以實作功能:

git status

git commit

深入了解學習Git工作流

早上小紅為新功能添加一些送出。

去吃午飯前,<code>push</code>功能分支到中央倉庫是很好的做法,這樣可以友善地備份,如果和其它開發協作,也讓他們可以看到小紅的送出。

git push -u origin marys-feature

這條指令<code>push</code> <code>marys-feature</code>分支到中央倉庫(<code>origin</code>),<code>-u</code>選項設定本地分支去跟蹤遠端對應的分支。

設定好跟蹤的分支後,小紅就可以使用<code>git push</code>指令省去指定推送分支的參數。

深入了解學習Git工作流

她發起一個<code>pull request</code>讓團隊的其它人知道功能已經完成。但首先,她要确認中央倉庫中已經有她最近的送出:

git push

然後,在她的<code>git</code> <code>gui</code>用戶端中發起<code>pull request</code>,請求合并<code>marys-feature</code>到<code>master</code>,團隊成員會自動收到通知。

<code>pull request</code>很酷的是可以在相關的送出旁邊顯示評注,是以你可以很對某個變更集提問。

小黑收到<code>pull request</code>

深入了解學習Git工作流

小黑收到了<code>pull request</code>後會檢視<code>marys-feature</code>的修改。決定在合并到正式項目前是否要做些修改,且通過<code>pull request</code>和小紅來回地讨論。

深入了解學習Git工作流

要再做修改,小紅用和功能第一個疊代完全一樣的過程。編輯、暫存、送出并<code>push</code>更新到中央倉庫。小紅這些活動都會顯示在<code>pull request</code>上,小黑可以斷續做評注。

如果小黑有需要,也可以把<code>marys-feature</code>分支拉到本地,自己來修改,他加的送出也會一樣顯示在<code>pull request</code>上。

深入了解學習Git工作流

一旦小黑可以的接受<code>pull request</code>,就可以合并功能到穩定項目代碼中(可以由小黑或是小紅來做這個操作):

git checkout master

git pull

git pull origin marys-feature

無論誰來做合并,首先要檢出<code>master</code>分支并确認是它是最新的。然後執行<code>git pull origin marys-feature</code>合并<code>marys-feature</code>分支到和已經和遠端一緻的本地<code>master</code>分支。

你可以使用簡單<code>git merge marys-feature</code>指令,但前面的指令可以保證總是最新的新功能分支。

最後更新的<code>master</code>分支要重新<code>push</code>回到<code>origin</code>。

這個過程常常會生成一個合并送出。有些開發者喜歡有合并送出,因為它像一個新功能和原來代碼基線的連通符。

但如果你偏愛線性的送出曆史,可以在執行合并時<code>rebase</code>新功能到<code>master</code>分支的頂部,這樣生成一個快進(<code>fast-forward</code>)的合并。

一些<code>gui</code>用戶端可以隻要點一下『接受』按鈕執行好上面的指令來自動化<code>pull request</code>接受過程。

如果你的不能這樣,至少在功能合并到<code>master</code>分支後能自動關閉<code>pull request</code>。

當小紅和小黑在<code>marys-feature</code>上工作并讨論她的<code>pull request</code>的時候,小明在自己的功能分支上做完全一樣的事。

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

到了這裡,但願你發現了功能分支可以很直接地在 <code>集中式工作流</code> 的僅有的<code>master</code>分支上完成多功能的開發。

另外,功能分支還使用了<code>pull request</code>,使得可以在你的版本控制<code>gui</code>用戶端中讨論某個送出。

功能分支工作流是開發項目異常靈活的方式。問題是,有時候太靈活了。對于大型團隊,常常需要給不同分支配置設定一個更具體的角色。

<code>gitflow</code>工作流是管理功能開發、釋出準備和維護的常用模式。

2.3 <code>gitflow</code>工作流

<code>gitflow</code>工作流通過為功能開發、釋出準備和維護配置設定獨立的分支,讓釋出疊代過程更流暢。嚴格的分支模型也為大型項目提供了一些非常必要的結構。

深入了解學習Git工作流

<code>gitflow</code>工作流沒有用超出功能分支工作流的概念和指令,而是為不同的分支配置設定一個很明确的角色,并定義分支之間如何和什麼時候進行互動。

除了使用功能分支,在做準備、維護和記錄釋出也使用各自的分支。

當然你可以用上功能分支工作流所有的好處:<code>pull requests</code>、隔離實驗性開發和更高效的協作。

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

相對使用僅有的一個<code>master</code>分支,<code>gitflow</code>工作流使用2個分支來記錄項目的曆史。<code>master</code>分支存儲了正式釋出的曆史,而<code>develop</code>分支作為功能的內建分支。

這樣也友善<code>master</code>分支上的所有送出配置設定一個版本号。

深入了解學習Git工作流

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

新功能送出應該從不直接與<code>master</code>分支互動。

深入了解學習Git工作流

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

深入了解學習Git工作流

一旦<code>develop</code>分支上有了做一次釋出(或者說快到了既定的釋出日)的足夠功能,就從<code>develop</code>分支上<code>fork</code>一個釋出分支。

建立的分支用于開始釋出循環,是以從這個時間點開始之後新的功能不能再加到這個分支上——

這個分支隻應該做<code>bug</code>修複、文檔生成和其它面向釋出任務。

一旦對外釋出的工作都完成了,釋出分支合并到<code>master</code>分支并配置設定一個版本号打好<code>tag</code>。

另外,這些從建立釋出分支以來的做的修改要合并回<code>develop</code>分支。

使用一個用于釋出準備的專門分支,使得一個團隊可以在完善目前的釋出版本的同時,另一個團隊可以繼續開發下個版本的功能。

這也打造定義良好的開發階段(比如,可以很輕松地說,『這周我們要做準備釋出版本4.0』,并且在倉庫的目錄結構中可以實際看到)。

常用的分支約定:

用于建立釋出分支的分支: develop

用于合并的分支: master

分支命名: release-* 或 release/*

深入了解學習Git工作流

維護分支或說是熱修複(<code>hotfix</code>)分支用于生成快速給産品釋出版本(<code>production releases</code>)打更新檔,這是唯一可以直接從<code>master</code>分支<code>fork</code>出來的分支。

修複完成,修改應該馬上合并回<code>master</code>分支和<code>develop</code>分支(目前的釋出分支),<code>master</code>分支應該用新的版本号打好<code>tag</code>。

為<code>bug</code>修複使用專門分支,讓團隊可以處理掉問題而不用打斷其它工作或是等待下一個釋出循環。

你可以把維護分支想成是一個直接在<code>master</code>分支上處理的臨時釋出。

下面的示例示範本工作流如何用于管理單個釋出循環。假設你已經建立了一個中央倉庫。

深入了解學習Git工作流

git branch develop

git push -u origin develop

git checkout -b develop origin/develop

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

深入了解學習Git工作流

git checkout -b some-feature develop

他們用老套路添加送出到各自功能分支上:編輯、暫存、送出:

深入了解學習Git工作流

添加了送出後,小紅覺得她的功能ok了。如果團隊使用<code>pull requests</code>,這時候可以發起一個用于合并到<code>develop</code>分支。

否則她可以直接合并到她本地的<code>develop</code>分支後<code>push</code>到中央倉庫:

git pull origin develop

git checkout develop

git merge some-feature

git branch -d some-feature

第一條指令在合并功能前確定<code>develop</code>分支是最新的。注意,功能決不應該直接合并到<code>master</code>分支。

深入了解學習Git工作流

這個時候小明正在實作他的功能,小紅開始準備她的第一個項目正式釋出。

像功能開發一樣,她用一個新的分支來做釋出準備。這一步也确定了釋出的版本号:

git checkout -b release-0.1 develop

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

隻要小紅建立這個分支并<code>push</code>到中央倉庫,這個釋出就是功能當機的。任何不在<code>develop</code>分支中的新功能都推到下個釋出循環中。

深入了解學習Git工作流

一旦準備好了對外釋出,小紅合并修改到<code>master</code>分支和<code>develop</code>分支上,删除釋出分支。合并回<code>develop</code>分支很重要,因為在釋出分支中已經送出的更新需要在後面的新功能中也要是可用的。

另外,如果小紅的團隊要求<code>code review</code>,這是一個發起<code>pull request</code>的理想時機。

git merge release-0.1

git branch -d release-0.1

釋出分支是作為功能開發(<code>develop</code>分支)和對外釋出(<code>master</code>分支)間的緩沖。隻要有合并到<code>master</code>分支,就應該打好<code>tag</code>以友善跟蹤。

git tag -a 0.1 -m "initial public release" master

git push --tags

<code>git</code>有提供各種勾子(<code>hook</code>),即倉庫有事件發生時觸發執行的腳本。

可以配置一個勾子,在你<code>push</code>中央倉庫的<code>master</code>分支時,自動建構好對外釋出。

最終使用者發現<code>bug</code>

深入了解學習Git工作流

對外釋出後,小紅回去和小明一起做下個釋出的新功能開發,直到有最終使用者開了一個<code>ticket</code>抱怨目前版本的一個<code>bug</code>。

為了處理<code>bug</code>,小紅(或小明)從<code>master</code>分支上拉出了一個維護分支,送出修改以解決問題,然後直接合并回<code>master</code>分支:

git checkout -b issue-#001 master

# fix the bug

git merge issue-#001

git branch -d issue-#001

你應該也牢固的掌握了本地倉庫的潛能,<code>push</code>/<code>pull</code>模式和<code>git</code>健壯的分支和合并模型。

記住,這裡示範的工作流隻是可能用法的例子,而不是在實際工作中使用<code>git</code>不可違逆的條例。

是以不要畏懼按自己需要對工作流的用法做取舍。不變的目标就是讓<code>git</code>為你所用。

<code>forking</code>工作流是分布式工作流,充分利用了<code>git</code>在分支和克隆上的優勢。可以安全可靠地管理大團隊的開發者(<code>developer</code>),并能接受不信任貢獻者(<code>contributor</code>)的送出。

<code>forking</code>工作流和前面讨論的幾種工作流有根本的不同,這種工作流不是使用單個服務端倉庫作為『中央』代碼基線,而讓各個開發者都有一個服務端倉庫。這意味着各個代碼貢獻者有2個<code>git</code>倉庫而不是1個:一個本地私有的,另一個服務端公開的。

深入了解學習Git工作流

<code>forking</code>工作流的一個主要優勢是,貢獻的代碼可以被內建,而不需要所有人都能<code>push</code>代碼到僅有的中央倉庫中。

開發者<code>push</code>到自己的服務端倉庫,而隻有項目維護者才能<code>push</code>到正式倉庫。

這樣項目維護者可以接受任何開發者的送出,但無需給他正式代碼庫的寫權限。

效果就是一個分布式的工作流,能為大型、自發性的團隊(包括了不受信的第三方)提供靈活的方式來安全的協作。

也讓這個工作流成為開源項目的理想工作流。

和其它的<code>git</code>工作流一樣,<code>forking</code>工作流要先有一個公開的正式倉庫存儲在伺服器上。

但一個新的開發者想要在項目上工作時,不是直接從正式倉庫克隆,而是<code>fork</code>正式項目在伺服器上建立一個拷貝。

這個倉庫拷貝作為他個人公開倉庫 ——

其它開發者不允許<code>push</code>到這個倉庫,但可以<code>pull</code>到修改(後面我們很快就會看這點很重要)。

要送出本地修改時,<code>push</code>送出到自己公開倉庫中 —— 而不是正式倉庫中。

然後,給正式倉庫發起一個<code>pull request</code>,讓項目維護者知道有更新已經準備好可以內建了。

對于貢獻的代碼,<code>pull request</code>也可以很友善地作為一個讨論的地方。

為了內建功能到正式代碼庫,維護者<code>pull</code>貢獻者的變更到自己的本地倉庫中,檢查變更以確定不會讓項目出錯,

到此,貢獻的送出成為了項目的一部分,其它的開發者應該執行<code>pull</code>操作與正式倉庫同步自己本地倉庫。

在<code>forking</code>工作流中,『官方』倉庫的叫法隻是一個約定,了解這點很重要。

從技術上來看,各個開發者倉庫和正式倉庫在<code>git</code>看來沒有任何差別。

事實上,讓正式倉庫之是以正式的唯一原因是它是項目維護者的公開倉庫。

2.4.3 <code>forking</code>工作流的分支使用方式

所有的個人公開倉庫實際上隻是為了友善和其它的開發者共享分支。

唯一的差別是這些分支被共享了。在<code>forking</code>工作流中這些分支會被<code>pull</code>到另一個開發者的本地倉庫中,而在功能分支工作流和<code>gitflow</code>工作流中是直接被<code>push</code>到正式倉庫中。

深入了解學習Git工作流

和任何使用<code>git</code>項目一樣,第一步是建立在伺服器上一個正式倉庫,讓所有團隊成員都可以通路到。

通常這個倉庫也會作為項目維護者的公開倉庫。

是以項目維護者會運作像下面的指令來搭建正式倉庫:

<code>bitbucket</code>和<code>stash</code>提供了一個友善的<code>gui</code>用戶端以完成上面指令行做的事。

這個搭建中央倉庫的過程和前面提到的工作流完全一樣。

如果有現存的代碼庫,維護者也要<code>push</code>到這個倉庫中。

開發者<code>fork</code>正式倉庫

深入了解學習Git工作流

其它所有的開發需要<code>fork</code>正式倉庫。

拷貝倉庫到伺服器另一個位置 —— 是的,<code>fork</code>操作基本上就隻是一個服務端的克隆。

<code>bitbucket</code>和<code>stash</code>上可以點一下按鈕就讓開發者完成倉庫的<code>fork</code>操作。

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

開發者克隆自己<code>fork</code>出來的倉庫

深入了解學習Git工作流

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

在這個示例中,假定用<code>bitbucket</code>托管了倉庫。記住,如果這樣的話各個開發者需要有各自的<code>bitbucket</code>賬号,

使用下面指令克隆服務端自己的倉庫:

git clone https://[email protected]/user/repo.git

相比前面介紹的工作流隻用了一個<code>origin</code>遠端别名指向中央倉庫,<code>forking</code>工作流需要2個遠端别名 ——

一個指向正式倉庫,另一個指向開發者自己的服務端倉庫。别名的名字可以任意命名,常見的約定是使用<code>origin</code>作為遠端克隆的倉庫的别名

(這個别名會在運作<code>git clone</code>自動建立),<code>upstream</code>(上遊)作為正式倉庫的别名。

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

需要自己用上面的指令建立<code>upstream</code>别名。這樣可以簡單地保持本地倉庫和正式倉庫的同步更新。

注意,如果上遊倉庫需要認證(比如不是開源的),你需要提供使用者:

git remote add upstream https://[email protected]/maintainer/repo.git

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

深入了解學習Git工作流

git checkout -b some-feature

# edit some code

git commit -a -m "add first draft of some feature"

git pull upstream master

深入了解學習Git工作流

一旦開發者準備好了分享新功能,需要做二件事。

首先,通過<code>push</code>他的貢獻代碼到自己的公開倉庫中,讓其它的開發者都可以通路到。

他的<code>origin</code>遠端别名應該已經有了,是以要做的就是:

git push origin feature-branch

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

第二件事,開發者要通知項目維護者,想要合并他的新功能到正式庫中。

一般你會想內建你的功能分支到上遊遠端倉庫的<code>master</code>分支中。

深入了解學習Git工作流

當項目維護者收到<code>pull request</code>,他要做的是決定是否內建它到正式代碼庫中。有二種方式來做:

直接在<code>pull request</code>中檢視代碼

<code>pull</code>代碼到他自己的本地倉庫,再手動合并

第一種做法更簡單,維護者可以在<code>gui</code>中檢視變更的差異,做評注和執行合并。

合并到他本地的<code>master</code>分支,解決沖突:

git fetch https://bitbucket.org/user/repo feature-branch

# 檢視變更

git merge fetch_head

變更內建到本地的<code>master</code>分支後,維護者要<code>push</code>變更到伺服器上的正式倉庫,這樣其它的開發者都能通路到:

注意,維護者的<code>origin</code>是指向他自己公開倉庫的,即是項目的正式代碼庫。到此,開發者的貢獻完全內建到了項目中。

深入了解學習Git工作流

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

如果你之前是使用<code>svn</code>,<code>forking</code>工作流可能看起來像是一個激進的範式切換(paradigm shift)。

不是直接通過單個中央倉庫來分享分支,而是把貢獻代碼釋出到開發者自己的服務端倉庫中。

示例中解釋了,一個貢獻如何從一個開發者流到正式的<code>master</code>分支中,但同樣的方法可以把貢獻內建到任一個倉庫中。

比如,如果團隊的幾個人協作實作一個功能,可以在開發之間用相同的方法分享變更,完全不涉及正式倉庫。

這使得<code>forking</code>工作流對于松散組織的團隊來說是個非常強大的工具。任一開發者可以友善地和另一開發者分享變更,任何分支都能有效地合并到正式代碼庫中。

2.5 <code>pull requests</code>

<code>pull requests</code>是<code>bitbucket</code>提供的讓開發者更友善地進行協作的功能,提供了友好的<code>web</code>界面可以在提議的修改合并到正式項目之前對修改進行讨論。

深入了解學習Git工作流

開發者向團隊成員通知功能開發已經完成,<code>pull requests</code>是最簡單的用法。

開發者完成功能開發後,通過<code>bitbucket</code>賬号發起一個<code>pull request</code>。

這樣讓涉及這個功能的所有人知道要去做<code>code review</code>和合并到<code>master</code>分支。

但是,<code>pull request</code>遠不止一個簡單的通知,而是為讨論送出的功能的一個專門論壇。

如果變更有任何問題,團隊成員回報在<code>pull request</code>中,甚至<code>push</code>新的送出微調功能。

所有的這些活動都直接跟蹤在<code>pull request</code>中。

深入了解學習Git工作流

相比其它的協作模型,這種分享送出的形式有助于打造一個更流暢的工作流。

<code>svn</code>和<code>git</code>都能通過一個簡單的腳本收到通知郵件;但是,讨論變更時,開發者通常隻能去回複郵件。

這樣做會變得雜亂,尤其還要涉及後面的幾個送出時。

<code>pull requests</code>把所有相關功能整合到一個和<code>bitbucket</code>倉庫界面內建的使用者友好<code>web</code>界面中。

2.5.1 解析<code>pull request</code>

當要發起一個<code>pull request</code>,你所要做的就是請求(<code>request</code>)另一個開發者(比如項目的維護者)

來<code>pull</code>你倉庫中一個分支到他的倉庫中。這意味着你要提供4個資訊以發起<code>pull request</code>:

源倉庫、源分支、目的倉庫、目的分支。

深入了解學習Git工作流

這幾值多數<code>bitbucket</code>都會設定上合适的預設值。但取決你用的協作工作流,你的團隊可能會要指定不同的值。

上圖顯示了一個<code>pull request</code>請求合并一個功能分支到正式的<code>master</code>分支上,但可以有多種不同的<code>pull request</code>用法。

在不同的工作流中使用<code>pull request</code>會有一些不同,但基本的過程是這樣的:

開發者在本地倉庫中建立一個專門的分支開發功能。

開發者<code>push</code>分支修改到公開的<code>bitbucket</code>倉庫中。

開發者通過<code>bitbucket</code>發起一個<code>pull request</code>。

團隊的其它成員<code>review</code> <code>code</code>,讨論并修改。

項目維護者合并功能到官方倉庫中并關閉<code>pull request</code>。

本文後面内容說明,<code>pull request</code>在不同協作工作流中如何應用。

2.5.3 在功能分支工作流中使用<code>pull request</code>

功能分支工作流用一個共享的<code>bitbucket</code>倉庫來管理協作,開發者在專門的分支上開發功能。

但不是立即合并到<code>master</code>分支上,而是在合并到主代碼庫之前開發者應該開一個<code>pull request</code>發起功能的讨論。

深入了解學習Git工作流

功能分支工作流隻有一個公開的倉庫,是以<code>pull request</code>的目的倉庫和源倉庫總是同一個。

通常開發者會指定他的功能分支作為源分支,<code>master</code>分支作為目的分支。

收到<code>pull request</code>後,項目維護者要決定如何做。如果功能沒問題,就簡單地合并到<code>master</code>分支,關閉<code>pull request</code>。

但如果送出的變更有問題,他可以在<code>pull request</code>中回報。之後新加的送出也會評論之後接着顯示出來。

在功能還沒有完全開發完的時候,也可能發起一個<code>pull request</code>。

比如開發者在實作某個需求時碰到了麻煩,他可以發一個包含正在進行中工作的<code>pull request</code>。

其它的開發者可以在<code>pull request</code>提供建議,或者甚至直接添加送出來解決問題。

2.5.4 在<code>gitflow</code>工作流中使用<code>pull request</code>

<code>gitflow</code>工作流和功能分支工作流類似,但圍繞項目釋出定義一個嚴格的分支模型。

在<code>gitflow</code>工作流中使用<code>pull request</code>讓開發者在釋出分支或是維護分支上工作時,

可以有個友善的地方對關于釋出分支或是維護分支的問題進行交流。

深入了解學習Git工作流

<code>gitflow</code>工作流中<code>pull request</code>的使用過程和上一節中完全一緻:

當一個功能、釋出或是熱修複分支需要<code>review</code>時,開發者簡單發起一個<code>pull request</code>,

團隊的其它成員會通過<code>bitbucket</code>收到通知。

新功能一般合并到<code>develop</code>分支,而釋出和熱修複則要同時合并到<code>develop</code>分支和<code>master</code>分支上。

<code>pull request</code>可能用做所有合并的正式管理。

2.5.5 在<code>forking</code>工作流中使用<code>pull request</code>

在<code>forking</code>工作流中,開發者<code>push</code>完成的功能到他自己的倉庫中,而不是共享倉庫。

然後,他發起一個<code>pull request</code>,讓項目維護者知道他的功能已經可以<code>review</code>了。

在這個工作流,<code>pull request</code>的通知功能非常有用,

因為項目維護者不可能知道其它開發者在他們自己的倉庫添加了送出。

深入了解學習Git工作流

由于各個開發有自己的公開倉庫,<code>pull request</code>的源倉庫和目标倉庫不是同一個。

源倉庫是開發者的公開倉庫,源分支是包含了修改的分支。

如果開發者要合并修改到正式代碼庫中,那麼目标倉庫是正式倉庫,目标分支是<code>master</code>分支。

<code>pull request</code>也可以用于正式項目之外的其它開發者之間的協作。

比如,如果一個開發者和一個團隊成員一起開發一個功能,他們可以發起一個<code>pull request</code>,

用團隊成員的<code>bitbucket</code>倉庫作為目标,而不是正式項目的倉庫。

然後使用相同的功能分支作為源和目标分支。

深入了解學習Git工作流

2個開發者之間可以在<code>pull request</code>中讨論和開發功能。

完成開發後,他們可以發起另一個<code>pull request</code>,請求合并功能到正式的<code>master</code>分支。

在<code>forking</code>工作流中,這樣的靈活性讓<code>pull request</code>成為一個強有力的協作工具。

下面的示例示範了<code>pull request</code>如何在在<code>forking</code>工作流中使用。

也同樣适用于小團隊的開發協作和第三方開發者向開源項目的貢獻。

在示例中,小紅是個開發,小明是項目維護者。他們各自有一個公開的<code>bitbucket</code>倉庫,而小明的倉庫包含了正式工程。

小紅<code>fork</code>正式項目

深入了解學習Git工作流

小紅先要<code>fork</code>小明的<code>bitbucket</code>倉庫,開始項目的開發。她登陸<code>bitbucket</code>,浏覽到小明的倉庫頁面,

點<code>fork</code>按鈕。

深入了解學習Git工作流

然後為<code>fork</code>出來的倉庫填寫名字和描述,這樣小紅就有了服務端的項目拷貝了。

小紅克隆她的<code>bitbucket</code>倉庫

深入了解學習Git工作流

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

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

深入了解學習Git工作流

在開始改代碼前,小紅要為新功能先建立一個新分支。她會用這個分支作為<code>pull request</code>的源分支。

# 編輯代碼

對于大型項目,整理功能分支的曆史可以讓項目維護者更容易看出在<code>pull request</code>中做了什麼内容。

小紅<code>push</code>功能到她的<code>bitbucket</code>倉庫中

深入了解學習Git工作流

小紅完成了功能後,<code>push</code>功能到她自己的<code>bitbucket</code>倉庫中(不是正式倉庫),用下面簡單的指令:

git push origin some-branch

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

小紅發起<code>pull request</code>

深入了解學習Git工作流

<code>bitbucket</code>上有了她的功能分支後,小紅可以用她的<code>bitbucket</code>賬号浏覽到她的<code>fork</code>出來的倉庫頁面,

點右上角的【<code>pull request</code>】按鈕,發起一個<code>pull request</code>。

彈出的表單自動設定小紅的倉庫為源倉庫,詢問小紅以指定源分支、目标倉庫和目标分支。

小紅想要合并功能到正式倉庫,是以源分支是她的功能分支,目标倉庫是小明的公開倉庫,

而目标分支是<code>master</code>分支。另外,小紅需要提供<code>pull request</code>的标題和描述資訊。

如果需要小明以外的人稽核準許代碼,她可以把這些人填在【reviewers】文本框中。

深入了解學習Git工作流

建立好了<code>pull request</code>,通知會通過<code>bitbucket</code>系統消息或郵件(可選)發給小明。

小明review <code>pull request</code>

深入了解學習Git工作流

在小明的<code>bitbucket</code>倉庫頁面的【<code>pull request</code>】tab可以看到所有人發起的<code>pull request</code>。

點選小紅的<code>pull request</code>會顯示出<code>pull request</code>的描述、功能的送出曆史和每個變更的差異(<code>diff</code>)。

如果小明想要合并到項目中,隻要點一下【<code>merge</code>】按鈕,就可以同意<code>pull request</code>并合并到<code>master</code>分支。

但如果像這個示例中一樣小明發現了在小紅的代碼中的一個小<code>bug</code>,要小紅在合并前修複。

小明可以在整個<code>pull request</code>上加上評注,或是選擇曆史中的某個送出加上評注。

深入了解學習Git工作流

如果小紅對回報有任何疑問,可以在<code>pull request</code>中響應,把<code>pull request</code>當作是她功能讨論的論壇。

小紅在她的功能分支新加送出以解決代碼問題,并<code>push</code>到她的<code>bitbucket</code>倉庫中,就像前一輪中的做法一樣。

這些送出會進入的<code>pull request</code>,小明在原來的評注旁邊可以再次<code>review</code>變更。

小明接受<code>pull request</code>

最終,小明接受變更,合并功能分支到<code>master</code>分支,并關閉<code>pull request</code>。

至此,功能內建到項目中,其它的項目開發者可以用标準的<code>git pull</code>指令<code>pull</code>這些變更到自己的本地倉庫中。

到了這裡,你應該有了所有需要的工具來內建<code>pull request</code>到你自己的工作流。

請記住,<code>pull request</code>并不是為了替代任何 <code>基于</code>git<code>的協作工作流</code>,

而是它們的一個便利的補充,讓團隊成員間的協作更輕松友善。

來源:51cto