版本管理工具Git的使用(三)-Git分支

幾乎全部的版本控制系統都以某種形式支持分支。 使用分支意味着你能夠把你的工做從開發主線上分離開來,以避免影響開發主線。 在不少版本控制系統中,這是一個略微低效的過程——經常須要徹底建立一個源代碼目錄的副本。對於大項目來講,這樣的過程會耗費不少時間。
有人把 Git 的分支模型稱爲它的「必殺技特性」,也正由於這一特性,使得 Git 從衆多版本控制系統中脫穎而出。 爲什麼 Git 的分支模型如此出衆呢? Git 處理分支的方式可謂是難以置信的輕量,建立新分支這一操做幾乎能在瞬間完成,而且在不一樣分支之間的切換操做也是同樣便捷。 與許多其它版本控制系統不一樣,Git 鼓勵在工做流程中頻繁地使用分支與合併,哪怕一天以內進行許屢次。 理解和精通這一特性,你便會意識到Git 是如此的強大而又獨特,而且今後真正改變你的開發方式。
在介紹Git分支,先將下Git是如何保存數據的?html

1、Git是如何保存數據的

直接記錄快照,而非比較差別

Git 和其它版本控制系統(包括 Subversion 和近似工具)的主要差異在於 Git 對待數據的方法。 概念上來區分,其它大部分系統以文件變動列表的方式存儲信息。 這類系統(CVS、Subversion、Perforce、Bazaar 等等)將它們保存的信息看做是一組基本文件和每一個文件隨時間逐步累積的差別。
image.png
Git 不按照以上方式對待或保存數據。 反之,Git 更像是把數據看做是對小型文件系統的一組快照。 每次你提交更新,或在 Git 中保存項目狀態時,它主要對當時的所有文件製做一個快照並保存這個快照的索引。 爲了高效,若是文件沒有修改,Git 再也不從新存儲該文件,而是隻保留一個連接指向以前存儲的文件。 Git 對待數據更像是一個 快照流。
image.png
這是 Git 與幾乎全部其它版本控制系統的重要區別。
在進行提交操做時,Git 會保存一個提交對象(commit object)。知道了 Git 保存數據的方式,咱們能夠很天然的想到——該提交對象會包含一個指向暫存內容快照的指針。 但不只僅是這樣,該提交對象還包含了做者的姓
名和郵箱、提交時輸入的信息以及指向它的父對象的指針。首次提交產生的提交對象沒有父對象,普通提交操做產生的提交對象有一個父對象,而由多個分支合併產生的提交對象有多個父對象,爲了更加形象地說明,咱們假設如今有一個工做目錄,裏面包含了三個將要被暫存和提交的文件。 暫存操做會爲每個文件計算校驗,而後會把當前版本的文件快照保存到Git 倉庫中,最終將校驗和加入到暫存區域等待提交:git

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

當使用 git commit 進行提交操做時,Git 會先計算每個子目錄的校驗和,而後在Git 倉庫中這些校驗和保存爲樹對象。 隨後,Git 便會建立一個提交對象,它除了包含上面提到的那些信息外,還包含指向這個樹對象(項目根目錄)的指針。如此一來,Git 就能夠在須要的時候重現這次保存的快照。
如今,Git 倉庫中有五個對象:三個 blob 對象(保存着文件快照)、一個樹對象(記錄着目錄結構和 blob 對象索引)以及一個提交對象(包含着指向前述樹對象的指針和全部提交信息)。
image.png數據庫

作些修改後再次提交,那麼此次產生的提交對象會包含一個指向上次提交對象(父對象)的指針。服務器

image.png

(以上內容參考於progit)
Git 的分支,其實本質上僅僅是指向提交對象的可變指針。 Git 的默認分支名字是 master。 在屢次提交操做以後,你其實已經有一個指向最後那個提交對象的 master 分支。 它會在每次的提交操做中自動向前動。
(Git 的 「master」 分支並非一個特殊分支。 它就跟其它分支徹底沒有區別。 之因此幾乎每個倉庫都有 master 分支,是由於 git init 命令默認建立它)
image.png
2、分支相關的概念及操做
瞭解了上面的概念,接下來假設下,如今須要兩我的爲同一個項目開發不一樣的功能,在開發過程當中,如何保證不相互影響了?---分支就能夠解決這種影響網絡

2.1分支的新建合併

來看一個簡單的分支新建與分支合併的例子,實際工做中你可能會用到相似的工做流。 你將經歷以下步
驟:工具

  1. 開發某個網站。
  2. 爲實現某個新的需求,建立一個分支。
  3. 在這個分支上開展工做。

正在此時,你忽然接到一個電話說有個很嚴重的問題須要緊急修補。 你將按照以下方式來處理:測試

  1. 切換到你的線上分支(production branch)。
  2. 爲這個緊急任務新建一個分支,並在其中修復它。
  3. 在測試經過以後,切換回線上分支,而後合併這個修補分支,最後將改動推送到線上分支。
  4. 切換回你最初工做的分支上,繼續工做。

新建分支
首先,假設正在你的項目上master分支工做,而且已經有一些提交。
如今須要修復線上的一個問題(issuse 52),這時須要新建一個修復該問題的分支,並切換到該分支
這時使用命令:fetch

git checkout -b issuse52

它是下面兩條命令的簡寫:網站

git branch issuse52
git checkout issuse52

注意:在切換分支時,要擺正暫存區是乾淨的,負責切換分支會失敗。
你花了一些時間將該issuse修復,並將修復的代碼推送到遠程庫。
這時,你能夠切換到原來的master分支繼續以前的工做了
請牢記:當你切換分支的時候,Git 會重置你的工做目錄,使其看起來像回到了你在那個分支上最後一次提交的樣子。 Git 會自動添加、刪除、修改文件以確保此時你的工做目錄和這個分支最後一次提交時的樣子如出一轍。
你能夠運行你的測試,確保你的修改是正確的,而後將其合併回你的master 分支來部署到線上。 你可使用git merge 命令來達到上述目的:

git checkout master
git merge issuse52
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

在合併的時候,你應該注意到了"快進(fast-forward)"這個詞。 因爲當前 master 分支所指向的提交是你當前提交(有關 hotfix 的提交)的直接上游,因此 Git 只是簡單的將指針向前移動。 換句話說,當你試圖合併兩個分支時,若是順着一個分支走下去可以到達另外一個分支,那麼 Git 在合併二者的時候,只會簡單的將指針向前推動(指針右移),由於這種狀況下的合併操做沒有須要解決的分歧——這就叫作 「快進(fast-forward)」。如今,最新的修改已經在 master 分支所指向的提交快照中,你能夠着手發佈該修復了。
關於這個緊急問題的解決方案發布以後,你準備回到被打斷以前時的工做中。 然而,你應該先刪除 hotfix 分
支,由於你已經再也不須要它了 —— master 分支已經指向了同一個位置。 你可使用帶 -d 選項的 git
branch 命令來刪除分支:

git branch -d issuse52
Deleted branch issuse52 (3a0874c).

2.2遇到衝突時的分支合併

有時候合併操做不會如此順利。 若是你在兩個不一樣的分支中,對同一個文件的同一個部分進行了不一樣的修改,Git 就無法乾淨的合併它們。 若是你對 #53 問題的修改和有關 hotfix 的修改都涉及到同一個文件的同一
處,在合併它們的時候就會產生合併衝突:

git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

此時 Git 作了合併,可是沒有自動地建立一個新的合併提交。 Git 會暫停下來,等待你去解決合併產生的衝突。
你能夠在合併衝突後的任意時刻使用 git status 命令來查看那些因包含合併衝突而處於未合併(unmerged)狀態的文件:

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 會在有衝突的文件中加入標準的衝突解決標記,這樣你能夠打開這些包含衝突的文件而後手動解決衝突。 出現衝突的文件會包含一些特殊區段,看起來像下面這個樣子:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

這表示 HEAD 所指示的版本(也就是你的 master 分支所在的位置,由於你在運行 merge 命令的時候已經檢出到了這個分支)在這個區段的上半部分(======= 的上半部分),而 iss53 分支所指示的版本在======= 的
下半部分。 爲了解決衝突,你必須選擇使用由 =======
分割的兩部分中的一個,或者你也能夠自行合併這些內容。 例如,你能夠經過把這段內容換成下面的樣子來解
決衝突:

<div id="footer">
please contact us at email.support@github.com
</div>

上述的衝突解決方案僅保留了其中一個分支的修改,而且 <<<<<<< , ======= , 和 >>>>>>> 這些行被徹底刪除了。 在你解決了全部文件裏的衝突以後,對每一個文件使用 git add 命令來將其標記爲衝突已解決。 一旦暫存這些本來有衝突的文件,Git 就會將它們標記爲衝突已解決。

2.3分支開發工做流

接下,介紹一些常見的利用分支進行開發的工做流程。而正是因爲分支管理的便捷,才衍生出這些典型的工做模式,你能夠根據項目實際狀況選擇一種用用看。
長期分支
由於 Git 使用簡單的三方合併,因此就算在一段較長的時間內,反覆把一個分支合併入另外一個分支,也不是什麼難事。 也就是說,在整個項目開發週期的不一樣階段,你能夠同時擁有多個開放的分支;你能夠按期地把某些特性分支合併入其餘分支中。許多使用 Git 的開發者都喜歡使用這種方式來工做,好比只在 master 分支上保留徹底穩定的代碼——有可能僅
僅是已經發布或即將發佈的代碼。 他們還有一些名爲 develop 或者 next 的平行分支,被用來作後續開發或者測試穩定性——這些分支沒必要保持絕對穩定,可是一旦達到穩定狀態,它們就能夠被合併入 master 分支了。 這樣,在確保這些已完成的特性分支(短時間分支,好比以前的 iss53 分支)可以經過全部測試,而且不會引入更多 bug 以後,就能夠合併入主幹分支中,等待下一次的發佈。事實上咱們剛纔討論的,是隨着你的提交而不斷右移的指針。 穩定分支的指針老是在提交歷史中落後一大截,而前沿分支的指針每每比較靠前。
一般把他們想象成流水線(work silos)可能更好理解一點,那些通過測試考驗的提交會被遴選到更加穩定的流水線上去。
image.png
特性分支
特性分支對任何規模的項目都適用。 特性分支是一種短時間分支,它被用來實現單一特性或其相關工做。在 Git 中一天以內屢次建立、使用、合併、刪除分支都很常見。
你已經在上一節中你建立的 iss53 和 hotfix 特性分支中看到過這種用法。 你在上一節用到的特性分支(iss52 )中提交了一些更新,而且在它們合併入主幹分支以後,你又刪除了它們。 這項技術能使你快速而且完整地進行上下文切換(context-switch)——由於你的工做被分散到不一樣的流水線中,在不一樣的流水線中每一個分支都僅與其目標特性相關,所以,在作代碼審查之類的工做的時候就能更加容易地看出你作了哪些改動。 你能夠把作出的改動在特性分支中保留幾分鐘、幾天甚至幾個月,等它們成熟以後再合併,而不用在意它們創建的順序或工做進度。
請牢記,當你作這麼多操做的時候,這些分支所有都存於本地。
當你新建和合並分支的時候,全部這一切都只發生在你本地的 Git 版本庫中 —— 沒有與服務器發生交互。

2.4遠程分支

遠程引用是對遠程倉庫的引用(指針),包括分支、標籤等等。 你能夠經過 git ls-remote (remote) 來顯式地得到遠程引用的完整列表,或者經過 git remote show (remote) 得到遠程分支的更多信息。 然而,一個更常見的作法是利用遠程跟蹤分支。
遠程跟蹤分支是遠程分支狀態的引用。 它們是你不能移動的本地引用,當你作任何網絡通訊操做時,它們會自動移動。 遠程跟蹤分支像是你上次鏈接到遠程倉庫時,那些分支所處狀態的書籤。

它們以 (remote)/(branch) 形式命名。 例如,若是你想要看你最後一次與遠程倉庫 origin 通訊時 master分支的狀態,你能夠查看origin/master 分支。 你與同事合做解決一個問題而且他們推送了一個 iss52 分支,你可能有本身的本地 iss53 分支;可是在服務器上的分支會指向 origin/iss53 的提交。
這可能有一點兒難以理解,舉一個例子。 假設你的網絡裏有一個在 git.ourcompany.com 的 Git 服務器。 若是你從這裏克隆,Git 的 clone 命令會爲你自動將其命名爲 origin,拉取它的全部數據,建立一個指向它的 master 分支的指針,而且在本地將其命名爲origin/master。 Git 也會給你一個與 origin 的 master分支在指向同一個地方的本地 master 分支,這樣你就有工做的基礎。
若是你在本地的 master 分支作了一些工做,與此同時,其餘人推送提交到 git.ourcompany.com 並更新了它的 master 分支,那麼你的提交歷史將向不一樣的方向前進。 另外,只要你不與 origin 服務器鏈接,你的
origin/master 指針就不會移動。
若是要同步你的工做,運行 git fetch origin 命令。 這個命令查找 「origin」 是哪個服務器(在本例中,它是 git.ourcompany.com),從中抓取本地沒有的數據,而且更新本地數據庫,移動 origin/master
指針指向新的、更新後的位置。
推送
當你想要公開分享一個分支時,須要將其推送到有寫入權限的遠程倉庫上。 本地的分支並不會自動與遠程倉庫同步——你必須顯式地推送想要分享的分支。
若是但願和別人一塊兒在名爲 serverfix 的分支上工做,你能夠像推送第一個分支那樣推送它。 運行 git push (remote) (branch):

git push origin serverfix
Counting objects: 24, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (24/24), 1.91 KiB | 0 bytes/s, done.
Total 24 (delta 2), reused 0 (delta 0)
To https://github.com/schacon/simplegit
 * [new branch] serverfix -> serverfix

下一次其餘協做者從服務器上抓取數據時,他們會在本地生成一個遠程分支 origin/serverfix,指向服務器的 serverfix 分支的引用:

git fetch origin
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/schacon/simplegit
 * [new branch] serverfix -> origin/serverfix

要特別注意的一點是當抓取到新的遠程跟蹤分支時,本地不會自動生成一份可編輯的副本(拷貝)。 換一句話
說,這種狀況下,不會有一個新的 serverfix 分支——只有一個不能夠修改的 origin/serverfix 指針。
能夠運行 git merge origin/serverfix 將這些工做合併到當前所在的分支。 若是想要在本身的
serverfix 分支上工做,能夠將其創建在遠程跟蹤分支之上:

git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'

這會給你一個用於工做的本地分支,而且起點位於 origin/serverfix。
跟蹤分支
從一個遠程跟蹤分支檢出一個本地分支會自動建立所謂的「跟蹤分支」(它跟蹤的分支叫作「上游分支」)。
跟蹤分支是與遠程分支有直接關係的本地分支。 若是在一個跟蹤分支上輸入 git pull,Git 能自動地識別去哪一個服務器上抓取、合併到哪一個分支。
當克隆一個倉庫時,它一般會自動地建立一個跟蹤 origin/master 的 master 分支。

總結:

上面介紹瞭如何建並切換至新分支、在不一樣分支之間切換以及合併本地分支以及向Git的服務器推送本地分支的內容以及獲取遠程分支的最新內容、使用共享分支與他人協做。

相關文章
相關標籤/搜索