天天看點

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

在學習Git工作流的過程中,可能會有好多遺留在心中的困惑:

  1. 我們以使用SVN的工作流來使用Git有什麼不妥?
  2. Git友善的branch在哪裡,團隊多人如何協作?沖突了怎麼辦?
  3. 如何進行釋出控制? 經典的master-釋出、develop-主開發、hotfix-bug修複如何避免代碼不經過驗證上線?
  4. 如何在GitHub上面與他人一起協作,star-fork-pull request是怎樣的流程?

更多内容: C&O - 有面試上C&O (baidu搜尋)

一、譯序

這篇指南以大家在SVN中已經廣為熟悉使用的集中式工作流作為起點,循序漸進地演進到其它高效的分布式工作流,還介紹了如何配合使用便利的Pull Request功能,系統地講解了各種工作流的應用。 如果你Git用的還不多,可以從前面的講的工作流開始操練。在操作過程中去感受指南的講解:解決什麼問題、如何解決問題,這樣了解就深了,也友善活用。

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

工作流其實不是一個初級主題,背後的本質問題是 有效的項目流程管理 和 高效的開發協同約定,而不僅僅是Git或SVN等工具的使用。

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

Gitflow工作流是經典模型,處于核心位置,展現了工作流的經驗和精髓。随着項目過程複雜化,你會感受到這個工作流中的深思熟慮和威力!

Forking工作流是分布式協作的(GitHub風格)可以先看看GitHub的Help:Fork A Repo和Using pull requests 。照着操作,給一個GitHub項目貢獻你的送出,有操作經驗再看指南容易意會。指南中給了自己實作Fork的方法:Fork就是服務端的克隆。在指南的操練中使用代碼托管服務(如GitHub、Bitbucket),可以點一下按鈕就讓開發者完成倉庫的fork操作。

PS:

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

二、Git工作流指南 工作流有各式各樣的用法,但也正是以使得在實際工作中如何上手使用變得很頭大。這篇指南通過總覽公司團隊中最常用的幾種Git工作流讓大家可以上手使用。 在閱讀的過程中請記住,本文中的幾種工作流是作為方案指導而不是條例規定。在展示了各種工作流可能的用法後,你可以從不同的工作流中挑選或揉合出一個滿足你自己需求的工作流。

2.1 集中式工作流

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

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

但使用Git加強開發的工作流,相比SVN,Git有以下兩個優勢: 首先,每個開發者可以有屬于自己的整個工程的本地拷貝。隔離的環境讓各個開發者的工作和項目的其他部分修改獨立開來 —— 即自由地送出到自己的本地倉庫,先完全忽略上遊的開發,直到友善的時候再把修改回報上去。

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

2.1.1 工作方式

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

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

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

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

2.1.2 沖突解決

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

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

如果本地修改和上遊送出有沖突,Git會暫停rebase過程,給你手動解決沖突的機會。Git解決合并沖突,用和生成送出一樣的git status和git add指令,很一緻友善。還有一點,如果解決沖突時遇到麻煩,Git可以很簡單中止整個rebase操作,重來一次(或者讓别人來幫助解決)。

2.1.3 示例

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

有人先初始化好中央倉庫

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

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

ssh [email protected] git init --bare /path/to/repo.git
           

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

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

所有人克隆中央倉庫

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

git clone ssh://[email protected]/path/to/repo.git
           

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

小明開發功能

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

git status # 檢視本地倉庫的修改狀态 git add # 暫存檔案 git commit # 送出檔案
           

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

小紅開發功能

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

小明釋出功能

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

git push origin master
           

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

小紅試着釋出功能

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

一起來看看在小明釋出修改後,小紅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 fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

小紅用git pull合并上遊的修改到自己的倉庫中。 這條指令類似svn update——拉取所有上遊送出指令到小紅的本地倉庫,并嘗試和她的本地修改合并:

git pull --rebase origin master
           

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

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

如果你忘加了這個選項,pull操作仍然可以完成,但每次pull操作要同步中央倉庫中别人修改時,送出曆史會以一個多餘的『合并送出』結尾。 對于集中式工作流,最好是使用rebase而不是生成一個合并送出。

小紅解決合并沖突

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

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

CONFLICT (content): Merge conflict in 
           
git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

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

接着小紅編輯這些檔案。修改完成後,用老套路暫存這些檔案,并讓git rebase完成剩下的事:

git add git rebase --continue
           

要做的就這些了。Git會繼續一個一個地合并後面的送出,如其它的送出有沖突就重複這個過程。 如果你碰到了沖突,但發現搞不定,不要驚慌。隻要執行下面這條指令,就可以回到你執行git pull --rebase指令前的樣子:

git rebase --abort
           

小紅成功釋出功能

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

git push origin master
           

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

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

2.2 功能分支工作流

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

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

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

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

2.2.1 工作方式

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

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

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

2.2.2 Pull Requests

功能分支除了可以隔離功能的開發,也使得通過Pull Requests讨論變更成為可能。

一旦某個開發者完成一個功能,不是立即合并到master,而是push到中央倉庫的功能分支上并發起一個Pull Request請求,将修改合并到master。 在修改成為主幹代碼前,這讓其它的開發者有機會先去Review變更。

Code Review是Pull Requests的一個重要的收益,而Pull Requests則是讨論代碼的一個通用方式。 你可以把Pull Requests作為專門給某個分支的讨論。

這意味着可以在更早的開發過程中就可以進行Code Review。 比如,一個開發者開發功能需要幫助時,要做的就是發起一個Pull Request,相關的人就會自動收到通知,在相關的送出旁邊能看到需要幫助解決的問題。

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

2.2.3 示例

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

小紅開始開發一個新功能

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

git checkout -b marys-feature master
           

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

git status git add git commit 
           

小紅要去吃個午飯

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

git push -u origin marys-feature
           

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

小紅完成功能開發

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

git push
           

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

小黑收到Pull Request

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

小紅再做修改

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

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

小紅釋出她的功能

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

一旦小黑可以的接受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 fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

這節介紹的Gitflow工作流借鑒自在nvie的Vincent Driessen。

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

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

2.3.1 工作方式

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

2.3.2 曆史分支

相對于使用僅有的一個master分支,Gitflow工作流使用兩個分支來記錄項目的曆史。master分支存儲了正式釋出的曆史,而develop分支作為功能的內建分支。 這樣也友善master分支上的所有送出配置設定一個版本号。

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

2.3.3 功能分支

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

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

2.3.4 釋出分支

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

一旦develop分支上有了做一次釋出(或者說快到了既定的釋出日)的足夠功能,就從develop分支上checkout一個釋出分支。 建立的分支用于開始釋出循環,是以從這個時間點開始之後新的功能不能再加到這個分支上—— 這個分支隻應該做Bug修複、文檔生成和其它面向釋出任務。 一旦對外釋出的工作都完成了,釋出分支合并到master分支并配置設定一個版本号打好Tag。 另外,這些從建立釋出分支以來的做的修改要合并回develop分支。

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

常用的分支約定:

用于建立釋出分支的分支: develop 用于合并的分支: master 分支命名: release-* 或 release/*
           

2.3.5 維護分支

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

維護分支或說是熱修複(hotfix)分支用于給産品釋出版本(production releases)快速生成更新檔,這是唯一可以直接從master分支fork出來的分支。 修複完成,修改應該馬上合并回master分支和develop分支(目前的釋出分支),master分支應該用新的版本号打好Tag。

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

2.3.6 示例

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

建立開發分支

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

git branch develop git push -u origin develop
           

以後這個分支将會包含了項目的全部曆史,而master分支将隻包含了部分曆史。其它開發者這時應該克隆中央倉庫,建好develop分支的跟蹤分支:

git clone ssh://[email protected]/path/to/repo.git git checkout -b develop origin/develop
           

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

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

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

git checkout -b some-feature develop
           

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

git status git add git commit
           

小紅完成功能開發

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

添加了送出後,小紅覺得她的功能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 fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

git checkout -b release-0.1 develop
           

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

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

小紅完成釋出

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

一旦準備好了對外釋出,小紅合并修改到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

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

對外版本釋出後,小紅小明一起開發下一版本的新功能,直到有最終使用者開了一個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個:一個本地私有的,另一個服務端公開的。

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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 fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

公開倉庫應該是裸倉庫,不管是不是正式代碼庫。 是以項目維護者會運作像下面的指令來搭建正式倉庫:

ssh [email protected] git init --bare /path/to/repo.git
           

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

開發者fork正式倉庫

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

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

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

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

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

git clone https://[email protected]/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://[email protected]/maintainer/repo.git
           

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

開發者開發自己的功能

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

在剛克隆的本地倉庫中,開發者可以像其它工作流一樣的編輯代碼、送出修改和建立分支:

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操作結果會都是快進合并。

開發者釋出自己的功能

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

首先,通過push他的貢獻代碼到自己的公開倉庫中,讓其它的開發者都可以通路到。 他的origin遠端别名應該已經有了,是以要做的就是:

git push origin feature-branch
           

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

第二件事,開發者要通知項目維護者,想要合并他的新功能到正式庫中。 Bitbucket和Stash提供了Pull Request按鈕,彈出表單讓你指定哪個分支要合并到正式倉庫。 一般你會想內建你的功能分支到上遊遠端倉庫的master分支中。

項目維護者內建開發者的功能

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

當項目維護者收到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 fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

git pull upstream master
           

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

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

2.5 Pull Requests

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

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

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

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

相比其它的協作模型,這種分享送出的形式有助于打造一個更流暢的工作流。 SVN和Git都能通過一個簡單的腳本收到通知郵件;但是,讨論變更時,開發者通常隻能去回複郵件。 這樣做會變得雜亂,尤其還要涉及後面的幾個送出時。 Pull Requests把所有相關功能整合到一個和Bitbucket倉庫界面內建的使用者友好Web界面中。

2.5.1 解析Pull Request

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

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

2.5.2 工作方式

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

開發者在本地倉庫中建立一個專門的分支開發功能。 開發者push分支修改到公開的Bitbucket倉庫中。 開發者通過Bitbucket發起一個Pull Request。 團隊的其它成員review code,讨論并修改。 項目維護者合并功能到官方倉庫中并關閉Pull Request。
           

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

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

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

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

功能分支工作流隻有一個公開的倉庫,是以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讓開發者在釋出分支或是維護分支上工作時, 可以有個友善的地方對關于釋出分支或是維護分支的問題進行交流。

git fetch用法_學習Git操作, Git工作流(Git Workflow), 此文章足矣!

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

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

整篇文章整理自 xirong 的GitHub