SVN VS GIT,版本管理工作模式、流程與工具選擇

目錄

背景

我們的產(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)制提交方式提交代碼

FAQ

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容