熟悉git分支的原理是掌握了git的精髓,由於git和咱們經常使用的源碼管理系統有很大的區別和優勢在分支上能夠體現出來,通常咱們經常使用的源碼管理系統分支都是須要建立新目錄,有全新的源碼copy,通常都須要建立一個源代碼目錄完整的副本。對應大項目來講很是的耗費時間和空間。git正式由於其優秀的分支模式能夠從源碼管理系統中脫穎而出。由於git的分支很是的輕量級,他的操做機會瞬間完成,在不一樣的分支切換也很是快速。與其餘版本相比,git更加推崇使用分支管理。分支是一個git很是強大和高效的工具。熟悉使用能夠大大的提升工做效率和開發效率。git
一、分支
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
二、 建立分支
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 不管項目大小不一樣,他的特性與項目的複雜度無關,永遠能夠在幾毫秒內建立分支和切換分支。同時每次回記錄和指向父對象。這位未來要合併對象對參照標準基礎。
三、分支合併建立實例模型
分支建立和合並的例子,實際工做中工做流程
一、開發一個項目
二、爲顯示某個新的需求,建立一個新的分支。
三、在這個分支的開發
四、若是此時項目有bug須要當即修復須要在原先發布到生產環境的版本的分支。
五、爲此次緊急bugfix 建立一個新的分支,並在其中修復問題。
六、bug修復沒問題後,會到開發服務器生產分支,和修補分支合併,而後推送到生產服務器上。
七、全部一切都完成後,切換到以前新需求分支,繼續工做。
接着剛剛寫的範例,咱們在主分支發佈到生產環境後,已經推送到中心服務器,當前的狀態以下圖:
一、如今有新的需求須要開發,因此在當地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