git 分支管理策略 與 物理實現 --author by阮一峰 & 小魚


 

-------------------------下面是阮一峰博士的git branch 邏輯結構圖示----------------------------------------------

如果你嚴肅對待編程,就必定會使用”版本管理系統”(Version Control System)。

眼下最流行的”版本管理系統”,非Git莫屬。


阮一峰:Git分支管理策略

相比同類軟件,git有很多優點。其中很顯著的一點,就是版本的分支(branch)和合並(merge)十分方便。有些傳統的版本管理軟件,分支操作實際上會生成一份現有代碼的物理拷貝,而Git只生成一個指向當前版本(又稱”快照”)的指針,因此非常快捷易用。

但是,太方便了也會產生副作用。如果你不加注意,很可能會留下一個枝節蔓生、四處開放的版本庫,到處都是分支,完全看不出主干發展的脈絡。

 阮一峰:Git分支管理策略

Vincent Driessen提出了一個分支管理的策略中文簡譯版),我覺得非常值得借鑒。它可以使得版本庫的演進保持簡潔,主干清晰,各個分支各司其職、井井有條。理論上,這些策略對所有的版本管理系統都適用,Git只是用來舉例而已。如果你不熟悉Git,跳過舉例部分就可以了。

一、主分支Master

首先,代碼庫應該有一個、且僅有一個主分支。所有提供給用戶使用的正式版本,都在這個主分支上發布。

阮一峰:Git分支管理策略

Git主分支的名字,默認叫做Master。它是自動建立的,版本庫初始化以后,默認就是在主分支在進行開發。

二、開發分支Develop

主分支只用來分布重大版本,日常開發應該在另一條分支上完成。我們把開發用的分支,叫做Develop。

阮一峰:Git分支管理策略

這個分支可以用來生成代碼的最新隔夜版本(nightly)。如果想正式對外發布,就在Master分支上,對Develop分支進行”合並”(merge)。

Git創建Develop分支的命令:

git checkout -b develop master

將Develop分支發布到Master分支的命令:

# 切換到Master分支

git checkout master

# 對Develop分支進行合並

git merge –no–ff develop

這里稍微解釋一下,上一條命令的–no–ff參數是什么意思。默認情況下,Git執行”快進式合並”(fast-farward merge),會直接將Master分支指向Develop分支。

阮一峰:Git分支管理策略

使用–no–ff參數后,會執行正常合並,在Master分支上生成一個新節點。為了保證版本演進的清晰,我們希望采用這種做法。關於合並的更多解釋,請參考Benjamin Sandofsky的《Understanding the Git Workflow》

阮一峰:Git分支管理策略

三、臨時性分支

前面講到版本庫的兩條主要分支:Master和Develop。前者用於正式發布,后者用於日常開發。其實,常設分支只需要這兩條就夠了,不需要其他了。

但是,除了常設分支以外,還有一些臨時性分支,用於應對一些特定目的的版本開發。臨時性分支主要有三種:

* 功能(feature)分支

* 預發布(release)分支

* 修補bug(fixbug)分支

這三種分支都屬於臨時性需要,使用完以后,應該刪除,使得代碼庫的常設分支始終只有Master和Develop。

四、 功能分支

接下來,一個個來看這三種”臨時性分支”。

第一種是功能分支,它是為了開發某種特定功能,從Develop分支上面分出來的。開發完成后,要再並入Develop。

阮一峰:Git分支管理策略

功能分支的名字,可以采用feature-*的形式命名。

創建一個功能分支:

git checkout -b feature-x develop

開發完成后,將功能分支合並到develop分支:

git checkout develop

git merge –no-ff feature-x

刪除feature分支:

git branch -d feature-x

五、預發布分支

第二種是預發布分支,它是指發布正式版本之前(即合並到Master分支之前),我們可能需要有一個預發布的版本進行測試。

預發布分支是從Develop分支上面分出來的,預發布結束以后,必須合並進Develop和Master分支。它的命名,可以采用release-*的形式。

創建一個預發布分支:

git checkout -b release-1.2 develop

確認沒有問題后,合並到master分支:

git checkout master

git merge –no-ff release-1.2

# 對合並生成的新節點,做一個標簽

git tag -a 1.2

再合並到develop分支:

git checkout develop

git merge –no-ff release-1.2

最后,刪除預發布分支:

git branch -d release-1.2

六、修補bug分支

最后一種是修補bug分支。軟件正式發布以后,難免會出現bug。這時就需要創建一個分支,進行bug修補。

修補bug分支是從Master分支上面分出來的。修補結束以后,再合並進Master和Develop分支。它的命名,可以采用fixbug-*的形式。

阮一峰:Git分支管理策略

創建一個修補bug分支:

git checkout -b fixbug-0.1 master

修補結束后,合並到master分支:

git checkout master

git merge –no-ff fixbug-0.1

git tag -a 0.1.1

再合並到develop分支:

git checkout develop

git merge –no-ff fixbug-0.1

最后,刪除”修補bug分支”:

git branch -d fixbug-0.1

-------------------------下面是小魚同志的git branch 帶hash結構圖示---------------------------------------------

熟悉git分支的原理是掌握了git的精髓,因為git和我們常用的源碼管理系統有很大的區別和優點在分支上可以體現出來,一般我們常用的源碼管理系統分支都是需要創建新目錄,有全新的源碼copy,一般都需要創建一個源代碼目錄完整的副本。對應大項目來說非常的耗費時間和空間。git正式因為其優秀的分支模式可以從源碼管理系統中脫穎而出。因為git的分支非常的輕量級,他的操作機會瞬間完成,在不同的分支切換也非常快速。與其他版本相比,git更加推崇使用分支管理。分支是一個git非常強大和高效的工具。熟悉使用可以大大的提高工作效率和開發效率。

 
1、分支
 
git分支是如果存儲數據,我們通過git數據存儲的方式可以知道,存儲一些列文件快照的方式。而一般常用的源碼管理軟件保存的基於文件版本差異的變化保存數據。
在git提交中,會保存一個提交(commit)對象,該對象包含一個指向暫存內容快照的指針,包括本次提交的作業的相關附屬信息,包含零個或多個指向該提交對象的父對象指針,首次提交時沒有直接的祖先,普通提交有一個祖先,由兩個或多個分支合並產生的提交有多個祖先。
假如我們的工作目錄有三個文件,准備將他們暫存后提交,暫存操作會對每一個文件計算校驗和(Sha1哈希字符串)。然后把當前版本文件快照保存到git倉庫中。git使用blob類型對象存儲這些快照。並將校驗和加入暫存區。
當使用git commit 新建一個提交對象前,git會計算每一個子目錄或者項目的根目錄的校驗和,然后在git倉庫中將這些目錄保存為(tree)樹對象。之后git創建的提交對象,除了包含相關提交信息之外,還包含指向這個樹對象(項目根目錄)的指針。如此他就可以在將來需要的時候,重現此次快照的內容。
 
$ git add test1.txt test2.txt test3.txt
 
$ git commit -m " this is the firt commit"
[master (root-commit) 680e70a]  this is the firt commit
 3 files changed, 3 insertions(+), 0 deletions(-)
 create mode 100644 test1.txt
 create mode 100644 test2.txt
 create mode 100644 test3.txt
 
$ find .git/objects/ -type f
.git/objects/15/f2ed7f3d000ee95bf0e343f40eb892f622174d
.git/objects/30/758feb48e8bdba6ace1ecadf7bf2f89b94c115
.git/objects/5b/3c0c1c42386a00a8f81854b84628305f7082c7
.git/objects/68/0e70ac4e4376992c7d90905b8351badd233850
.git/objects/97/22bcfd0dcf84bbc82ce6095055010c011f91e6
 
以上是我剛剛創建的一個空的倉庫,添加test1.txt test2.txt test3.txt 三個文件。添加到暫存區后,在。git/objects/ 對象中會新增加三個文件快照blog 對象。暫存.git/index 會指向對應的對象。首次提交到倉庫后,倉庫中在.git/objecs/目錄中有5個對象。比在暫存區中增加了兩個對象,是因為一個是根目錄樹(tree)對象。和一個提交commit 的對象。根目錄tree對象指向三個文件blob的索引對象和保存文件內容的名稱。而commit 對象會指向樹對象,同時保存對象說明及相關的附屬信息。倉庫對象之間的關系圖可以如下圖。
 
     
 
 
當連續多次提及后,每次創建一個提交commit 對象會指向上一個提交的父對象。已經提交了三個對象,可以參照如下圖:
 
$ git log --oneline
4d38d40 the third commit
66a1a2e the secomd commit
680e70a  this is the firt commit
 
 
2、 創建分支
 
git bran branchname  這個命令會在當前活動分支的基礎上創建一個新的分支。其實是在當前活動分支對象commit 新添加一個指向該對象的分支指針,如我們在現master 分支的基礎上創建一個testing 分支。git branch testing 創建分支如下關系圖:
 
git 當前活動分支,即工作分支是是哪個呢,可以通過命令git branch 查看,*號標示的就是當前活動分支。有一個head 特別指針保存着當前活動分支。具體物理地址是保存在.git/head 文件中,他指向的是一個分支名稱,分支明確refs/heads/master  指向的是當前分支指向的commit 對象。他是指向當前工作本地活動分支的指針。看如下命令,我么可以通過git checkout branchname 轉換分支。如git checkout testing 后就指向了testing 分支。看如下圖轉變過程。
 
$ git branch
* master
  testing
  
$ ls .git/refs/heads
master  testing
 
$ cat .git/refs/heads/master
4d38d402b3873d95c03cb3e1d2c65bf9aa3b5894
 
$ cat .git/refs/heads/testing
4d38d402b3873d95c03cb3e1d2c65bf9aa3b5894
 
$ cat .git/head
ref: refs/heads/master
 
$ git checkout testing
Switched to branch 'testing'
 
$ cat .git/head
ref: refs/heads/testing
  
 
 
當前活動分支是在testing 分支,我們在testing 分支做修改后提交。這是testing 分支指向最近一個提交。master 分支是指向當前提交的父提交commit 對象。
現在我們切換到master 分支,並把工作目錄文件換成master分支指向的快照內容,現在所做的修改時在老的版本上做修改,在master 分支上做修改。修改不同余testing 分支另一條分支提交。如下圖結果。
 
$ git commit -a -m "testing branch change"
[testing 0c8f2de] testing branch change
 1 files changed, 1 insertions(+), 0 deletions(-)
 
$ git checkout master
Switched to branch 'master'
 
$ git commit -a -m "the master branch commit "
[master 1d37642] the master branch commit
 1 files changed, 2 insertions(+), 0 deletions(-)
 
 
 
如現在我們需要把testing 分支和 master 分支合並。合並操作命令git merge branch name ,查看分支日志。
 
$ git merge testing
Merge made by the 'recursive' strategy.
 test2.txt |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
 
$ git log --oneline --graph
*   ac631f6 Merge branch 'testing'
|\
| * 0c8f2de testing branch change
* | 1d37642 the master branch commit
|/
* 4d38d40 the third commit
* 66a1a2e the secomd commit
* 680e70a  this is the firt commit
 
 
 
由於分支實際上僅僅是一個包含所指對象校驗和(40個字符串長度的sha1字符串)文件。所以創建和銷毀一個分支會非常的容易和廉價。實際上新建一個分支就是向一個文件寫入41個字節(一個換行符)。指向一某個提交點。
正式由於這個特性和其它版本形成了特別鮮明的對比。原始的源碼管理系統管理分支一般都采用備份所有項目文件到特定的目錄,所以根據項目文件數量和大小不同,消耗的時間隨着項目的大小時間也不同。而gti 無論項目大小不同,他的特性與項目的復雜度無關,永遠可以在幾毫秒內創建分支和切換分支。同時每次回記錄和指向父對象。這位將來要合並對象對參照標准基礎。
 
 
 
3、分支合並創建實例模型
 
    分支創建和合並的例子,實際工作中工作流程
    1、開發一個項目
    2、為顯示某個新的需求,創建一個新的分支。
    3、在這個分支的開發
    4、如果此時項目有bug需要立即修復需要在原先發布到生產環境的版本的分支。
    5、為這次緊急bugfix 創建一個新的分支,並在其中修復問題。
    6、bug修復沒問題后,會到開發服務器生產分支,和修補分支合並,然后推送到生產服務器上。
    7、所有一切都完成后,切換到之前新需求分支,繼續工作。
 
   接着剛剛寫的范例,我們在主分支發布到生產環境后,已經推送到中心服務器,當前的狀態如下圖:
 
     
 
   1、現在有新的需求需要開發,所以在當地master 分支上創建一個新的開發分支。然后再開發分支上做開發。用到的命令式git checkout -b dev ,相當於git branch dev ,git checkout dev 兩條命令。當前工作活動分支是指向dev開發分支,我們在新的開發分支上做修改提交后。如下圖:
 
$ git checkout -b dev
Switched to a new branch 'dev'
 
$ git commit -a -m "dev branch revise"
[dev 46324fb] dev branch revise
 1 files changed, 1 insertions(+), 0 deletions(-)
 
現在正式的生產環境發現有bug 需要修復,現在我們不需要在創建和發布到修復服務器上花大力氣做修改,現在需要做的只需要切回到master分支,然后再master 的分支上創建bugfix分支。在分支切回過程有一點需要注意的是。在暫存區和工作目錄是否還沒有提交的修改,如果他和你即將檢出的分支產生沖突從而阻止切回分支。在切換分支前需要保持一個干凈的工作區域。
 
 
 
$ git checkout master
Switched to branch 'master'
 
$ git checkout -b bugfix
Switched to a new branch 'bugfix'
 
$ git commit -a -m "bug fix branch commit"
[bugfix 7599941] bug fix branch commit
 1 files changed, 2 insertions(+), 0 deletions(-)
 
 
 
 
 
在bugfix 分支上測試成功后,發現已經ok了,需要把master分支合並進來,然后推送到中心倉庫服務器。先切換成到master 分支,然后git merge 合並bugfix 分支做合並,你會發現fast-forfward 合並,git只需要把master分支指針直接右移,如果順着一個分支走下去可以到達另一個分支,那么在git 合並的時候,只會簡單的把指針右移,因為這種單線歷史分支不存在解決沖突和分歧。所以這種合並過程成為快進(fast forward). 當master 分支和bugfix 分支合並后,見下圖,當前的bugfix 分支和master 分支會指向一個相同的提交對象。如果bugfix 分支已經沒有存在的必要了,可以使用git branch -d branch 進行刪除。
 
$ git merge bugfix
Updating ac631f6..7599941
Fast-forward
 test1.txt |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)
 
$ git log --graph --oneline
* 7599941 bug fix branch commit
*   ac631f6 Merge branch 'testing'
|\
| * 0c8f2de testing branch change
* | 1d37642 the master branch commit
|/
* 4d38d40 the third commit
* 66a1a2e the secomd commit
* 680e70a  this is the firt commit
 
$ git branch -d bugfix
Deleted branch bugfix (was 7599941).
 
 
 
 
 
以上bug修復完成后,可以切換到dev 分支,繼續需求開發,可以將master 分支合並到dev 分支,或者等dev 版本開發完成后,再將dev分支更新到並入master.現在分支不同於hotfix 合並的方式,因為這次從歷史ac631 處有分支同父對象,dev 和master 分支在進行合並的時候,會找到相同的祖先分支。ac631 和最新分支末端 46324fb 和 22ef87 進行一個三方合並運算,合並提交三個對象,git沒有簡單的把分支指正右移,而是三方合並結果做一個新的快照創建一個提交commit 對象1421fd5 。這個提交對象有兩個祖先,有一點需要注意的是,git 可以決定哪個祖先是最佳合並的基礎,這和CVS 和 SVN 不同,他們需要開發者手工合並基礎,所以此特性合並操作會讓git操作會比其它系統要簡單許多。
 
 
$ git merge dev
Merge made by the 'recursive' strategy.
 
$ git log --oneline --graph
*   1421fd5 Merge branch 'dev'
|\
| * 46324fb dev branch revise
* | 22ef787  before dev merege commit
* | 7599941 bug fix branch commit
|/
*   ac631f6 Merge branch 'testing'
|\
| * 0c8f2de testing branch change
* | 1d37642 the master branch commit
|/
* 4d38d40 the third commit
 
 
 
 
 
 
 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM