Git之(四)分支管理

分類:IT技術 時間:2016-10-11

當我們初始化Git倉庫的時候,Git會默認創建一個名為master的主分支。在實際工作中,主分支要求是一個穩定、健壯、安全的主線,一般不允許在主分支上直接進行開發,而是拉取一個新的分支,開發、測試完成後,再將分支合並到主分支上。

使用分支意味著你可以從開發主線上分離開來,然後在不影響主線的同時繼續工作。在很多版本控制系統中,這是個昂貴的過程,常常需要創建一個源代碼目錄的完整副本,對大型項目來說會花費很長時間。

 Git 的分支模型可稱為“必殺技特性”,而正是因為該特性將 Git 從版本控制系統家族裏區分出來,鶴立雞群。其他版本控制系統如SVN等都有分支管理,但是用過之後你會發現,這些版本控制系統創建和切換分支比蝸牛還慢,簡直讓人無法忍受,結果分支功能成了擺設,大家都不去用。但Git的分支是與眾不同的,無論創建、切換和刪除分支,Git能在瞬間完成!無論你的版本庫是1個文件還是1萬個文件。

Git 鼓勵在工作流程中頻繁使用分支與合並,哪怕一天之內進行許多次都沒有關系。在實際工作中,往往修復一個bug都會使用一個分支來完成。

理解分支的概念並熟練運用後,你才會意識到為什麽 Git 是一個如此強大而獨特的工具,並從此真正改變你的開發方式。


分支實現原理

在第一篇文章中提到過,Git 保存的不是文件差異或者變化量,而只是一系列文件快照。

在 Git 中提交時,會保存一個提交(commit)對象,該對象包含一個指向暫存內容快照的指針,包含本次提交的作者等相關附屬信息,包含零個或多個指向該提交對象的父對象指針:首次提交是沒有直接祖先的,普通提交有一個祖先,由兩個或多個分支合並產生的提交則有多個祖先。

為直觀起見,我們假設在工作目錄中有三個文件,準備將它們暫存後提交。暫存操作會對每一個文件計算校驗和(即SHA-1 哈希字串),然後把當前版本的文件快照保存到 Git 倉庫中(Git 使用 blob 類型的對象存儲這些快照),並將校驗和加入暫存區域。

當使用 Git commit新建一個提交對象前,Git 會先計算每一個子目錄(本例中就是項目根目錄)的校驗和,然後在 Git 倉庫中將這些目錄保存為樹(tree)對象。之後 Git 創建的提交對象,除了包含相關提交信息以外,還包含著指向這個樹對象(項目根目錄)的指針,如此它就可以在將來需要的時候,重現此次快照的內容了。

現在,Git 倉庫中有五個對象:三個表示文件快照內容的 blob 對象;一個記錄著目錄樹內容及其中各個文件對應 blob 對象索引的 tree 對象;以及一個包含指向 tree 對象(根目錄)的索引和其他提交信息元數據的 commit 對象:


作些修改後再次提交,那麽這次的提交對象會包含一個指向上次提交對象的指針(即下圖中的 parent 對象)。兩次提交後,倉庫歷史會這個樣子:


Git 中的分支,其實本質上僅僅是個指向 commit 對象的可變指針。Git 會使用 master 作為分支的默認名字。在若幹次提交後,其實已經有了一個指向最後一次提交對象的 master 分支,它在每次提交的時候都會自動向前移動。



創建分支

創建一個新的名為“testing”分支,可以使用“git branch<branchName>”命令:

git branch testing

該命令會在當前 commit 對象上新建一個指針:


那麽,Git 是如何知道你當前在哪個分支上工作的呢?其實答案也很簡單,它保存著一個名為HEAD 的特別指針。在 Git 中,它是一個指向正在工作中的本地分支的指針(可以將 HEAD 想象為當前分支的別名)。運行Git branch 命令,僅僅是建立了一個新的分支,但不會自動切換到這個分支中去,所以在這個例子中,我們依然還在 master 分支裏工作。


使用不帶任何參數的“git branch”命令可以查看當前的分支情況:

* master
  testing

Git顯示,共有兩個分支,當前工作分支為master,分支列表中的星號“*”相當於HEAD指針,標註了當前工作分支。


切換分支

命令“git checkout <branchName>”可以將當前工作分支切換到名為branchName的分支。比如,運行命令:

git checkout testing

Git會提示:

Switched to branch 'testing'

這樣 HEAD 就指向了 testing 分支:


現在我們如果修改了工作區的文件,所有commit操作都是提交到testing分支,而非master。


現在 testing 分支向前移動了一步,而 master 分支仍然指向原先 git checkout 時所在的 commit 對象。現在重新切換到master分支:

git checkout master


這條命令做了兩件事。它把 HEAD 指針移回到 master 分支,並把工作目區的文件換成了 master 分支所指向的快照內容。也就是說,現在開始所做的改動,將始於本項目中一個較老的版本。它的主要作用是將 testing 分支裏作出的修改暫時取消,這樣你就可以向另一個方向進行開發。

在mast分支上再做些修改,然後提交。現在我們的項目提交歷史產生了分叉,因為剛才我們創建了一個分支testing,轉換到其中進行了一些工作,然後又回到原來的master主分支進行了另外一些工作。


這些改變分別孤立在不同的分支裏。我們可以在不同分支裏反復切換,並在時機成熟時把它們合並到一起。

由於 Git 中的分支實際上僅是一個包含所指對象校驗和(40 個字符長度 SHA-1 字串)的文件,所以創建和銷毀一個分支就變得非常廉價。說白了,新建一個分支就是向一個文件寫入 41 個字節(外加一個換行符)那麽簡單,當然也就很快。


合並分支

模擬這樣的一個場景,早上到了公司接到新任務,新建一個名為“iss53”的分支來進行開發工作。要新建並切換到該分支,運行git checkout 並加上 -b 參數:

git checkout -b iss53

這相當於執行下面這兩條命令:

git branch iss53
git checkout iss53


然後不斷地寫代碼,提交代碼:


突然,接到通知,需要立即修復master分支上的一個嚴重bug。

第一步肯定需要切換到master。如果當前工作區與暫存區都是幹凈的,OK,直接切換回master即可。但是如果iss53分支上的開發還沒有完成,並且不便於commit到版本庫,怎麽辦?一旦切回到其他分支,工作區與暫存區就會被清空、覆蓋。實際上,如果工作區或暫存區不是幹凈的,存在沒有提交到版本庫的更改,Git是不允許切換分支的,會提示:

error: Your local changes to the following files wouldbe overwritten by checkout:
      readme.txt
Please, commit your changes or stash them before you can switch branches.

解決這個問題的辦法就是git stash命令。

該命令可以獲取工作目錄的中間狀態——也就是修改過的被追蹤的文件和暫存的變更——並將它保存到一個未完結變更的堆棧中,隨時可以重新應用。

運行“git stash”命令之後,iss53分支上未commit得變更就會被“儲藏”起來,可以順利地切換到master分支了。要查看現有的儲藏,你可以使用 git stash list,會的到這樣的一個列表:

stash@{0}: WIP on testing: 049d078 …
stash@{1}: WIP on testing: c264051 …
stash@{2}: WIP on testing: 21d80a5 …

列出的是該分支上所有被stash過的編號,使用命令“git stash apply”即可恢復到最新stash過的場景。如果想應用更早的儲藏,可以通過名字指定它,像這樣:git stash apply stash@{2}。如果不指明編號,Git 默認使用最近的儲藏並嘗試應用它。

題歸正轉,我們切換到master分支,拉去一個名為“hotfix”的分支來緊急修復bug。

git checkout -b 'hotfix'

修復好之後,commit到版本庫,則現在Git的分支結構如下圖所示:


經測試之後,該bug成功修復,然後需要將該分支合並到master,首先依然要切換到master,然後使用命令“git merge”合並分支:

git checkout master
git merge hotfix

Git提示:

Updating 771f6de..adea62a
Fast-forward
 …

請註意,合並時出現了“Fast forward”的提示。由於當前 master 分支所在的提交對象是要並入的 hotfix 分支的直接上遊,Git 只需把master 分支指針直接右移。換句話說,如果順著一個分支走下去可以到達另一個分支的話,那麽 Git在合並兩者時,只會簡單地把指針右移,因為這種單線的歷史分支不存在任何需要解決的分歧,所以這種合並過程可以稱為快進(Fast forward)

現在最新的修改已經在當前master 分支所指向的提交對象中了:


在那個超級重要的修補發布以後,就可以回繼續之前未完成的工作。由於當前 hotfix 分支和 master 都指向相同的提交對象,所以hotfix 已經完成了歷史使命,可以刪掉了。使用 git branch 的 -d 選項執行刪除操作:

git branch -d hotfix

不用擔心之前 hotfix 分支的修改內容尚未包含到 iss53 中來。如果確實需要納入此次修補,可以用git merge master 把 master 分支合並到 iss53;或者等 iss53 完成之後,再將iss53 分支中的更新並入 master。

現在回到之前未完成的 iss53分支上繼續工作,完成後commit到版本庫。


在問iss53 分支上的工作完成之後,可以合並回 master 分支。實際操作同前面合並 hotfix 分支差不多,只需回到master分支,運行 git merge 命令指定要合並進來的分支。

請註意,這次合並操作的底層實現,並不同於之前 hotfix 的並入方式。因為這次開發歷史是從更早的地方開始分叉的。由於當前master 分支所指向的提交對象(C4)並不是 iss53 分支的直接祖先,Git 不得不進行一些額外處理。就此例而言,Git 會用兩個分支的末端(C4 和 C5)以及它們的共同祖先(C2)進行一次簡單的三方合並計算。下圖用紅框標出了Git 用於合並的三個提交對象:


這次,Git 沒有簡單地把分支指針右移,而是對三方合並後的結果重新做一個新的快照,並自動創建一個指向它的提交對象(C6)。這個提交對象比較特殊,它有兩個祖先(C4 和 C5)。

值得一提的是 Git 可以自己裁決哪個共同祖先才是最佳合並基礎,不需要開發者手工指定合並基礎。此特性讓Git 的合並操作比其他系統都要簡單不少。


解決沖突

有時候合並操作並不會如此順利。如果在不同的分支中都修改了同一個文件的同一部分,Git 就無法幹凈地把兩者合到一起,邏輯上說,這種問題只能由人來裁決。這時候如果合並分支就會出現下面的結果:

Auto-merging readme.txt
CONFLICT (content): Merge conflictin readme.txt
Automatic merge failed; fix conflicts and then committhe result.

Git 作了合並,但沒有提交,它會停下來等你解決沖突。要看看哪些文件在合並時發生沖突,可以用git status 查閱:

On branch master
You have unmerged paths.
  (fix conflictsand run "git commit")
 
Unmerged paths:
  (use "gitadd <file>..." to mark resolution)
      bothmodified:   readme.txt
no changes added to commit (use "git add"and/or "git commit -a")

任何包含未解決沖突的文件都會以未合並(unmerged)的狀態列出。Git 會在有沖突的文件裏加入標準的沖突解決標記,可以通過它們來手工定位並解決這些沖突。可以看到此文件包含類似下面這樣的部分:

this is my first git project
<<<<<<<HEAD
add row on master branch
add annother row on master branch
=======
add row on testing branch
add another row on testing branch
>>>>>>>testing

可以看到 ======= 隔開的上半部分,是 HEAD(即 master 分支,在運行merge 命令時所切換到的分支)中的內容,下半部分是在 iss53 分支中的內容。解決沖突的辦法無非是二者選其一或者由你親自整合到一起。當然,Git插入的額外標記行也需要刪除。

在解決了所有文件裏的所有沖突後,運行 git add將把它們標記為已解決狀態(實際上就是來一次快照保存到暫存區域)。因為一旦暫存,就表示沖突已經解決。

刪除分支

分支合並到master之後,如果無特殊用途,應該及時刪除分支。

要從該清單中篩選出已經(或尚未)與當前分支合並的分支,可以用 “--merged”和“--no-merged” 選項。

比如用“git branch --merged” 查看哪些分支已被並入當前分支,也就是說哪些分支是當前分支的直接上遊:

* master
  testing

證明testing分支已經合並到master分支當中了,可以刪除:

git branch –d testing


使用“git branch --no-merge”查看還沒有合並的分支: 

 newTesting

如果使用命令“git branch–d newTesting”刪除該分支,Git會提示:

error: The branch 'newTesting' is not fully merged.
If you are sure you want to delete it, run 'git branch -D newTesting'.

由於這些分支中還包含著尚未合並進來的工作成果,所以簡單地用 Git branch -d 刪除該分支會提示錯誤,因為那樣做會丟失數據。

不過,如果你確實想要刪除該分支上的改動,可以用大寫的刪除選項 -D 強制執行,就像上面提示信息中給出的那樣。



Tags: 乳腺增生 微創手術 控制系統 源代碼 commit

文章來源:


ads
ads

相關文章
ads

相關文章

ad