# 分支簡介
為了真正理解 Git 處理分支的方式,我們需要回顧一下 Git 是如何保存數據的。
或許你還記得 [Chapter?1](#) 的內容,Git 保存的不是文件的變化或者差異,而是一系列不同時刻的文件快照。
在進行提交操作時,Git 會保存一個提交對象(commit object)。知道了 Git 保存數據的方式,我們可以很自然的想到——該提交對象會包含一個指向暫存內容快照的指針。但不僅僅是這樣,該提交對象還包含了作者的姓名和郵箱、提交時輸入的信息以及指向它的父對象的指針。首次提交產生的提交對象沒有父對象,普通提交操作產生的提交對象有一個父對象,而由多個分支合并產生的提交對象有多個父對象,
為了說得更加形象,我們假設現在有一個工作目錄,里面包含了三個將要被暫存和提交的文件。暫存操作會為每一個文件計算校驗和(使用我們在 [Chapter?1](#) 中提到的 SHA-1 哈希算法),然后會把當前版本的文件快照保存到 Git 倉庫中(Git 使用 blob 對象來保存它們),最終將校驗和加入到暫存區域等待提交:
~~~
$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'
~~~
當使用 `git commit` 進行提交操作時,Git 會先計算每一個子目錄(本例中只有項目根目錄)的校驗和,然后在 Git 倉庫中這些校驗和保存為樹對象。隨后,Git 便會創建一個提交對象,它除了包含上面提到的那些信息外,還包含指向這個樹對象(項目根目錄)的指針。如此一來,Git 就可以在需要的時候重現此次保存的快照。
現在,Git 倉庫中有五個對象:三個 blob 對象(保存著文件快照)、一個樹對象(記錄著目錄結構和 blob 對象索引)以及一個提交對象(包含著指向前述樹對象的指針和所有提交信息)。

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

Figure 3-2. 提交對象及其父對象
Git 的分支,其實本質上僅僅是指向提交對象的可變指針。Git 的默認分支名字是 `master`。在多次提交操作之后,你其實已經有一個指向最后那個提交對象的 `master` 分支。它會在每次的提交操作中自動向前移動。
Git 的 “master” 分支并不是一個特殊分支。它就跟其它分支完全沒有區別。之所以幾乎每一個倉庫都有 master 分支,是因為 `git init` 命令默認創建它,并且大多數人都懶得去改動它。

Figure 3-3. 分支及其提交歷史
## 分支創建
Git 是怎么創建新分支的呢?很簡單,它只是為你創建了一個可以移動的新的指針。比如,創建一個 testing 分支,你需要使用 `git branch` 命令:
~~~
$ git branch testing
~~~
這會在當前所在的提交對象上創建一個指針。

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

Figure 3-5. HEAD 指向當前所在的分支
你可以簡單地使用 `git log` 命令查看各個分支當前所指的對象。提供這一功能的參數是 `--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
~~~
正如你所見,當前 “master” 和 “testing” 分支均指向校驗和以 `f30ab` 開頭的提交對象。
## 分支切換
要切換到一個已存在的分支,你需要使用 `git checkout` 命令。我們現在切換到新創建的 `testing` 分支去:
~~~
$ git checkout testing
~~~
這樣 `HEAD` 就指向 `testing` 分支了。

Figure 3-6. HEAD 指向當前所在的分支
那么,這樣的實現方式會給我們帶來什么好處呢?現在不妨再提交一次:
~~~
$ vim test.rb
$ git commit -a -m 'made a change'
~~~

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

Figure 3-8. 檢出時 HEAD 隨之移動
這條命令做了兩件事。一是使 HEAD 指回 `master` 分支,二是將工作目錄恢復成 `master` 分支所指向的快照內容。也就是說,你現在做修改的話,項目將始于一個較舊的版本。本質上來講,這就是忽略 `testing` 分支所做的修改,以便于向另一個方向進行開發。
>[info] ### 分支切換會改變你工作目錄中的文件
> 在切換分支時,一定要注意你工作目錄里的文件會被改變。如果是切換到一個較舊的分支,你的工作目錄會恢復到該分支最后一次提交時的樣子。如果 Git 不能干凈利落地完成這個任務,它將禁止切換分支。
我們不妨再稍微做些修改并提交:
~~~
$ vim test.rb
$ git commit -a -m 'made other changes'
~~~
現在,這個項目的提交歷史已經產生了分叉(參見 [Figure?3-9](#))。因為剛才你創建了一個新分支,并切換過去進行了一些工作,隨后又切換回 master 分支進行了另外一些工作。上述兩次改動針對的是不同分支:你可以在不同分支間不斷地來回切換和工作,并在時機成熟時將它們合并起來。而所有這些工作,你需要的命令只有 `branch`、`checkout` 和 `commit`。

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 的分支實質上僅是包含所指對象校驗和(長度為 40 的 SHA-1 值字符串)的文件,所以它的創建和銷毀都異常高效。創建一個新分支就像是往一個文件中寫入 41 個字節(40 個字符和 1 個換行符),如此的簡單能不快嗎?
這與過去大多數版本控制系統形成了鮮明的對比,它們在創建分支時,將所有的項目文件都復制一遍,并保存到一個特定的目錄。完成這樣繁瑣的過程通常需要好幾秒鐘,有時甚至需要好幾分鐘。所需時間的長短,完全取決于項目的規模。而在 Git 中,任何規模的項目都能在瞬間創建新分支。同時,由于每次提交都會記錄父對象,所以尋找恰當的合并基礎(譯注:即共同祖先)也是同樣的簡單和高效。這些高效的特性使得 Git 鼓勵開發人員頻繁地創建和使用分支。
接下來,讓我們看看為什么你應該這么做?
- 前言
- Scott Chacon 序
- Ben Straub 序
- 獻辭
- 貢獻者
- 引言
- 1. 起步
- 1.1 關于版本控制
- 1.2 Git 簡史
- 1.3 Git 基礎
- 1.4 命令行
- 1.5 安裝 Git
- 1.6 初次運行 Git 前的配置
- 1.7 獲取幫助
- 1.8 總結
- 2. Git 基礎
- 2.1 獲取 Git 倉庫
- 2.2 記錄每次更新到倉庫
- 2.3 查看提交歷史
- 2.4 撤消操作
- 2.5 遠程倉庫的使用
- 2.6 打標簽
- 2.7 Git 別名
- 2.8 總結
- 3. Git 分支
- 3.1 分支簡介
- 3.2 分支的新建與合并
- 3.3 分支管理
- 3.4 分支開發工作流
- 3.5 遠程分支
- 3.6 變基
- 3.7 總結
- 4. 服務器上的 Git
- 4.1 協議
- 4.2 在服務器上搭建 Git
- 4.3 生成 SSH 公鑰
- 4.4 配置服務器
- 4.5 Git 守護進程
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管的選擇
- 4.10 總結
- 5. 分布式 Git
- 5.1 分布式工作流程
- 5.2 向一個項目貢獻
- 5.3 維護項目
- 5.4 總結
- 6. GitHub
- 6.1 賬戶的創建和配置
- 6.2 對項目做出貢獻
- 6.3 維護項目
- 6.4 管理組織
- 6.5 腳本 GitHub
- 6.6 總結
- 7. Git 工具
- 7.1 選擇修訂版本
- 7.2 交互式暫存
- 7.3 儲藏與清理
- 7.4 簽署工作
- 7.5 搜索
- 7.6 重寫歷史
- 7.7 重置揭密
- 7.8 高級合并
- 7.9 Rerere
- 7.10 使用 Git 調試
- 7.11 子模塊
- 7.12 打包
- 7.13 替換
- 7.14 憑證存儲
- 7.15 總結
- 8. 自定義 Git
- 8.1 配置 Git
- 8.2 Git 屬性
- 8.3 Git 鉤子
- 8.4 使用強制策略的一個例子
- 8.5 總結
- 9. Git 與其他系統
- 9.1 作為客戶端的 Git
- 9.2 遷移到 Git
- 9.3 總結
- 10. Git 內部原理
- 10.1 底層命令和高層命令
- 10.2 Git 對象
- 10.3 Git 引用
- 10.4 包文件
- 10.5 引用規格
- 10.6 傳輸協議
- 10.7 維護與數據恢復
- 10.8 環境變量
- 10.9 總結
- A. 其它環境中的 Git
- A1.1 圖形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Eclipse 中的 Git
- A1.4 Bash 中的 Git
- A1.5 Zsh 中的 Git
- A1.6 Powershell 中的 Git
- A1.7 總結
- B. 將 Git 嵌入你的應用
- A2.1 命令行 Git 方式
- A2.2 Libgit2
- A2.3 JGit
- C. Git 命令
- A3.1 設置與配置
- A3.2 獲取與創建項目
- A3.3 快照基礎
- A3.4 分支與合并
- A3.5 項目分享與更新
- A3.6 檢查與比較
- A3.7 調試
- A3.8 補丁
- A3.9 郵件
- A3.10 外部系統
- A3.11 管理
- A3.12 底層命令