如何進行程式碼版本控制
2.2 程式碼版本控制
在平時的開發過程中,尤其是多人協同開發時,經常會遇到以下問題:
- 程式碼管理混亂
- 程式碼衝突
- 在程式碼整合期間引發bug
- 無法對程式碼的擁有者進行許可權控制
- 專案不同版本的釋出困難
程式碼版本控制(Version Control)通過追蹤檔案的變化解決上述問題。
2.2.1 SVN
SVN是前幾年用的最為普遍的一個VCS工具,採用了分支管理系統。具有以下幾個特點:
- 原子提交。一次提交不管是單個還是多個檔案,都是作為一個整體提交的。在這當中發生的意外例如傳輸中斷,不會引起資料庫的不完整和資料損壞。
- 重新命名、複製、刪除檔案等動作都儲存在版本歷史記錄當中。對於二進位制檔案,使用了節省空間的儲存方法(只儲存和上一版本不同之處)。
- 目錄也有版本歷史。整個目錄樹可以被移動或者複製,操作很簡單,而且能夠保留全部版本記錄。
- 分支的開銷非常小。
- 優化過的資料庫訪問,使得一些操作不必訪問資料庫就可以做到。這樣減少了很多不必要的和資料庫主機之間的網路流量。
- 集中式版本控制,依賴於中央版本伺服器。
流程與常用命令
一個通常較簡單的SVN工作流程如上圖所示。
-
加入一個新專案的開發時,從專案SVN地址獲取程式碼
svn co [svn_url]
-
把改變的檔案新增到版本庫中
svn add [file]
這裡可以使用svn add --a可以將所有變動都新增到版本庫中。
-
將改動的檔案提交到版本庫
svn commit -m [LogMessage]
-
更新到某個版本
svn update -r [version] [path]
svn update如果後面沒有目錄,預設將當前目錄以及子目錄下的所有檔案都更新到最新版本,如果沒有版本號,則更新到最新版本。
-
檢視檔案或者目錄狀態
svn status [path]
對應於每一個檔案有這幾個狀態:[?:不在svn的控制中;!:被刪除;M:內容被修改;C:發生衝突;A:預定加入到版本庫;K:被鎖定]。
-
刪除檔案
svn delete path -m “delete test fle“ svn delete [file] svn ci -m [message]
-
檢視日誌
svn log [path]
-
檢視檔案詳細資訊
svn info [path]
-
比較差異
svn diff [path]
這裡是將修改的檔案與基礎版本比較
-
恢復本地修改
svn revert [path]
恢復原始未改變的工作副本檔案(恢復大部份的本地修改),需要注意的是此命令不會恢復被刪除的目錄.
-
程式碼庫URL變更
svn switch [svn_url]
switch僅限於同一個repository下的目錄之間,switch並不關心切換的目標分支與源分支之間的關係。switch的代價很小,因此應儘量使用switch,而不是完整的check out。
-
解決衝突
svn resolved [PATH]
移除工作副本的目錄或檔案的“衝突”狀態。這裡只是移除衝突的相關檔案,然後讓 PATH 可以再次提交。
伺服器搭建
SVN是集中式版本控制系統,依賴於中央版本伺服器的。Windows下使用 ofollow,noindex" target="_blank">VisualSVN Server 即可;Linux下通過官網的原始碼包編譯安裝即可,不再詳述。
提示
- 應儘量避免的操作:
- 格式化程式碼,完全改變檔案的行號縮排等。
- 直接用另一個版本的檔案覆蓋當前檔案。
- 在Windows下更改檔名,但只改了檔名的大小寫。
- 保證提交的完整性: 零碎提交功能不完整的程式碼將引起協同開發的同事以及QA執行程式碼的困難,且不利於版本的回退等操作。
- 不應提交的檔案:以’.’開頭的檔案、目錄,例如.classpath、縮圖檔案、class檔案。
- 提交程式碼前務必先
svn update
。
2.2.2 Git
Git是一個分散式版本控制系統,相比起SVN的集中式版本控制,每個工作計算機都可以做為版本庫,可以不依賴遠端倉庫伺服器離線工作。此外,它的每一個版本庫都儲存了完整資訊,且是按照元資料的方式儲存的。
流程與常用命令
上圖所示,使用Git的流程一般如此,通常使用圖中的六個命令即可。
-
克隆專案的Git地址
git clone [repository url]
-
切換專案分支
git checkout [branchName]
-
增加或改動了一些檔案
git add [fileName]
這裡也可以使用 git add --a 新增所有變動到暫存區
-
提交檔案到本地庫
git commit -m [message]
可以使用 git commit -a 跳過git add步驟直接commit。
-
提交到遠端庫
git push origin master
-
從遠端庫更新程式碼併合並
git pull
此命令是 git fetch 和 git merge 的結合。
-
合併不相關的分支
git merge [third_repository]/master--allow-unrelated-histories
這樣即能夠合併兩個Git專案為一個且保留兩個專案的歷史記錄。其中,third_repository為要合併的專案的遠端倉庫名稱,需要先使用
git remote add third_repository [third_repository_url]
新增為當前專案的遠端倉庫,並git fetch third_repository
;--allow-unrelated-histories
引數則是允許合併不相關的歷史記錄。
此外,現在不管是使用Git官方的伺服器作為中央版本庫,還是使用Github這種,本地庫與遠端庫互動的一種方式就是通過公私鑰來進行的。生成公私鑰的命令如下:
ssh-keygen -t rsa -C [userName]
然後按照提示輸入相應資訊生成成功後,把公鑰放置到伺服器上即可。
更多的Git使用命令介紹請見附錄B。
伺服器搭建
雖然Git是分散式的,並不需要中央版本伺服器即可使用。但是當多人協作時,中央伺服器必不可少。
按照官方文件搭建一個Git伺服器是比較繁瑣的。面對這些複雜的操作步驟以及Git本身上手的門檻,很多人望而卻步,造成了之前Git並沒有那麼普及的狀況。而一切從Github橫空出世開始改變了,這個看似簡單的網站給很多人帶來了簡單極致方便的程式碼託管服務,同時也給全世界帶來了一股開源的風潮。很多初創公司或者小公司直接選擇使用Github建立自己的程式碼倉庫。
與此同時,許多Github的開源實現層出不窮,如今你只需要下載一個類似的實現部署到伺服器上,就能擁有自己的“Github”。
目前比較知名、用的較多的Github實現,有以下幾個:
- Gitlab : Ruby on rails實現,是目前最為出名也最為強大的github克隆實現,並且除了版本管理之外,集成了專案管理、持續整合等等很多功能。官網提供了很多作業系統下的一鍵安裝包。遺憾的是,Gitlab現在已經商業化,一些強大的功能只有在其收費版本中才能體驗到。
- Gitbucket :基於Scala編寫,極易安裝,扔一個war包到Tomcat就完成部署,完全可以和其他如Maven、Jenkins並存在一個JavaEE容器中。雖然功能沒有Gitlab那麼強大,但勝在簡單。更重要的是對於Java工程師來說是友好的,有不滿意的直接可以做二次開發。
工作流
由於Git是分散式版本控制系統,雖然帶來了很多優勢,但是也同時帶來了協同工作的複雜性。因此需要一套基於Git的工作流來規範整個協同流程。目前,比較流行的有以下兩種:
- Github workflow
- GitFlow
Github workflow
Github workflow是基於github一種常用的工作方式,比較簡單。流程分為以下幾步:
- 檢出新的分支。
- 在開發分支上完成開發工作,commmit並push到遠端庫分支中。
- 向主分支發起pull request
- 在pull request中發起討論和修改
- 將開發分支部署測試環境經測試無誤後merge回主分支。
這裡最為核心的就是基於pull request的協作方式,在類似於Github這種應用中,還可以基於此來進行code review、任務溝通等工作。
GitFlow
相比起Github workflow,GitFlow根據Git原來的命令和語義,做了一層語義抽象,多了很多新的定義,從原始碼管理角度對通常意義上的軟體開發活動進行了約束。流程如下圖所示:
其中,GitFlow中定義了兩大類分支:
- 主分支:主分支是所有開發活動的核心分支。所有的開發活動產生的輸出物最終都會反映到主分支的程式碼中。
- 輔助分支:用於組織解決特定問題的各種軟體開發活動的分支。輔助分支主要用於組織軟體新功能的並行開發、簡化新功能開發程式碼的跟蹤、輔助完成版本釋出工作以及對生產程式碼的缺陷進行緊急修復工作。這些分支與主分支不同,通常只會在有限的時間範圍記憶體在。也可以視作臨時分支。
其中,主分支又分為:
- master分支:存放的是隨時可供在生產環境中部署的程式碼。當開發活動告一段落,產生了一份新的可供部署的程式碼時,master分支上的程式碼會被更新。同時,每一次更新,都新增對應的版本號標籤(tag)。
- develop分支: 儲存當前最新開發成果的分支。通常這個分支上的程式碼也是可進行每日夜間釋出的程式碼。當develop分支上的程式碼已實現了軟體需求說明書中所有的功能,通過了所有的測試後,並且程式碼已經足夠穩定時,就可以將所有的開發成果合並回master分支了。
輔助分支又分為:
- feature分支:用於開發新功能時所使用的分支。從develop分支發起feature分支,程式碼必須合併回develop分支。此分支甚至可以僅僅儲存在開發者自己的程式碼庫裡而不提交。
- release分支:用於輔助版本釋出的分支。從develop分支派生。必須合併回develop分支和master分支。此分支為釋出新的產品版本而設計的,當develop上開發的功能基本成型可以釋出的時候就可以派生出release版本。在這個分支上的程式碼允許做小的缺陷修正、準備釋出版本所需的各項說明資訊,如:版本號、釋出時間、編譯時間等等。
- hotfix分支:用於修正生產程式碼中的缺陷的分支。從master分支派生且必須合併回master分支和develop分支。此分支一般是計劃外的分支,但最終輸出和release分支類似,都可以產生一個新的可供在生產環境部署的軟體版本。當生產環境遇到了異常情況或者需要緊急修復的bug時,就可以從master分支上指定的tag版本派生hotfix分支來組織程式碼的緊急修復工作。
為了簡化使用GitFlow模型時Git指令的複雜性,GitFlow的作者開發出了一套Git增強指令集。可以運行於Windows、Linux、Unix和Mac作業系統之下。地址見: https://github.com/nvie/gitflow 。
提示
- .gitignore中新增不需要版本管理的檔案,如:.DS_Store、logs、target、node_modules等。根據工程的型別不同這裡需要加入的檔案也不同。
- git push之前需要先git pull更新程式碼
- 如果想要忽略已經在版本庫裡的檔案/資料夾,即有一個版本庫裡的檔案你做了改動但並不想被提交版本庫中。這時可以使用命令
git update-index --assume-unchanged <file>
,之後可以通過git update-index -—no-assume-unchanged <file>
恢復跟蹤。通過git ls-files -v | grep -e "^[hsmrck]"
可以列出當前被忽略的、已經納入版本庫管理的檔案。 - 使用
git cherry-pick <commit id>
可以選擇某一個分支中的一個或幾個commit(s)來進行操作。 - 永遠不要所有人都在的公共開發分支上做rebase操作。一般情況下在臨時分支上是需要rebase主分支程式碼的,而merge則主要用在主分支上將臨時分支的程式碼合併過來,然後就可以刪除臨時分支了。
2.2.3 提交日誌
不論對於SVN還是Git來說,還有一點尤為重要的就是每次提交到程式碼庫時的日誌撰寫。很多人都認為日誌是很沒必要的,浪費時間還沒啥用,其實撰寫清晰規範的格式化日誌有助於追蹤版本修改、檢視歷史記錄等。SVN的預設配置是允許提交空日誌的,但Git卻是不允許日誌為空的。現在,市面上有很多類似的提交日誌規範,這裡推薦使用Angular規範,是目前使用最廣的寫法,比較合理和系統化,並且有配套的工具。
Angular規範的commit message包括三個部分:Header、Body 和 Footer,格式如下。
<type>(<scope>): <subject> // 空一行 <body> // 空一行 <footer>
-
type用於說明 commit 的類別,只允許使用下面7個標識。
- feat:新功能(feature)
- fix:修補bug
- docs:文件(documentation)
- style: 格式(不影響程式碼執行的變動)
- refactor:重構(即不是新增功能,也不是修改bug的程式碼變動)
- test:增加測試
- chore:構建過程或輔助工具的變動
-
scope用於說明 commit 影響的範圍,比如資料層、控制層、檢視層等等,視專案不同而不同。
-
subject是 commit目的的簡短描述,不超過50個字元。
-
body部分是對本次 commit 的詳細描述,可以分成多行。
-
footer部分只用於兩種情況: 不相容變動時,以BREAKING CHANGE開頭,後面是對變動的描述、以及變動理由和遷移方法;如果當前 commit 針對某個issue,那麼可以在 Footer 部分關閉這個issue。
-
還有一種特殊情況,如果當前commit用於撤銷以前的commit,則必須以revert:開頭,後面跟著被撤銷 Commit 的 Header。Body部分的格式是固定的,必須寫成This reverts commit .,其中的hash是被撤銷commit的 SHA 識別符號。
一個符合Angular規範的commit message例子如下:
如此規範commit message,可以帶來以下好處:
-
提供更多的歷史資訊,方便快速瀏覽。可以對程式碼的歷史記錄一目瞭然,能夠大體上知道每次提交都做了什麼。
git log [last tag] HEAD --pretty=format:%s
-
可以過濾某些commit(比如文件改動),便於快速查詢資訊。比如要檢視新增加的功能。
git log [last release] HEAD --grep feat
-
使用 conventional-changelog 可以直接從commit生成change log(釋出新版本時,用來說明與上一個版本差異的文件)。
conventional-changelog -p angular -i CHANGELOG.md -w
此外,可以使用 Commitizen 來撰寫符合規範的commit message。
雖然以上介紹使用Git來做示例的,但是對於SVN也是可以按照此套規範來進行的。