Chapter3 分支
參考自 https://git-scm.com/book/zh/v1/Git-%E5%88%86%E6%94%AF
3.1 什么是分支
為了理解 Git 分支的實現方式,我們需要回顧一下 Git 是如何存儲數據的。Git 保存的不是文件差異或者變化量,而是一系列文件快照。
下面用一個實際例子來說明分支的實現方式:
-
首先,我們創建一個新的本地倉庫,并在工作目錄新增三個文件:
$ mkdir branchtest $ cd branchtest $ git init $ git add file_001 file_002 file_003
注意,這個時候倉庫中還沒有任何分支,不信你用
git branch --list
試一下,沒有輸出任何東西。 -
接下來我們將暫存區的文件提交到倉庫:
git commit -m 'initial commit of my project' [master (root-commit) 2618748] initial commit of my project 3 files changed, 3 insertions(+) create mode 100644 file_001 create mode 100644 file_002 create mode 100644 file_003 Dongyu@Dongyu-PC MINGW64 /e/workspace/others/git/branchtest (master) $ git branch --list * master
可以看到這個時候有了一個新的分支 master, 在這個過程中發生了什么呢?
- 首先,在暫存操作(也就是
git add
)中,Git 會計算每個文件的校驗和,然后把文件快照和校驗和打包到 blob 結構體中,再存儲到 倉庫里。暫存區并不存儲文件快照,只是存儲了文件校驗和。 - 然后,在提交操作(也就是
git commit
)執行前,Git 會先計算每個子目錄的校驗和,然后把目錄和文件的校驗和一起打包到 tree 結構體中。 - 最后,執行提交操作,Git 會將之前的 tree 結構體和作者信息等內容打包到 commit 結構體(提交對象)中,存儲到倉庫里。
上述過程可以用下圖表示:
單個提交對象在倉庫中的數據結構每次
git commit
操作都會將一個 commit 提交對象存儲到倉庫里。新存儲的 commit 會跟在之前 commit 對象的后面:
多個提交對象之間的鏈接關系 - 首先,在暫存操作(也就是
-
提交對象鏈 與 分支:
多次提交會讓 commit 對象連成一條提交鏈。而 Git 中的分支,本質上只是一個指向某個 commit 對象的可變指針。master 是分支的默認名字,每次你提交一個 commit 對象上去,master 指針就會向后移動,指向最新提交的 commit 對象:分支其實就是從某個提交對象往回看的歷史 -
新建一個分支:
新建一個分支,實際上就是創建一個新的分支指針,這可以用git branch
命令實現,比如git branch testing
就會在當前 commit 對象上新建一個分支指針:多個分支指向提交數據的歷史 -
切換到新建分支上:
Git 是如何知道你當前在那個分支上工作呢?很簡單,它保存著一個名為 HEAD 的特別指針。剛剛的git branch
只是創建了一個新分支,并沒有切換到那個分支上,所以 HEAD 指針還是指向 master 分支:HEAD 指向 master 分支要切換到新建的 testing 分支,就得把 HEAD 指針移動到 testing 分支上,這可以用
git checkout testing
命令來實現:HEAD 指向 testing 分支注意 使用
git checkout
切換分支的同時,工作目錄中的文件也會被修改為 目標分支 的文件。如果工作目錄中有未提交的修改的話, git 是不允許你切換分支的。 -
在新建分支上提交:
現在 master 和 testing 分支上的內容是完全一樣的,我們修改一下 工作目錄 中的文件,然后提交。下圖顯示了提交后的結果:每次提交后 HEAD 隨著分支一起向前移動 -
在原分支上提交:
我們用git checkout master
命令切換回原 master 分支。此時工作目錄會還原回 master 分支的狀態。做些修改后再次提交。下圖顯示了提交后的結果:不同流向的分支歷史
3.2 分支的新建與合并
例子
現在我們來看一個簡單的分支與合并的實際例子:
已經有了一個項目倉庫(master),接到了一個新需求,創建一個分支用來實現這個需求 (iss53)。
這個時候,你突然接到任務要在原項目中做一個緊急修復。那么可以按照如下步驟處理:
- 返回原有項目分支(master);
- 為這個緊急任務創建一個新分支,并在其中修復這個問題(hotfix);
- Bug 解完后,回到原有分支(master),把修補分支合并進來;
- 切換到 新需求分支(iss53),繼續工作。
好,我們來看一下每個步驟中分支的狀態:
-
原項目分支(master):
原項目分支 -
接到新需求,創建一個分支(iss53):
創建分支使用git branch iss53
命令:創建新需求分支 -
在新需求分支上提交了一些代碼:
經過提交的分支 -
接到解 Bug 任務,再創建一個解Bug分支(hotfix),并在其中了提交一些代碼:
# 先回到 master git checkout master # 切換到 hotfix 工作 git branch hotfix git checkout hotfix # 進行代碼提交 git add ... git commit -m 'fix bug'
經過提交的分支注意,創建 hotfix 之前要先回到 master 分支,不然的話創建的分支是基于 iss53 分支的。
-
Bug 解完,回到原有分支(master), 把 hotfix 合并進來:
# 先回到 master git checkout master # 進行分支合并 git merge hotfix
合并后的分支 -
hotfix 用完了,刪除掉它
git branch -d hotfix
刪除 hotfix -
回到 新需求分支 上繼續開發:
git checkout iss53
新分支 -
新需求開發完了,合并到 master 上:
# 先切換回 master git checkout master # 合并分支 git merge iss53
這次的分支合并跟之前不大一樣,合并 hotfix 的時候,master 還是原來的 master, 但合并 iss53 分支的時候,master 已經不是原來的 master 了(此時的 master 由于合并了 hotfix, 指針向右移動了一格)。
此時,master 指針不是直接移動到 iss53 指針所在的位置,而是在 master 分支上創建了一個新的節點用于存儲合并的結果:
iss53 分支合并
沖突時的分支合并
加入在合并 iss53 分支的時候遇到了沖突,git 會顯示如下內容:
$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
上述指令顯示在 index.html 文件中出現了沖突,你可以用 git status
命令查看一下此時的狀態:
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")
git 提醒你你要先解決沖突,然后使用 git add <file>
來將沖突標記為已解決狀態,然后調用 git commit
命令來修復沖突。
-
打開 index.html 文件,找到沖突所在位置:
<<<<<<< HEAD <div id="footer">contact : email.support@github.com</div> ======= <div id="footer"> please contact us at support@github.com </div> >>>>>>> iss53
=======
以上的部分是 master 分支的內容,以下的部分是 iss53 分支的內容,你可以保留其中某個,或者改成另一種實現。 解決完沖突后,調用
git add index.html
將該文件放入暫存區;然后調用
git commit
命令提交到倉庫。
3.3 分支的管理
-
顯示分支列表
git branch
:$ git branch iss53 * master testing
-
查看各個分支的最后提交信息
git branch -v
:$ git branch -v iss53 93b412c fix javascript issue * master 7a98805 Merge branch 'iss53' testing 782fd34 add scott to the author list in the readmes
-
顯示已合并的分支
git branch --merged
:$ git branch --merged iss53 * master
知道哪些分支已經被合并,就可以刪除它們了
-
顯示未合并的分支
git branch --no-merged
:$ git branch --no-merged testing
-
分支已經合并的情況下,可以用
git branch -d branch_name
命令來刪除:$ git branch -d iss53
-
分支未合并的情況下,直接使用上述命令會報錯,因為分支上的修改可能是有用的,刪除的話可能會有危險:
$ git branch -d testing error: The branch 'testing' is not fully merged. If you are sure you want to delete it, run 'git branch -D testing'.
正如提示所說,如果你確實不需要這個分支,可以用
git branch -D branch_name
來強制刪除。
利用分支進行開發的工作流程
這一節介紹一些使用分支進行開發的工作流程。你可以結合實際項目的情況選擇一種用用看。
長期分支
由于 git 使用簡單的三方合并,你可以同時擁有多個開放的分支,每個分支用于特定的任務。
許多使用 git 的開發者都喜歡用這種方式來開展工作。比如在 master 分支中保留完全穩定的代碼,與此同時,在 master 的基礎上新建一個 develop 分支用于開發,還可以在 develop 分支的基礎上新建一個 topic 分支用于開發某些新特性。
topic 分支開發完畢后,合并到 develop 分支;develop 分支穩定下來后,合并到 master 分支:
注意: 這種方式是基于一種篩選的思想,新代碼通過 topic -> develop -> master 層層篩選,從而達到提交代碼穩定性的目的。跟之前 同時做兩件事的分叉分支 不是一個思路。
特性分支
這種方式就是上一節中同時做好幾件事的思路。在任何項目中都可以使用 特性分支(topic) 。一個特性分支是指一個短期的,用來實現單一特性的分支。可能你在其它版本控制工具里從未這樣做過,因為創建與合并分支的消耗太大。然而,在 git 中,一天之內建立、合并再刪除多個分支是常見的事。
這種技術有一個顯而易見的好處:各個特性分支之間互不干擾。瀏覽代碼之類的事情會因此變得更簡單。你可以把特性分支保存幾天甚至幾個月,在需要的時候合并它們就好了。
這種方式就不舉例子了,之前已經說過。