鍍金池/ 教程/ Java/ Git 分支
腳本 GitHub
Git 鉤子
分支與合并
撤消操作
5.4 總結(jié)
Git 對象
變基
Bash 中的 Git
補丁
Git 引用
項目分享與更新
總結(jié)
GitWeb
Visual Studio 中的 Git
外部系統(tǒng)
替換
Zsh 中的 Git
Git 命令
打包
使用強制策略的一個例子
總結(jié)
簽署工作
分支開發(fā)工作流
遠(yuǎn)程分支
總結(jié)
安裝 Git
Powershell 中的 Git
快照基礎(chǔ)
管理組織
Git 與其他系統(tǒng)
在服務(wù)器上搭建 Git
GitHub
Git 別名
憑證存儲
維護與數(shù)據(jù)恢復(fù)
包文件
子模塊
將 Git 嵌入你的應(yīng)用
獲取幫助
對項目做出貢獻
Git 分支
使用 Git 調(diào)試
Libgit2
Git 基礎(chǔ)
郵件
Git 內(nèi)部原理
維護項目
調(diào)試
向一個項目貢獻
總結(jié)
維護項目
命令行
分布式 Git
總結(jié)
JGit
儲藏與清理
管理
獲取與創(chuàng)建項目
總結(jié)
Rerere
Git 簡史
Smart HTTP
總結(jié)
Eclipse 中的 Git
總結(jié)
引用規(guī)格
傳輸協(xié)議
查看提交歷史
自定義 Git
底層命令
Git 守護進程
搜索
Git 基礎(chǔ)
Git 工具
關(guān)于版本控制
環(huán)境變量
其它環(huán)境中的 Git
高級合并
服務(wù)器上的 Git
第三方托管的選擇
遷移到 Git
遠(yuǎn)程倉庫的使用
GitLab
生成 SSH 公鑰
分支的新建與合并
配置服務(wù)器
交互式暫存
重寫歷史
重置揭密
Git 屬性
總結(jié)
初次運行 Git 前的配置
記錄每次更新到倉庫
總結(jié)
分支管理
打標(biāo)簽
檢查與比較

Git 分支

幾乎所有的版本控制系統(tǒng)都以某種形式支持分支。 使用分支意味著你可以把你的工作從開發(fā)主線上分離開來,以免影響開發(fā)主線。 在很多版本控制系統(tǒng)中,這是一個略微低效的過程——常常需要完全創(chuàng)建一個源代碼目錄的副本。對于大項目來說,這樣的過程會耗費很多時間。

有人把 Git 的分支模型稱為它的“必殺技特性”,也正因為這一特性,使得 Git 從眾多版本控制系統(tǒng)中脫穎而出。 為何 Git 的分支模型如此出眾呢? Git 處理分支的方式可謂是難以置信的輕量,創(chuàng)建新分支這一操作幾乎能在瞬間完成,并且在不同分支之間的切換操作也是一樣便捷。 與許多其它版本控制系統(tǒng)不同,Git 鼓勵在工作流程中頻繁地使用分支與合并,哪怕一天之內(nèi)進行許多次。 理解和精通這一特性,你便會意識到 Git 是如此的強大而又獨特,并且從此真正改變你的開發(fā)方式。

分支簡介

為了真正理解 Git 處理分支的方式,我們需要回顧一下 Git 是如何保存數(shù)據(jù)的。

或許你還記得 起步 的內(nèi)容,Git 保存的不是文件的變化或者差異,而是一系列不同時刻的文件快照。

在進行提交操作時,Git 會保存一個提交對象(commit object)。知道了 Git 保存數(shù)據(jù)的方式,我們可以很自然的想到——該提交對象會包含一個指向暫存內(nèi)容快照的指針。 但不僅僅是這樣,該提交對象還包含了作者的姓名和郵箱、提交時輸入的信息以及指向它的父對象的指針。首次提交產(chǎn)生的提交對象沒有父對象,普通提交操作產(chǎn)生的提交對象有一個父對象,而由多個分支合并產(chǎn)生的提交對象有多個父對象,

為了說得更加形象,我們假設(shè)現(xiàn)在有一個工作目錄,里面包含了三個將要被暫存和提交的文件。 暫存操作會為每一個文件計算校驗和(使用我們在 起步 中提到的 SHA-1 哈希算法),然后會把當(dāng)前版本的文件快照保存到 Git 倉庫中(Git 使用 blob 對象來保存它們),最終將校驗和加入到暫存區(qū)域等待提交:

$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'

當(dāng)使用 git commit 進行提交操作時,Git 會先計算每一個子目錄(本例中只有項目根目錄)的校驗和,然后在 Git 倉庫中這些校驗和保存為樹對象。 隨后,Git 便會創(chuàng)建一個提交對象,它除了包含上面提到的那些信息外,還包含指向這個樹對象(項目根目錄)的指針。如此一來,Git 就可以在需要的時候重現(xiàn)此次保存的快照。

現(xiàn)在,Git 倉庫中有五個對象:三個 blob 對象(保存著文件快照)、一個樹對象(記錄著目錄結(jié)構(gòu)和 blob 對象索引)以及一個提交對象(包含著指向前述樹對象的指針和所有提交信息)。

http://wiki.jikexueyuan.com/project/pro-git-two/images/13.png" alt="" />

Figure 3-1. 首次提交對象及其樹結(jié)構(gòu) 做些修改后再次提交,那么這次產(chǎn)生的提交對象會包含一個指向上次提交對象(父對象)的指針。

http://wiki.jikexueyuan.com/project/pro-git-two/images/14.png" alt="" />

Figure 3-2. 提交對象及其父對象 Git 的分支,其實本質(zhì)上僅僅是指向提交對象的可變指針。 Git 的默認(rèn)分支名字是 master。 在多次提交操作之后,你其實已經(jīng)有一個指向最后那個提交對象的 master 分支。 它會在每次的提交操作中自動向前移動。

Note

Git 的 “master” 分支并不是一個特殊分支。 它就跟其它分支完全沒有區(qū)別。 之所以幾乎每一個倉庫都有 master 分支,是因為 git init 命令默認(rèn)創(chuàng)建它,并且大多數(shù)人都懶得去改動它。

http://wiki.jikexueyuan.com/project/pro-git-two/images/15.png" alt="" />

Figure 3-3. 分支及其提交歷史

分支創(chuàng)建

Git 是怎么創(chuàng)建新分支的呢? 很簡單,它只是為你創(chuàng)建了一個可以移動的新的指針。 比如,創(chuàng)建一個 testing 分支, 你需要使用 git branch 命令:

$ git branch testing

這會在當(dāng)前所在的提交對象上創(chuàng)建一個指針。

http://wiki.jikexueyuan.com/project/pro-git-two/images/16.png" alt="" />

Figure 3-4. 兩個指向相同提交歷史的分支 那么,Git 又是怎么知道當(dāng)前在哪一個分支上呢? 也很簡單,它有一個名為 HEAD 的特殊指針。 請注意它和許多其它版本控制系統(tǒng)(如 Subversion 或 CVS)里的 HEAD 概念完全不同。 在 Git 中,它是一個指針,指向當(dāng)前所在的本地分支(譯注:將 HEAD 想象為當(dāng)前分支的別名)。 在本例中,你仍然在 master 分支上。 因為 git branch 命令僅僅 創(chuàng)建 一個新分支,并不會自動切換到新分支中去。

http://wiki.jikexueyuan.com/project/pro-git-two/images/17.png" alt="" />

Figure 3-5. HEAD 指向當(dāng)前所在的分支 你可以簡單地使用 git log 命令查看各個分支當(dāng)前所指的對象。 提供這一功能的參數(shù)是 --decorate。

$ git log --oneline --decorate
f30ab (HEAD, master, testing) add feature #32 - ability to add new
34ac2 fixed bug #1328 - stack overflow under certain conditions
98ca9 initial commit of my project

正如你所見,當(dāng)前 “master” 和 “testing” 分支均指向校驗和以 f30ab 開頭的提交對象。

分支切換

要切換到一個已存在的分支,你需要使用 git checkout 命令。 我們現(xiàn)在切換到新創(chuàng)建的 testing 分支去:

$ git checkout testing

這樣 HEAD 就指向 testing 分支了。

http://wiki.jikexueyuan.com/project/pro-git-two/images/18.png" alt="" />

Figure 3-6. HEAD 指向當(dāng)前所在的分支 那么,這樣的實現(xiàn)方式會給我們帶來什么好處呢? 現(xiàn)在不妨再提交一次:

$ vim test.rb
$ git commit -a -m 'made a change'

http://wiki.jikexueyuan.com/project/pro-git-two/images/19.png" alt="" />

Figure 3-7. HEAD 分支隨著提交操作自動向前移動 如圖所示,你的 testing 分支向前移動了,但是 master 分支卻沒有,它仍然指向運行 git checkout 時所指的對象。 這就有意思了,現(xiàn)在我們切換回 master 分支看看:

$ git checkout master

http://wiki.jikexueyuan.com/project/pro-git-two/images/20.png" alt="" />

Figure 3-8. 檢出時 HEAD 隨之移動 這條命令做了兩件事。 一是使 HEAD 指回 master 分支,二是將工作目錄恢復(fù)成 master 分支所指向的快照內(nèi)容。 也就是說,你現(xiàn)在做修改的話,項目將始于一個較舊的版本。 本質(zhì)上來講,這就是忽略 testing 分支所做的修改,以便于向另一個方向進行開發(fā)。

Note

分支切換會改變你工作目錄中的文件 在切換分支時,一定要注意你工作目錄里的文件會被改變。 如果是切換到一個較舊的分支,你的工作目錄會恢復(fù)到該分支最后一次提交時的樣子。 如果 Git 不能干凈利落地完成這個任務(wù),它將禁止切換分支。

我們不妨再稍微做些修改并提交:

$ vim test.rb
$ git commit -a -m 'made other changes'

現(xiàn)在,這個項目的提交歷史已經(jīng)產(chǎn)生了分叉(參見 Figure 3-9)。 因為剛才你創(chuàng)建了一個新分支,并切換過去進行了一些工作,隨后又切換回 master 分支進行了另外一些工作。 上述兩次改動針對的是不同分支:你可以在不同分支間不斷地來回切換和工作,并在時機成熟時將它們合并起來。 而所有這些工作,你需要的命令只有 branch、checkout 和 commit。

http://wiki.jikexueyuan.com/project/pro-git-two/images/21.png" alt="" />

Figure 3-9. 項目分叉歷史 你可以簡單地使用 git log 命令查看分叉歷史。 運行 git log --oneline --decorate --graph --all ,它會輸出你的提交歷史、各個分支的指向以及項目的分支分叉情況。

$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project

由于 Git 的分支實質(zhì)上僅是包含所指對象校驗和(長度為 40 的 SHA-1 值字符串)的文件,所以它的創(chuàng)建和銷毀都異常高效。 創(chuàng)建一個新分支就像是往一個文件中寫入 41 個字節(jié)(40 個字符和 1 個換行符),如此的簡單能不快嗎?

這與過去大多數(shù)版本控制系統(tǒng)形成了鮮明的對比,它們在創(chuàng)建分支時,將所有的項目文件都復(fù)制一遍,并保存到一個特定的目錄。 完成這樣繁瑣的過程通常需要好幾秒鐘,有時甚至需要好幾分鐘。所需時間的長短,完全取決于項目的規(guī)模。而在 Git 中,任何規(guī)模的項目都能在瞬間創(chuàng)建新分支。 同時,由于每次提交都會記錄父對象,所以尋找恰當(dāng)?shù)暮喜⒒A(chǔ)(譯注:即共同祖先)也是同樣的簡單和高效。 這些高效的特性使得 Git 鼓勵開發(fā)人員頻繁地創(chuàng)建和使用分支。

接下來,讓我們看看為什么你應(yīng)該這么做?

上一篇:使用 Git 調(diào)試下一篇:總結(jié)