目錄
- 背景
- 目標(biāo)
- 主流協(xié)作模式分析
- 主流版本管理工具:SVN和Git
- 小結(jié)
- 快速掌握Git
- 團(tuán)隊(duì)Git版本管理規(guī)范
- FAQ
- 參考
背景
我們的產(chǎn)品線在十幾個(gè)省獨(dú)立部署,十幾個(gè)省的個(gè)性化需求層出不窮,各省的bug也需要緊急修復(fù),在svn集中版本控制模式下,主線版本、十幾個(gè)分支版本如何有效管理,如何并行開發(fā)十幾個(gè)省的個(gè)性化需求、同時(shí)并行修復(fù)十幾個(gè)省的bug、主線版本、十多個(gè)分支版本發(fā)布周期及其節(jié)奏如何控制成為一個(gè)棘手問題:
- 一直沿用branchs、tags、trunk分支模式,未關(guān)注有效的版本控制方法論和版本控制流程,往往是多人同時(shí)在同一個(gè)遠(yuǎn)程分支中工作,在一個(gè)迭代周期內(nèi),版本的發(fā)布由耗時(shí)最長(zhǎng)的任務(wù)決定,同一迭代周期內(nèi)已修復(fù)的bug或開發(fā)完成的feature無法及時(shí)發(fā)布,開發(fā)人員互相等待,效率降低,用戶滿意度下降;
- 創(chuàng)建分支的動(dòng)作是在遠(yuǎn)端執(zhí)行,無法直接創(chuàng)建本地分支,分支的創(chuàng)建實(shí)際上是對(duì)遠(yuǎn)程發(fā)送創(chuàng)建指令,然后中央倉(cāng)庫(kù)完整拷貝(可能是全部)文件;遠(yuǎn)程創(chuàng)建完畢后才能從遠(yuǎn)端把分支檢出到本地,在遠(yuǎn)端創(chuàng)建分支意味著創(chuàng)建分支是一件影響廣泛、需要謹(jǐn)慎對(duì)待的事;
- svn采用基于目錄的分支模式,分支的重要標(biāo)識(shí)是該分支的RUL,雖然也支持在在同一workspace下切換分支,但切換分支必須通過網(wǎng)絡(luò)訪問到中央版本庫(kù),分支管理嚴(yán)重依賴網(wǎng)絡(luò)的穩(wěn)定性。
- 團(tuán)隊(duì)人員組織方式未能匹配當(dāng)前產(chǎn)品發(fā)展要求,各小組的分工、組織、管理以及協(xié)同工作效率低下。
工具本身不是銀彈,無論是svn還是git,都有其特點(diǎn)和優(yōu)勢(shì),我們要解決的問題并不是工具的取舍問題,而是需要探尋我們的工作流模型如何更具有彈性、更高效、更便利,在建立工作模型的基礎(chǔ)上,再根據(jù)團(tuán)隊(duì)特點(diǎn)、組織特點(diǎn)、工作特點(diǎn),探索合理的workflow ,尋求和團(tuán)隊(duì)相適應(yīng)的協(xié)作模式、版本管理規(guī)范以及團(tuán)隊(duì)組織配置方式,并選擇相對(duì)更優(yōu)的工具。
目標(biāo)
- 在多分支場(chǎng)景下理清主線與分支的關(guān)系,有效管理分支顆粒大小,有效提高團(tuán)隊(duì)協(xié)作效率
- 研究一套高效的版本管理與協(xié)作流程規(guī)范,實(shí)現(xiàn)多版本并行開發(fā)、實(shí)現(xiàn)版本快速發(fā)布甚至隨時(shí)發(fā)布
- 梳理團(tuán)隊(duì)易于理解、便于執(zhí)行的代碼版本管理規(guī)范,并進(jìn)行有效傳達(dá)執(zhí)行
- 便捷高效的以
代碼review + 自動(dòng)化單元測(cè)試為基礎(chǔ)的代碼質(zhì)量管理規(guī)范 - 構(gòu)建自動(dòng)單元測(cè)試、自動(dòng)構(gòu)建的基礎(chǔ)支持環(huán)境
- 在團(tuán)隊(duì)學(xué)習(xí)成本可控的前提下,選擇合適的版本管理工具
主流協(xié)作模式分析
為了解決上述問題,下面對(duì)主流并行協(xié)作工作模式、常見版本控制工具進(jìn)行介紹,結(jié)合當(dāng)前團(tuán)隊(duì)現(xiàn)狀,選擇合適的團(tuán)隊(duì)協(xié)作模式和版本控制工具:
經(jīng)典集中式svn協(xié)作模式
trunk
|
branches
|
tags
這是svn的標(biāo)準(zhǔn)結(jié)構(gòu)布局,trunk為主開發(fā)目錄,branches為分支開發(fā)目錄,tags為tag存檔目錄,tags分支通常不允許修改。但是具體這幾個(gè)目錄應(yīng)該如何使用,svn并沒有明確的規(guī)范,更多的還是用戶自己的習(xí)慣。常見的方式為:
trunk
Truck(主干)是用來做主方向開發(fā)的,初始的開發(fā)應(yīng)放在主線中,當(dāng)模塊開發(fā)完成后,需要修改,就要用到branch。
branches
branch(分支)開發(fā)和Truck(主干)開發(fā)是可以同時(shí)進(jìn)行的,也就是并行開發(fā),分支通常用于修復(fù)bug時(shí)使用,也可以用于為了引入新技術(shù)所做的驗(yàn)證性開發(fā)。
tags
tags用來備份代碼,通常是只讀的,不被用來開發(fā),只是用來標(biāo)記代碼的狀態(tài)、建立代碼基線。
SVN版本控制流程
主干(trunk)、分支(branch)、標(biāo)簽(tag)的創(chuàng)建由配置管理員統(tǒng)一負(fù)責(zé),開發(fā)人員在工作電腦中可以存在兩個(gè)工作空間(workspace),用于根據(jù)不同開發(fā)需要切換主干和分支。(也可以在同一個(gè)workspace中直接切換分支,需要注意切換過程不要填錯(cuò)版本url)
分支作用的選擇:
集中式:trunk進(jìn)行主要開發(fā)
所有的開發(fā)都基于trunk進(jìn)行開發(fā),當(dāng)一個(gè)版本/release開發(fā)告一段落(開發(fā)、測(cè)試、文檔、制作安裝程序、打包等)結(jié)束后,代碼處于凍結(jié)狀態(tài)(人為規(guī)定,可以通過hook來進(jìn)行管理)。此時(shí)應(yīng)該基于當(dāng)前凍結(jié)的代碼庫(kù),打tag。當(dāng)下一個(gè)版本/階段的開發(fā)任務(wù)開始,繼續(xù)在trunk進(jìn)行開發(fā)。
分散式:分支進(jìn)行主要開發(fā)
這種開發(fā)模式當(dāng)中,trunk是不承擔(dān)具體開發(fā)任務(wù)的,主要承擔(dān)版本發(fā)布,一個(gè)版本/階段的開發(fā)任務(wù)在開始的時(shí)候,根據(jù)已經(jīng)release的版本做新的開發(fā)分支,并且基于這個(gè)分支進(jìn)行開發(fā)。開發(fā)完成后merge回trunk分支。這種模式相對(duì)集中式開發(fā)而言具有更大的靈活性,最大的問題是代碼合并困難。
在分支上開發(fā)的代碼,可以在日常開發(fā)過程中自測(cè)通過后同步合并到主干,也可在分支通過測(cè)試并發(fā)布后統(tǒng)一合并到主干。建議采用日常開發(fā)過程中同步合并到主干,以降低最終合并的復(fù)雜度。
我們注意到,svn的工作流沒有明確定義,開啟分支是在遠(yuǎn)程倉(cāng)庫(kù)上開啟,分支的開啟與合并是一項(xiàng)高成本活動(dòng),在實(shí)際團(tuán)隊(duì)工作中往往造成分支包含的工作顆粒度過大。
這個(gè)協(xié)作模式在項(xiàng)目型軟件中,當(dāng)只有極少量線上部署版本,需求變化、bug梳理不多時(shí),在小規(guī)模團(tuán)隊(duì)中非常便捷。
[圖片上傳失敗...(image-95a78e-1564123012241)]
富有影響力的分布式gitflow協(xié)作模式
Git Flow有主分支和輔助分支兩類分支。其中主分支用于組織與軟件開發(fā)、部署相關(guān)的活動(dòng);輔助分支組織為了解決特定的問題而進(jìn)行的各種開發(fā)活動(dòng)。輔助分支完成其使命后,可以 / 應(yīng)該從遠(yuǎn)程倉(cāng)庫(kù)中刪除,以保持遠(yuǎn)程版本庫(kù)的整潔。
[圖片上傳失敗...(image-f05a3-1564123012241)]
(圖片來源:https://shockerli.net/post/git-flow-guide/)
主分支
主分支是所有開發(fā)活動(dòng)的核心分支。所有的開發(fā)活動(dòng)產(chǎn)生的輸出物最終都會(huì)反映到主分支的代碼中。主分支包含master分支和develop分支。
master分支
- master分支存放的是隨時(shí)可供在生產(chǎn)環(huán)境中部署的穩(wěn)定版本代碼
- master分支保存官方發(fā)布版本歷史,通過在master分支上打上tag來標(biāo)識(shí)不同的發(fā)布版本
- 一個(gè)項(xiàng)目只能有一個(gè)master分支
- 僅在發(fā)布新的可供部署的代碼時(shí)才更新master分支上的代碼
- 每次更新master,都需對(duì)master添加指定格式的tag,用于發(fā)布或回滾
- master分支是保護(hù)分支,不可直接push到遠(yuǎn)程倉(cāng)master分支
- master分支代碼只能被release分支或hotfix分支合并
develop分支
- develop分支是保存當(dāng)前最新開發(fā)成果的分支
- 一個(gè)項(xiàng)目只能有一個(gè)develop分支
- develop分支衍生出各個(gè)feature分支
- develop分支是保護(hù)分支,不可直接push到遠(yuǎn)程倉(cāng)庫(kù)develop分支
- develop分支不能與master分支直接交互
輔助分支
輔助分支是用于組織解決特定問題的各種軟件開發(fā)活動(dòng)的分支。輔助分支主要用于組織軟件新功能的并行開發(fā)、簡(jiǎn)化新功能開發(fā)代碼的跟蹤、輔助完成版本發(fā)布工作以及對(duì)生產(chǎn)代碼的缺陷進(jìn)行緊急修復(fù)工作。這些分支與主分支不同,通常只會(huì)在有限的時(shí)間范圍內(nèi)存在。
輔助分支包括:
- 用于開發(fā)新功能時(shí)所使用的feature分支
- 用于輔助版本發(fā)布的release分支
- 用于修正生產(chǎn)代碼中的缺陷的hotfix分支
- 用于輔助老版本運(yùn)維、發(fā)布老版本補(bǔ)丁的support分支
feature 分支使用規(guī)范
- 命名規(guī)則:feature/*
- develop分支的功能分支
- feature分支使用develop分支作為它們的父類分支
- 以功能為單位從develop拉一個(gè)feature分支
- 每個(gè)feature分支顆粒要盡量小,以利于快速迭代和避免沖突
- 當(dāng)其中一個(gè)feature分支完成后,它會(huì)合并回develop分支
- 當(dāng)一個(gè)功能因?yàn)楦鞣N原因不開發(fā)了或者放棄了,這個(gè)分支直接廢棄,不影響develop分支
- feature分支代碼可以保存在開發(fā)者自己的代碼庫(kù)中而不強(qiáng)制提交到主代碼庫(kù)里
- feature分支只與develop分支交互,不能與master分支直接交互
如有幾個(gè)同事同時(shí)開發(fā),需要分割成幾個(gè)小功能,每個(gè)人都需要從develop中拉出一個(gè)feature分支,但是每個(gè)feature顆粒要盡量小,因?yàn)樗枰覀兡鼙M早merge回develop分支,否則沖突解決起來就沒完沒了。同時(shí),當(dāng)一個(gè)功能因?yàn)楦鞣N原因不開發(fā)了或者放棄了,這個(gè)分支直接廢棄,不影響develop分支。
release 分支使用規(guī)范
- 命名規(guī)則:release/,“”以本次發(fā)布的版本號(hào)為標(biāo)識(shí)
- release分支主要用來為發(fā)布新版的測(cè)試、修復(fù)做準(zhǔn)備
- 當(dāng)需要為發(fā)布新版做準(zhǔn)備時(shí),從develop衍生出一個(gè)release分支
- release分支可以從develop分支上指定commit派生出
- release分支測(cè)試通過后,合并到master分支并且給master標(biāo)記一個(gè)版本號(hào)
- release分支一旦建立就將獨(dú)立,不可再?gòu)钠渌种ull代碼
- 必須合并回develop分支和master分支
release分支是為發(fā)布新的產(chǎn)品版本而設(shè)計(jì)的。在這個(gè)分支上的代碼允許做小的缺陷修正、準(zhǔn)備發(fā)布版本所需的各項(xiàng)說明信息(版本號(hào)、發(fā)布時(shí)間、編譯時(shí)間等)。通過在release分支上進(jìn)行這些工作可以讓develop分支空閑出來以接受新的feature分支上的代碼提交,進(jìn)入新的軟件開發(fā)迭代周期。
當(dāng)develop分支上的代碼已經(jīng)包含了所有即將發(fā)布的版本中所計(jì)劃包含的軟件功能,并且已通過所有測(cè)試時(shí),我們就可以考慮準(zhǔn)備創(chuàng)建release分支了。而所有在當(dāng)前即將發(fā)布的版本之外的業(yè)務(wù)需求一定要確保不能混到release分支之內(nèi)(避免由此引入一些不可控的系統(tǒng)缺陷)。
成功的派生了release分支,并被賦予版本號(hào)之后,develop分支就可以為“下一個(gè)版本”服務(wù)了。所謂的“下一個(gè)版本”是在當(dāng)前即將發(fā)布的版本之后發(fā)布的版本。版本號(hào)的命名可以依據(jù)項(xiàng)目定義的版本號(hào)命名規(guī)則進(jìn)行。
hotfix 分支使用規(guī)范
- 命名規(guī)則:hotfix/*
- hotfix分支用來快速給已發(fā)布產(chǎn)品修復(fù)bug或微調(diào)功能
- 只能從master分支指定tag版本衍生出來
- 一旦完成修復(fù)bug,必須合并回master分支和develop分支
- master被合并后,應(yīng)該被標(biāo)記一個(gè)新的版本號(hào)
- hotfix分支一旦建立就將獨(dú)立,不可再?gòu)钠渌种ull代碼
除了是計(jì)劃外創(chuàng)建的以外,hotfix分支與release分支十分相似:都可以產(chǎn)生一個(gè)新的可供在生產(chǎn)環(huán)境部署的軟件版本。
當(dāng)生產(chǎn)環(huán)境中的軟件遇到了異常情況或者發(fā)現(xiàn)了嚴(yán)重到必須立即修復(fù)的軟件缺陷的時(shí)候,就需要從master分支上指定的TAG版本派生hotfix分支來組織代碼的緊急修復(fù)工作。
這樣做的顯而易見的好處是不會(huì)打斷正在進(jìn)行的develop分支的開發(fā)工作,能夠讓團(tuán)隊(duì)中負(fù)責(zé)新功能開發(fā)的人與負(fù)責(zé)代碼緊急修復(fù)的人并行的開展工作。
support 分支使用規(guī)范
- 命名規(guī)則:support/*
Say you had a project, and you were happily releasing new versions.
Maybe your current production version was 8.x. But you had some Really
Important Customers who refused to upgrade to anything after 6.0. Now,
if someone found a security flaw in the 6.0 version of your project,
it would be a bad idea to hang all those Really Important Customers
out to dry. So you release a new hotfix against 6.0, even though all
their problems would be solved if they just upgraded to the new
release (It has been nearly 10 years since you released 6.0 after
all).
In order to keep supporting these Really Lazy Customers, you release
hotfix 6.0.1 to fix the security bug. In "normal" git, it would look
something like:
> git checkout 6.0
> git checkout -b support/6.x
> git checkout -b hotfix/6.0.1
make your fix, then:
> git checkout support/6.x
> git merge hotfix/6.0.1
> git branch -d hotfix/6.0.1
> git tag 6.0.1
if needed, this change can also be applied to a hotfix for 8.x,
via cherry-pick.
You would keep the support/6.x version around as long as your Really
Important Customers still needed security fixes for 6.0, and would
make 6.x.x releases off this branch instead of production.
其他幾種常見的協(xié)作模型
Forking工作流
Forking工作流是分布式工作流,充分利用了Git在分支和克隆上的優(yōu)勢(shì)。可以安全可靠地管理大團(tuán)隊(duì)的開發(fā)者(developer),并能接受不信任貢獻(xiàn)者(contributor)的提交。
forking工作流最大的特點(diǎn)是每個(gè)開發(fā)者在兩個(gè)遠(yuǎn)程倉(cāng)庫(kù)中工作,F(xiàn)orking工作流要先有一個(gè)公開的正式倉(cāng)庫(kù)存儲(chǔ)在服務(wù)器上。 但一個(gè)新的開發(fā)者想要在項(xiàng)目上工作時(shí),不是直接從正式倉(cāng)庫(kù)克隆,而是fork正式項(xiàng)目在服務(wù)器上創(chuàng)建一個(gè)拷貝。
這個(gè)fork來的倉(cāng)庫(kù)拷貝作為他個(gè)人公開倉(cāng)庫(kù) —— 其它開發(fā)者不允許push到這個(gè)倉(cāng)庫(kù),但可以pull到修改。 在創(chuàng)建了自己服務(wù)端拷貝之后,和gitflow工作流一樣,開發(fā)者執(zhí)行g(shù)it clone命令克隆倉(cāng)庫(kù)到本地機(jī)器上,作為私有的開發(fā)環(huán)境。
要提交本地修改時(shí),push提交到自己公開倉(cāng)庫(kù)中 —— 而不是正式倉(cāng)庫(kù)中。 然后,給正式倉(cāng)庫(kù)發(fā)起一個(gè)pull request,讓項(xiàng)目維護(hù)者知道有更新已經(jīng)準(zhǔn)備好可以集成了。 對(duì)于貢獻(xiàn)的代碼,pull request也可以很方便地作為一個(gè)討論的地方。
Pull Requests
Pull Requests通常在github、gitlab等基于git的工作平臺(tái)上使用。
當(dāng)要發(fā)起一個(gè)Pull Request,你所要做的就是請(qǐng)求(Request)另一個(gè)開發(fā)者(比如項(xiàng)目的維護(hù)者) 來pull你倉(cāng)庫(kù)中一個(gè)分支到他的倉(cāng)庫(kù)中。這意味著你要提供4個(gè)信息以發(fā)起Pull Request: 源倉(cāng)庫(kù)、源分支、目的倉(cāng)庫(kù)、目的分支。
在不同的工作流中使用Pull Request會(huì)有一些不同,但基本的過程是這樣的:
- 開發(fā)者在本地倉(cāng)庫(kù)中新建一個(gè)專門的分支開發(fā)功能。
- 開發(fā)者push分支修改到公開的Bitbucket倉(cāng)庫(kù)中。
- 開發(fā)者通過Bitbucket/gitlab/github發(fā)起一個(gè)Pull Request。
- 團(tuán)隊(duì)的其它成員review code,討論并修改。
- 項(xiàng)目維護(hù)者合并功能到官方倉(cāng)庫(kù)中并關(guān)閉Pull Request。
前面我們討論了目前主流的幾種協(xié)作模型,不涉及具體工具的選擇,比如可以選擇git工具而使用經(jīng)典集中式svn協(xié)作模式,或者選擇svn工具而應(yīng)用gitflow協(xié)作模式。下面我們將進(jìn)入工具層面的討論。
主流版本管理工具:SVN和Git
SVN:集中式版本控制系統(tǒng)
SVN簡(jiǎn)介
SVN(Subversion)是集中式管理的版本控制器,SVN只有一個(gè)單一的集中管理的服務(wù)器,保存所有文件的修訂版本,而協(xié)同工作的人們都通過客戶端連到這臺(tái)服務(wù)器,取出最新的文件或者提交更新,這意味著協(xié)同工作時(shí)必須與中央倉(cāng)庫(kù)聯(lián)網(wǎng)。
SVN的主要特點(diǎn)
- svn中的版本號(hào)revision是全局版本號(hào),svn commit 操作被當(dāng)作一次原子事務(wù)操作。每當(dāng)版本庫(kù)接受了一個(gè)提交,文件系統(tǒng)進(jìn)入了一個(gè)新的狀態(tài),叫做revision,每個(gè)revision被賦予一個(gè)全局獨(dú)一無二的遞增的自然數(shù)。
- 每個(gè)版本庫(kù)及其版本都有唯一的URL(中心庫(kù)地址),每個(gè)用戶都從這個(gè)地址獲取代碼和數(shù)據(jù);
- 獲取該版本代碼的更新,也只能連接到這個(gè)唯一的版本庫(kù),同步以取得最新數(shù)據(jù);
- 提交必須有網(wǎng)絡(luò)連接;
- 較為精細(xì)的權(quán)限控制能力,提交需要授權(quán),如果沒有寫權(quán)限,提交會(huì)失?。?/li>
- SVN原理上關(guān)心文件內(nèi)容的具體差異。每次記錄有哪些文件作了更新,以及都更新了哪些行的什么內(nèi)容。
SVN常用工具
服務(wù)端
- VisualSVN:用于搭建svn服務(wù)器的工具,主要功能為用戶版本控制和版本庫(kù)存儲(chǔ);
客戶端
- TortoiseSVN : 封裝了svn客戶端核心并進(jìn)行優(yōu)化后的svn客戶端工具
Git:分布式版本控制系統(tǒng)
Git簡(jiǎn)介
Git是分布式管理的版本控制器,這是git和svn兩者之間最核心的區(qū)別。
Git每一個(gè)終端都是一個(gè)倉(cāng)庫(kù),包括中央倉(cāng)庫(kù)在內(nèi),每個(gè)倉(cāng)庫(kù)在原理上都是平等的,客戶端并不只提取最新版本的文件快照,而是把原始的代碼倉(cāng)庫(kù)完整地鏡像下來。每一次的提取操作,實(shí)際上都是一次對(duì)代碼倉(cāng)庫(kù)的完整備份。
git每次提交,所有數(shù)據(jù)都要進(jìn)行內(nèi)容的校驗(yàn)和(checksum)計(jì)算,并將此結(jié)果作為數(shù)據(jù)的唯一標(biāo)識(shí)和索引。這項(xiàng)特性作為 Git 的設(shè)計(jì)哲學(xué),建在整體架構(gòu)的最底層。所以如果文件在傳輸時(shí)變得不完整,或者磁盤損壞導(dǎo)致文件數(shù)據(jù)缺失,Git 都能立即察覺。
Git記錄版本歷史只關(guān)心文件數(shù)據(jù)的整體是否發(fā)生變化。Git 不保存文件內(nèi)容前后變化的差異數(shù)據(jù)。
實(shí)際上,Git 更像是把變化的文件作快照后,記錄在一個(gè)微型的文件系統(tǒng)中。每次提交更新時(shí),它會(huì)縱覽一遍所有文件的指紋信息并對(duì)文件作一快照,然后保存一個(gè)指向這次快照的索引。為提高性能,若文件沒有變化,Git 不會(huì)再次保存,而只對(duì)上次保存的快照作一連接。Git 的工作完全依賴于這類指紋字串,所以你會(huì)經(jīng)??吹竭@樣的哈希值。實(shí)際上,所有保存在 Git 數(shù)據(jù)庫(kù)中的東西都是用此哈希值來作索引的,而不是靠文件名。
Git的主要特點(diǎn)
- Git中每個(gè)版本庫(kù)原則上都是平等的
- Git的每一次提取操作,實(shí)際上都是一次對(duì)代碼倉(cāng)庫(kù)的完整備份。
- 強(qiáng)大的分支管理能力
- 多數(shù)操作是添加操作
- 近乎所有操作都是本地執(zhí)行
- 直接記錄快照,而非差異比較
- 提供離線版本控制能力
- 更為強(qiáng)大的沖突合并能力
- 更多可選協(xié)作模式
- 有一定的學(xué)習(xí)成本,學(xué)習(xí)曲線比較陡峭
- 相對(duì)較弱的權(quán)限控制能力
git常用工具
服務(wù)端
- 直接使用git
- github
- gitlab
客戶端
- Source Tree
號(hào)稱是最好用的Git GUI工具,內(nèi)建持Git Flow支持 - TortoiseGit
與TortoiseSVN一脈相承的操作體驗(yàn) - Eclipse – Egit
Eclipse內(nèi)置了egit插件來提供git的集成支持,低版本Eclipse的egit功能較弱 - Visual Studio – Git Integration & GitHub Extension
VS里面的Git支持已經(jīng)相當(dāng)?shù)耐晟?/li>
小結(jié)
svn的優(yōu)勢(shì)在于團(tuán)隊(duì)比較熟悉,幾乎無學(xué)習(xí)成本,基于目錄的分支方式簡(jiǎn)單、容易理解,也略顯粗暴,集中式的版本管理模式在團(tuán)隊(duì)集中辦公、集中開發(fā),且產(chǎn)品分支少、需求明確,開發(fā)過程分支少、所有工作均具備網(wǎng)絡(luò)環(huán)境、基于瀑布式的開發(fā)模式時(shí)簡(jiǎn)單易行。當(dāng)然也可以在svn集中版本控制模式下應(yīng)用gitflow工作流。
git的優(yōu)勢(shì)在于分布式、可離線版本控制,github、gitlab等類似工具和平臺(tái)的出現(xiàn)使得git分布式版本控制形成了一定的生態(tài),git在開源軟件的協(xié)同、分發(fā)更具優(yōu)勢(shì),與此帶來的問題是相對(duì)較弱的權(quán)限控制。git有一定的學(xué)習(xí)成本,想要享受git的便利和功能,就需要付出深入學(xué)習(xí)的成本。
git在提供工具的同時(shí),還明確給出了協(xié)同工作分支的建議模型gitflow,gitflow邏輯嚴(yán)謹(jǐn),適應(yīng)性強(qiáng),是更為先進(jìn)的協(xié)同工作和版本控制的思想,可以有效解決團(tuán)隊(duì)當(dāng)前面臨的諸多問題。gitflow可以在svn中使用,但它終究是為git量身設(shè)計(jì)和打造的,為了更好的享受gitflow帶來的便利,團(tuán)隊(duì)選擇 git + gitflow的模式。
快速掌握Git
git的物理結(jié)構(gòu)
本質(zhì)上,Git是一套內(nèi)容尋址(content-addressable)文件系統(tǒng)。在操作系統(tǒng)中,倉(cāng)庫(kù)就是一個(gè)文件夾。但是為什么這些文件夾就是Git倉(cāng)庫(kù)呢?這是因?yàn)镚it在初始化的時(shí)候會(huì)生成一個(gè).git的文件夾,Git進(jìn)行版本控制所需要的所有文件都放在這個(gè)文件夾中。
[圖片上傳失敗...(image-47df1c-1564123012241)]
[圖片上傳失敗...(image-db6f46-1564123012241)]
config文件
該文件主要記錄針對(duì)該項(xiàng)目的一些配置信息,例如是否以bare方式初始化、remote的信息等,通過git remote add命令增加的遠(yuǎn)程分支的信息就保存在這里;
objects文件夾
該文件夾主要包含git對(duì)象。Git中的文件和一些操作都會(huì)以git對(duì)象來保存,git對(duì)象分為BLOB、tree和commit三種類型,例如git commit便是git中的commit對(duì)象,而各個(gè)版本之間是通過版本樹來組織的,比如當(dāng)前的HEAD會(huì)指向某個(gè)commit對(duì)象,而該commit對(duì)象又會(huì)指向幾個(gè)BLOB對(duì)象或者tree對(duì)象。objects文件夾中會(huì)包含很多的子文件夾,其中Git對(duì)象保存在以其sha-1值的前兩位為子文件夾、后38位位文件名的文件中;除此以外,Git為了節(jié)省存儲(chǔ)對(duì)象所占用的磁盤空間,會(huì)定期對(duì)Git對(duì)象進(jìn)行壓縮和打包,其中pack文件夾用于存儲(chǔ)打包壓縮的對(duì)象。
info文件夾
用于從打包的文件中查找git對(duì)象;
HEAD文件
該文件指明了git branch(即當(dāng)前分支)的結(jié)果,比如當(dāng)前分支是master,則該文件就會(huì)指向master,但是并不是存儲(chǔ)一個(gè)master字符串,而是分支在refs中的表示,例如ref: refs/heads/master。
index文件
該文件保存了暫存區(qū)域的信息。該文件某種程度就是緩沖區(qū)(staging area),內(nèi)容包括它指向的文件的時(shí)間戳、文件名、sha1值等;
Refs文件夾
該文件夾存儲(chǔ)指向數(shù)據(jù)(分支)的提交對(duì)象的指針。其中heads文件夾存儲(chǔ)本地每一個(gè)分支最近一次commit的sha-1值(也就是commit對(duì)象的sha-1值),每個(gè)分支一個(gè)文件;remotes文件夾則記錄你最后一次和每一個(gè)遠(yuǎn)程倉(cāng)庫(kù)的通信,Git會(huì)把你最后一次推送到這個(gè)remote的每個(gè)分支的值都記錄在這個(gè)文件夾中;tag文件夾則是分支的別名,這里不需要對(duì)其有過多的了解;
除此以外,.git目錄下還有很多其他的文件和文件夾,這些文件和文件夾會(huì)額外支撐一些其他的功能,但是不是Git的核心部分,因此稍作了解即可。
hooks文件夾
主要定義了客戶端或服務(wù)端鉤子腳本,這些腳本主要用于在特定的命令和操作之前或者之后進(jìn)行特定的處理,比如:當(dāng)你把本地倉(cāng)庫(kù)push到服務(wù)器的遠(yuǎn)程倉(cāng)庫(kù)時(shí),可以在服務(wù)器倉(cāng)庫(kù)的hooks文件夾下定義post_update腳本,在該腳本中可以通過腳本代碼將最新的代碼部署到服務(wù)器的web服務(wù)器上,從而將版本控制和代碼發(fā)布無縫連接起來;
description文件
僅供GitWeb程序使用,不需要過多的關(guān)心;
logs
記錄了本地倉(cāng)庫(kù)和遠(yuǎn)程倉(cāng)庫(kù)的每一個(gè)分支的提交記錄,即所有的commit對(duì)象(包括時(shí)間、作者等信息)都會(huì)被記錄在這個(gè)文件夾中,因此這個(gè)文件夾中的內(nèi)容是我們查看最頻繁的,不管是Git log命令還是tortoiseGit的show log,都需要從該文件夾中獲取提交日志;
info文件夾
保存了一份不希望在.gitignore 文件中管理的忽略模式的全局可執(zhí)行文件,基本也用不上;
COMMIT_EDITMSG文件
記錄了最后一次提交時(shí)的注釋信息。從以上的描述中我們可以發(fā)現(xiàn),.git文件夾中包含了眾多功能不一的文件夾和文件,這些文件夾和文件是描述Git倉(cāng)庫(kù)所必不可少的信息,不可以隨意更改或刪除;尤其需要注意的是,.git文件夾隨著項(xiàng)目的演進(jìn),可能會(huì)變得越來越大,因?yàn)槿魏挝募娜魏我粋€(gè)變動(dòng),都需要Git在objects文件夾下將其重新存儲(chǔ)為一個(gè)新的對(duì)象文件,因此如果一個(gè)文件非常大,那么你提交幾次改動(dòng)就會(huì)造成.git文件夾容量成倍增長(zhǎng)。
因此,.git文件夾更像是一本書,每一個(gè)版本的每一個(gè)變動(dòng)都存儲(chǔ)在這本書中,而且這本書還有一個(gè)目錄,指明了不同的版本的變動(dòng)內(nèi)容存儲(chǔ)在這本書的哪一頁上,這就是Git的最基本的原理。
git的邏輯結(jié)構(gòu)
Git是一套內(nèi)容尋址(content-addressable)文件系統(tǒng),Git 的核心部分是一個(gè)簡(jiǎn)單的鍵值對(duì)數(shù)據(jù)庫(kù)(key-value data store)。 你可以向該數(shù)據(jù)庫(kù)插入任意類型的內(nèi)容,它會(huì)返回一個(gè)鍵值,通過該鍵值可以在任意時(shí)刻再次檢索(retrieve)該內(nèi)容。
Git采用HashTable的方式進(jìn)行查找,也就是說,Git只是通過簡(jiǎn)單的存儲(chǔ)鍵值對(duì)(key-value pair)的方式來實(shí)現(xiàn)內(nèi)容尋址的:
- key就是文件(頭+內(nèi)容)的哈希值(采用sha-1的方式,40位)
- value就是經(jīng)過壓縮后的文件內(nèi)容。要操作對(duì)象時(shí),需要通過key來指定所要操作的對(duì)象。
Git對(duì)象的存儲(chǔ)方式也很簡(jiǎn)單,基本可以用如下表達(dá)式來表示:
Key = sha1(file_header + file_content)
Value = zlib(file_content)
** git邏輯結(jié)構(gòu)中樹是其重要的體系思想:工作樹、提交樹等在git中發(fā)揮非常重要的作用。
git 中的四種對(duì)象
<img src="/assets/git data model.jpg" align="center" width="60%" />
<img src="/assets/git data model2.jpg" align="center" width="60%" />
Git中的文件和一些操作都會(huì)以git對(duì)象來保存,Git對(duì)象的類型包括:
- blob對(duì)象
- tree對(duì)象
- commit對(duì)象
- tag對(duì)象
Commit組件包含了Tree,Tree組件中又有Blob組件:
- BLOB對(duì)象可以存儲(chǔ)幾乎所有的文件類型,全稱為binary large object,顧名思義,就是大的二進(jìn)制表示的對(duì)象,這種對(duì)象類型和數(shù)據(jù)庫(kù)中的BLOB類型(經(jīng)常用來在數(shù)據(jù)庫(kù)中存儲(chǔ)圖片、視頻等)是一樣的,當(dāng)作一種數(shù)據(jù)類型即可;
- tree對(duì)象是用來組織BLOB對(duì)象的一種數(shù)據(jù)類型,你完全可以把它想象成二叉樹中的樹節(jié)點(diǎn),只不過Git中的樹不是二叉樹,而是"多叉樹";
- commit對(duì)象表示每一次的提交操作,由tree對(duì)象衍生,每一個(gè)commit對(duì)象表示一次提交,在創(chuàng)建的過程中可以指定該commit對(duì)象的父節(jié)點(diǎn),這樣所有的commit操作便可以連接在一起,而這些commit對(duì)象便組成了提交樹,branch只不過是這個(gè)樹中的某一個(gè)子樹罷了。如果你能理解commit樹,那Git幾乎就已經(jīng)理解了一半了。
git中文件的三種狀態(tài)
<img src="/assets/git file status2.png" align="center" width="70%" />
受Git管理的三種狀態(tài)
- staged
- modified
- committed
圖中處于untracked狀態(tài)的文件不受git管理。
Git文件的三個(gè)流轉(zhuǎn)區(qū)域
- 工作區(qū)域
- 索引區(qū)域
- 本地?cái)?shù)據(jù)區(qū)域
本地倉(cāng)庫(kù)與遠(yuǎn)程倉(cāng)庫(kù)的關(guān)系
<img src="/assets/git file status5.png" align="center" width="40%" />
<img src="/assets/git file status and command.png" align="center" width="50%" />
從上述圖形的描述我們可以直觀看出來,git的幾乎所有操作都屬于本地操作,會(huì)影響遠(yuǎn)程倉(cāng)庫(kù)的命令只有push、remote xx;將文件從遠(yuǎn)程倉(cāng)庫(kù)同步到本地的有pull、(rebase?)、fetch、clone。其他git命令基本都屬于本地操作。
近乎所有操作都是本地操作:
<img src="/assets/pull vs fetch.png" align="center" width="100%" />
git 的安裝及初始化
- 下載Git并安裝 官方地址為:
https://git-scm.com/download/win
https://git-scm.com/download/mac
https://git-scm.com/download/linux
git的結(jié)構(gòu)
關(guān)于git命令
git客戶端圖形化工具的所有操作都是基于git命令本身,深入掌握git命令之后任何一個(gè)git客戶端圖形化工具的使用都能運(yùn)用自如,并且可以通過圖形化界面的操作深刻理解該操作背后具體做了什么事。因此本文僅講解git命令,你可以在日常工作中根據(jù)個(gè)人喜好使用命令行、圖形化工具方式,這兩種方式?jīng)]有高低之分,關(guān)鍵在于你是否理解git背后做了什么。
git命令分為常用命令、輔助操作命令、外部互操作命令、底層命令幾大類。git的所有命令可以通過列出:
git --help -a -v
可以看到git支持的全部命令,包括:
Main Porcelain Commands
add Add file contents to the index
am Apply a series of patches from a mailbox
archive Create an archive of files from a named tree
bisect Use binary search to find the commit that introduced a bug
branch List, create, or delete branches
bundle Move objects and refs by archive
checkout Switch branches or restore working tree files
cherry-pick Apply the changes introduced by some existing commits
citool Graphical alternative to git-commit
clean Remove untracked files from the working tree
clone Clone a repository into a new directory
commit Record changes to the repository
describe Give an object a human readable name based on an available ref
diff Show changes between commits, commit and working tree, etc
fetch Download objects and refs from another repository
format-patch Prepare patches for e-mail submission
gc Cleanup unnecessary files and optimize the local repository
gitk The Git repository browser
grep Print lines matching a pattern
gui A portable graphical interface to Git
init Create an empty Git repository or reinitialize an existing one
log Show commit logs
merge Join two or more development histories together
mv Move or rename a file, a directory, or a symlink
notes Add or inspect object notes
pull Fetch from and integrate with another repository or a local branch
push Update remote refs along with associated objects
rebase Reapply commits on top of another base tip
reset Reset current HEAD to the specified state
revert Revert some existing commits
rm Remove files from the working tree and from the index
shortlog Summarize 'git log' output
show Show various types of objects
stash Stash the changes in a dirty working directory away
status Show the working tree status
submodule Initialize, update or inspect submodules
tag Create, list, delete or verify a tag object signed with GPG
worktree Manage multiple working trees
Ancillary Commands / Manipulators
config Get and set repository or global options
fast-export Git data exporter
fast-import Backend for fast Git data importers
filter-branch Rewrite branches
mergetool Run merge conflict resolution tools to resolve merge conflicts
pack-refs Pack heads and tags for efficient repository access
prune Prune all unreachable objects from the object database
reflog Manage reflog information
remote Manage set of tracked repositories
repack Pack unpacked objects in a repository
replace Create, list, delete refs to replace objects
Ancillary Commands / Interrogators
annotate Annotate file lines with commit information
blame Show what revision and author last modified each line of a file
cherry Find commits yet to be applied to upstream
count-objects Count unpacked number of objects and their disk consumption
difftool Show changes using common diff tools
fsck Verifies the connectivity and validity of the objects in the database
get-tar-commit-id Extract commit ID from an archive created using git-archive
gitweb Git web interface (web frontend to Git repositories)
help Display help information about Git
instaweb Instantly browse your working repository in gitweb
merge-tree Show three-way merge without touching index
rerere Reuse recorded resolution of conflicted merges
rev-parse Pick out and massage parameters
show-branch Show branches and their commits
verify-commit Check the GPG signature of commits
verify-tag Check the GPG signature of tags
whatchanged Show logs with difference each commit introduces
Interacting with Others
archimport Import an Arch repository into Git
cvsexportcommit Export a single commit to a CVS checkout
cvsimport Salvage your data out of another SCM people love to hate
cvsserver A CVS server emulator for Git
imap-send Send a collection of patches from stdin to an IMAP folder
p4 Import from and submit to Perforce repositories
quiltimport Applies a quilt patchset onto the current branch
request-pull Generates a summary of pending changes
send-email Send a collection of patches as emails
svn Bidirectional operation between a Subversion repository and Git
Low-level Commands / Manipulators
apply Apply a patch to files and/or to the index
checkout-index Copy files from the index to the working tree
commit-graph Write and verify Git commit graph files
commit-tree Create a new commit object
hash-object Compute object ID and optionally creates a blob from a file
index-pack Build pack index file for an existing packed archive
merge-file Run a three-way file merge
merge-index Run a merge for files needing merging
mktag Creates a tag object
mktree Build a tree-object from ls-tree formatted text
pack-objects Create a packed archive of objects
prune-packed Remove extra objects that are already in pack files
read-tree Reads tree information into the index
symbolic-ref Read, modify and delete symbolic refs
unpack-objects Unpack objects from a packed archive
update-index Register file contents in the working tree to the index
update-ref Update the object name stored in a ref safely
write-tree Create a tree object from the current index
Low-level Commands / Interrogators
cat-file Provide content or type and size information for repository objects
diff-files Compares files in the working tree and the index
diff-index Compare a tree to the working tree or index
diff-tree Compares the content and mode of blobs found via two tree objects
for-each-ref Output information on each ref
ls-files Show information about files in the index and the working tree
ls-remote List references in a remote repository
ls-tree List the contents of a tree object
merge-base Find as good common ancestors as possible for a merge
name-rev Find symbolic names for given revs
pack-redundant Find redundant pack files
rev-list Lists commit objects in reverse chronological order
show-index Show packed archive index
show-ref List references in a local repository
unpack-file Creates a temporary file with a blob's contents
var Show a Git logical variable
verify-pack Validate packed Git archive files
Low-level Commands / Synching Repositories
daemon A really simple server for Git repositories
fetch-pack Receive missing objects from another repository
http-backend Server side implementation of Git over HTTP
send-pack Push objects over Git protocol to another repository
update-server-info Update auxiliary info file to help dumb servers
Low-level Commands / Internal Helpers
check-attr Display gitattributes information
check-ignore Debug gitignore / exclude files
check-mailmap Show canonical names and email addresses of contacts
check-ref-format Ensures that a reference name is well formed
column Display data in columns
credential Retrieve and store user credentials
credential-cache Helper to temporarily store passwords in memory
credential-store Helper to store credentials on disk
fmt-merge-msg Produce a merge commit message
interpret-trailers add or parse structured information in commit messages
mailinfo Extracts patch and authorship from a single e-mail message
mailsplit Simple UNIX mbox splitter program
merge-one-file The standard helper program to use with git-merge-index
patch-id Compute unique ID for a patch
sh-i18n Git's i18n setup code for shell scripts
sh-setup Common Git shell script setup code
stripspace Remove unnecessary whitespace
(END)
如果你想查看具體某個(gè)命令(以remote命令為例)的用法說明,可以使用如下命令:
git remote --help
快速掌握git命令
git相關(guān)概念、命令、注意事項(xiàng)等,請(qǐng)仔細(xì)閱讀、練習(xí)以下思維導(dǎo)圖所講解知識(shí)點(diǎn):
<img src="/assets/cyberwinds-git-turtorial.png" align="center" width="100%" />
git版本控制的相關(guān)角色
- git管理員: 擁有主庫(kù)的所有權(quán)限、負(fù)責(zé)賬號(hào)、權(quán)限的管理和維護(hù)。
- 開發(fā)經(jīng)理: 具有將開發(fā)人員的合并需求(MR)合入主庫(kù)的權(quán)限?;诎踩紤],我們?cè)O(shè)置為只能通過MR的方式將代碼合入主庫(kù),而不能直接push到主庫(kù)。負(fù)責(zé)管理 重要遠(yuǎn)程分支 的開啟、切換、合并、廢棄、標(biāo)記(tag)等
- 開發(fā)工程師: 只能從自己的個(gè)人代碼庫(kù)(服務(wù)端)提交合并代碼的請(qǐng)求(merge request/pull request),是否能夠合入,由開發(fā)經(jīng)理負(fù)責(zé)、各開發(fā)工程師共同進(jìn)行審核。
- 測(cè)試工程師: 對(duì)所有分支擁有只讀權(quán)限
中小團(tuán)隊(duì)git日常工作最佳實(shí)踐
團(tuán)隊(duì)日常工作最佳實(shí)踐的目的至少包含以下四點(diǎn):
- 本地工作流程在源頭上盡量減少?zèng)_突的發(fā)生,或者及時(shí)發(fā)現(xiàn)沖突、及時(shí)解決沖突
- 合理的沖突解決方案
- 清晰的項(xiàng)目提交樹、清晰的提交說明
- 清晰的分支、tag管理,便利的里程碑版本切換和提取
以下是推薦的日常工作模式:
(原文《USING GIT IN A TEAM: A CHEATSHEET 》)
<img src="/assets/workflow-team-practice.gif" align="center" width="100%" />
About half the time I use Git on projects only I will ever see, and the rest of the time I work collaboratively with a handful of people in my team. The workflow outlined below caters very well to this kind of use, and we've had success with it while we've been building Boords.
It's aimed at people who want to use Git in a simply, efficiently and with a minimum of fuss. I've been heavily influenced by the concepts covered in the Git course on Upcase. If you're looking to improve your skills as a developer I can heartily recommend signing up for a subscription with them.
As I say, the ideas here aren't anything new. It basically boils down to check out a new branch, work on it, then merge your changes back into the master branch in a single, curated commit. The idea is that by merging a single commit with all your changes rather than lots of smaller commits, your master branch nice stays and neat.
STEP BY STEP
To give a little more context, let's go through each of the steps above in a bit more detail so we can investigate what's going on.
STEP 1: CREATE A NEW BRANCH TO WORK ON
/*Create a new feature branch*/
git branch jc_new_feature
/*Checkout your new feature branch*/
git checkout jc_new_feature
When naming feature branches, a good best practice is to start with your initials, then the feature name (e.g. jc_feature_name). This helps others working on a project to see who's been doing what.
STEP 2: WRITE SOME CODE, COMMIT REGULARLY
/*Add all files to the stage*/
git add .
/*Commit files*/
git commit -m "Description of this commit"
/*Optional (but recommended) push local branch to remote*/
git push origin jc_feature_name
Commit your code regularly. I've never regretted making a commit, but have regretted not making one. You'll have a chance to change your commit messages before merging back into master.
STEP 3: FETCH WHEN YOU'RE DONE
When you're ready to merge your features back into the master branch, run git fetch.
git fetch
Fetching makes sure you're up to date when merging changes back into master. This doesn't actually merge the code with the code your machine (git pull does that), but instead updates references to any remote changes which may have happened while you've been working locally. It's groundwork for the next stage.
STEP 4: SQUASH YOUR COMMITS AND GET READY TO MERGE
Now you'll rebase your changes into the master branch. This effectively condenses down all the commits you've made on your feature branch (jc_feature_name) into one commit. We'll merge this one single commit back into the master branch, keeping everything nice and neat.
/*Open the interactive rebase tool*/
git rebase -i origin/master
This will open an interactive rebase tool, which shows all the commits you've made on your branch. You'll then "squash" all your changes into one commit.
To do this, replace pick with s for all but the top commit. s is shorthand for squash - imagine all the changes you've made being "squashed" up into the top commit.
<img src="/assets/rebase-1.jpg" align="center" width="100%" />
<center style="color:gray">Interactive rebase tool</center>
<img src="/assets/rebase-2.jpg" align="center" width="100%" />
<center style="color:gray">
"Squashing" three commits into one. Here the second and third
commits are squashed into the first commit.</center>
When you close this window you'll see all your existing commits, which you can edit down into a simpler, more concise commit message.
<img src="/assets/rebase-3.jpg" align="center" width="100%" />
<center style="color:gray">All previous commit messages</center>
<img src="/assets/rebase-4.jpg" align="center" width="100%" />
<center style="color:gray">Replacing existing commits with a new commit message</center>
Exit the rebase tool and you're ready to merge.
<img src="/assets/rebase-5.jpg" align="center" width="100%" />
STEP 5: MERGE YOUR CHANGES
Switch to the master branch in preparation of merging your changes. When merging always remember that you're merging into the branch you're currently on.
/*Checkout the master branch*/
git checkout master
/*Merge jc_feature_name INTO master*''
git merge jc_feature_name
The changes from the jc_feature_name branch are now merged into your master branch. Happy days. Now's a good time to push your changes to the remote master branch.
/*Push your local master branch to remote*/
git push origin master
STEP 6:CLEANUP
With your changes merged into the master branch, you can safely delete your feature branches.
/*Delete remote feature branch (the colon is important!)*/
git push origin :jc_feature_name
/*Delete local branch*/
git branch -d jc_feature_name
And with that, you're done.
MAKING IT WORK
It's very easy to feel insecure about using Git, and the spectre of losing work looms large. The key to making this system work is to go through the process a lot, regularly branching and merging. This gets you comfortable with the workflow and has the added bonus of making sure one branch never gets too far out of line with another.
It's not a perfect system. It doesn't take into account pull requests for one. Fixing merge conflicts can be fiddly (although this is the case regardless of your workflow). But overall it gets the job done and I can say without hesitation that it's made my work, and by extension my very existence, that little bit easier.
團(tuán)隊(duì)Git版本管理規(guī)范
- 本地修改代碼前一定要先pull當(dāng)前分支的遠(yuǎn)程分支
- 提交分支前也需要pull遠(yuǎn)程分支、合并本地分支后提交
- 除非你非常清楚你在做什么,否則禁用rebase
- 未經(jīng)開發(fā)經(jīng)理同意,禁止采用強(qiáng)制提交方式提交代碼