1. 程式人生 > >Git詳解之三:Git分支

Git詳解之三:Git分支

本篇blog為轉載,因為這是我看過關於git的最清晰易懂的blog教程,以備後用

Git 分支

幾乎每一種版本控制系統都以某種形式支援分支。使用分支意味著你可以從開發主線上分離開來,然後在不影響主線的同時繼續工作。在很多版本控制系統中,這是個昂貴的過程,常常需要建立一個原始碼目錄的完整副本,對大型專案來說會花費很長時間。(伯樂線上注:如果你對Git還不瞭解,建議從本Git系列第一篇文章開始閱讀)

有人把 Git 的分支模型稱為“必殺技特性”,而正是因為它,將 Git 從版本控制系統家族裡區分出來。Git 有何特別之處呢?Git 的分支可謂是難以置信的輕量級,它的新建操作幾乎可以在瞬間完成,並且在不同分支間切換起來也差不多一樣快。和許多其他版本控制系統不同,Git 鼓勵在工作流程中頻繁使用分支與合併,哪怕一天之內進行許多次都沒有關係。理解分支的概念並熟練運用後,你才會意識到為什麼 Git 是一個如此強大而獨特的工具,並從此真正改變你的開發方式。

3.1  何謂分支

為了理解 Git 分支的實現方式,我們需要回顧一下 Git 是如何儲存資料的。或許你還記得第一章的內容,Git 儲存的不是檔案差異或者變化量,而只是一系列檔案快照。

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

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

Shell
1 2 $git add READMEtest.rbLICENSE $gitcommit-m'initial commit of my project'

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

現在,Git 倉庫中有五個物件:三個表示檔案快照內容的 blob 物件;一個記錄著目錄樹內容及其中各個檔案對應 blob 物件索引的 tree 物件;以及一個包含指向 tree 物件(根目錄)的索引和其他提交資訊元資料的 commit 物件。概念上來說,倉庫中的各個物件儲存的資料和相互關係看起來如圖 3-1 所示:

Git詳解之二:Git分支

圖 3-1. 單個提交物件在倉庫中的資料結構

作些修改後再次提交,那麼這次的提交物件會包含一個指向上次提交物件的指標(譯註:即下圖中的 parent 物件)。兩次提交後,倉庫歷史會變成圖 3-2 的樣子:

Git詳解之二:Git分支

圖 3-2. 多個提交物件之間的連結關係

現在來談分支。Git 中的分支,其實本質上僅僅是個指向 commit 物件的可變指標。Git 會使用 master 作為分支的預設名字。在若干次提交後,你其實已經有了一個指向最後一次提交物件的 master 分支,它在每次提交的時候都會自動向前移動。

Git詳解之二:Git分支

圖 3-3. 分支其實就是從某個提交物件往回看的歷史

那麼,Git 又是如何建立一個新的分支的呢?答案很簡單,建立一個新的分支指標。比如新建一個 testing 分支,可以使用git branch命令:

Shell
1 $git branch testing

這會在當前 commit 物件上新建一個分支指標(見圖 3-4)。

Git詳解之二:Git分支

圖 3-4. 多個分支指向提交資料的歷史

那麼,Git 是如何知道你當前在哪個分支上工作的呢?其實答案也很簡單,它儲存著一個名為 HEAD 的特別指標。請注意它和你熟知的許多其他版本控制系統(比如 Subversion 或 CVS)裡的 HEAD 概念大不相同。在 Git 中,它是一個指向你正在工作中的本地分支的指標(譯註:將 HEAD 想象為當前分支的別名。)。執行git branch 命令,僅僅是建立了一個新的分支,但不會自動切換到這個分支中去,所以在這個例子中,我們依然還在 master 分支裡工作(參考圖 3-5)。

Git詳解之二:Git分支

圖 3-5. HEAD 指向當前所在的分支

要切換到其他分支,可以執行 git checkout命令。我們現在轉換到新建的 testing 分支:

Shell
1 $git checkout testing

這樣 HEAD 就指向了 testing 分支(見圖3-6)。

Git詳解之二:Git分支

圖 3-6. HEAD 在你轉換分支時指向新的分支

這樣的實現方式會給我們帶來什麼好處呢?好吧,現在不妨再提交一次:
$ vim test.rb $ git commit -a -m ‘made a change’

圖 3-7 展示了提交後的結果。

Git詳解之二:Git分支

圖 3-7. 每次提交後 HEAD 隨著分支一起向前移動

非常有趣,現在 testing 分支向前移動了一格,而 master 分支仍然指向原先 git checkout 時所在的 commit 物件。現在我們回到 master 分支看看:

Shell
1 $git checkout master

圖 3-8 顯示了結果。

Git詳解之二:Git分支

圖 3-8. HEAD 在一次 checkout 之後移動到了另一個分支

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

我們作些修改後再次提交:

Shell
1 2 $vimtest.rb $git commit-a-m'made other changes'

現在我們的專案提交歷史產生了分叉(如圖 3-9 所示),因為剛才我們建立了一個分支,轉換到其中進行了一些工作,然後又回到原來的主分支進行了另外一些工作。這些改變分別孤立在不同的分支裡:我們可以 在不同分支裡反覆切換,並在時機成熟時把它們合併到一起。而所有這些工作,僅僅需要branch 和 checkout 這兩條命令就可以完成。

Git詳解之二:Git分支

圖 3-9. 不同流向的分支歷史

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

這和大多數版本控制系統形成了鮮明對比,它們管理分支大多采取備份所有專案檔案到特定目錄的方式,所以根據專案檔案數量和大小不同,可能花費的時間 也會有相當大的差別,快則幾秒,慢則數分鐘。而 Git 的實現與專案複雜度無關,它永遠可以在幾毫秒的時間內完成分支的建立和切換。同時,因為每次提交時都記錄了祖先資訊(譯註:即parent 物件),將來要合併分支時,尋找恰當的合併基礎(譯註:即共同祖先)的工作其實已經自然而然地擺在那裡了,所以實現起來非常容易。Git 鼓勵開發者頻繁使用分支,正是因為有著這些特性作保障。

接下來看看,我們為什麼應該頻繁使用分支。

3.2  分支的新建與合併

現在讓我們來看一個簡單的分支與合併的例子,實際工作中大體也會用到這樣的工作流程:

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

假設此時,你突然接到一個電話說有個很嚴重的問題需要緊急修補,那麼可以按照下面的方式處理:

1. 返回到原先已經發布到生產伺服器上的分支。 2. 為這次緊急修補建立一個新分支,並在其中修復問題。 3. 通過測試後,回到生產伺服器所在的分支,將修補分支合併進來,然後再推送到生產伺服器上。 4. 切換到之前實現新需求的分支,繼續工作。

分支的新建與切換

首先,我們假設你正在專案中愉快地工作,並且已經提交了幾次更新(見圖 3-10)。

Git詳解之二:Git分支

圖 3-10. 一個簡短的提交歷史

現在,你決定要修補問題追蹤系統上的 #53 問題。順帶說明下,Git 並不同任何特定的問題追蹤系統打交道。這裡為了說明要解決的問題,才把新建的分支取名為 iss53。要新建並切換到該分支,執行git checkout 並加上 -b 引數:

Shell
1 2 $git checkout-biss53 Switched toanewbranch"iss53"

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

Shell
1 2 $git branch iss53 $gitcheckout iss53

圖 3-11 示意該命令的執行結果。

Git詳解之二:Git分支

圖 3-11. 建立了一個新分支的指標

接著你開始嘗試修復問題,在提交了若干次更新後,iss53分支的指標也會隨著向前推進,因為它就是當前分支(換句話說,當前的 HEAD 指標正指向 iss53,見圖 3-12):

Shell
1 2 $vimindex.html $gitcommit-a-m'added a new footer [issue 53]'

Git詳解之二:Git分支

圖 3-12. iss53 分支隨工作進展向前推進

現在你就接到了那個網站問題的緊急電話,需要馬上修補。有了 Git ,我們就不需要同時釋出這個補丁和 iss53 裡作出的修改,也不需要在建立和釋出該補丁到伺服器之前花費大力氣來複原這些修改。唯一需要的僅僅是切換回master 分支。

不過在此之前,留心你的暫存區或者工作目錄裡,那些還沒有提交的修改,它會和你即將檢出的分支產生衝突從而阻止 Git 為你切換分支。切換分支的時候最好保持一個清潔的工作區域。稍後會介紹幾個繞過這種問題的辦法(分別叫做 stashing 和 commit amending)。目前已經提交了所有的修改,所以接下來可以正常轉換到master 分支:

Shell
1 2 $git checkout master Switched tobranch"master"

此時工作目錄中的內容和你在解決問題 #53 之前一模一樣,你可以集中精力進行緊急修補。這一點值得牢記:Git 會把工作目錄的內容恢復為檢出某分支時它所指向的那個提交物件的快照。它會自動新增、刪除和修改檔案以確保目錄的內容和你當時提交時完全一樣。

接下來,你得進行緊急修補。我們建立一個緊急修補分支 hotfix 來開展工作,直到搞定(見圖 3-13):

Shell
1 2 3 4 5 6 $git checkout-b'hotfix' Switched toanewbranch"hotfix" $vimindex.html $gitcommit-a-m'fixed the broken email address' [hotfix]:created3a0874c:"fixed the broken email address" 1fileschanged,0insertions(+),1deletions(-)

Git詳解之二:Git分支

圖 3-13. hotfix 分支是從 master 分支所在點分化出來的

有必要作些測試,確保修補是成功的,然後回到 master 分支並把它合併進來,然後釋出到生產伺服器。用 git merge 命令來進行合併:

Shell
1 2 3 4 5 6 $git checkout master $gitmerge hotfix Updatingf42c576..3a0874c Fast forward README|1- 1fileschanged,0insertions(+),1deletions(-)

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

現在最新的修改已經在當前 master 分支所指向的提交物件中了,可以部署到生產伺服器上去了(見圖 3-14)。

Git詳解之二:Git分支

圖 3-14. 合併之後,master 分支和 hotfix 分支指向同一位置。

在那個超級重要的修補釋出以後,你想要回到被打擾之前的工作。由於當前 hotfix 分支和 master 都指向相同的提交物件,所以hotfix 已經完成了歷史使命,可以刪掉了。使用 git branch 的 -d 選項執行刪除操作:

Shell
1 2 $git branch-dhotfix Deleted branch hotfix(3a0874c).

現在回到之前未完成的 #53 問題修復分支上繼續工作(圖 3-15):

Shell
1 2 3 4 5 6 $git checkout iss53 Switched tobranch"iss53" $vimindex.html $gitcommit-a-m'finished the new footer [issue 53]' [iss53]:created ad82d7a:"finished the new footer [issue 53]" 1fileschanged,1insertions(+),0deletions(-)

Git詳解之二:Git分支

圖 3-15. iss53 分支可以不受影響繼續推進。

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

分支的合併

在問題 #53 相關的工作完成之後,可以合併回 master 分支。實際操作同前面合併 hotfix 分支差不多,只需回到master分支,執行 git merge 命令指定要合併進來的分支:

Shell
1 2 3 4 5 $git checkout master $gitmerge iss53 Mergemade by recursive. README|1+ 1files changed,1insertions(+),0deletions(-)

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

Git詳解之二:Git分支

圖 3-16. Git 為分支合併自動識別出最佳的同源合併點。

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

值得一提的是 Git 可以自己裁決哪個共同祖先才是最佳合併基礎;這和 CVS 或 Subversion(1.5 以後的版本)不同,它們需要開發者手工指定合併基礎。所以此特性讓 Git 的合併操作比其他系統都要簡單不少。

Git詳解之二:Git分支

圖 3-17. Git 自動建立了一個包含了合併結果的提交物件。

既然之前的工作成果已經合併到 master 了,那麼 iss53 也就沒用了。你可以就此刪除它,並在問題追蹤系統裡關閉該問題。

Shell
1 $git branch-diss53

遇到衝突時的分支合併

有時候合併操作並不會如此順利。如果在不同的分支中都修改了同一個檔案的同一部分,Git 就無法乾淨地把兩者合到一起(譯註:邏輯上說,這種問題只能由人來裁決。)。如果你在解決問題 #53 的過程中修改了hotfix 中修改的部分,將得到類似下面的結果:

Shell
1 2 3 4 $git merge iss53 Auto-mergingindex.html CONFLICT(content):Merge conflict inindex.html Automatic merge failed;fix conflicts andthencommit the result.

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

Shell
1 2 3 4 5 6 7 8 9 10 11 [master*]$git status index.html:needs merge # On branch master # Changed but not updated: # (use "git add ..."toupdatewhatwillbe committed) # (use "git checkout -- ..." todiscardchanges inworking directory) # # unmerged: index.html #

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

可以看到 ======= 隔開的上半部分,是 HEAD(即 master 分支,在執行merge 命令時所切換到的分支)中的內容,下半部分是在 iss53 分支中的內容。解決衝突的辦法無非是二者選其一或者由你親自整合到一起。比如你可以通過把這段內容替換為下面這樣來解決:

這個解決方案各採納了兩個分支中的一部分內容,而且我還刪除了 <<<<<<<,======= 和 >>>>>>> 這些行。在解決了所有檔案裡的所有衝突後,執行 git add 將把它們標記為已解決狀態(譯註:實際上就是來一次快照儲存到暫存區域。)。因為一旦暫存,就表示衝突已經解決。如果你想用一個有圖形介面的工具來解決這些問題,不妨執行git mergetool,它會呼叫一個視覺化的合併工具並引導你解決所有衝突:

Shell
1 2 3 4 5 6 7 $git mergetool merge tool candidates:kdiff3 tkdiff xxdiff meld gvimdiffopendiff emerge vimdiff Mergingthe files:index.html Normal merge conflict

相關推薦

GitGit分支

本篇blog為轉載,因為這是我看過關於git的最清晰易懂的blog教程,以備後用 Git 分支 幾乎每一種版本控制系統都以某種形式支援分支。使用分支意味著你可以從開發主線上分離開來,然後在不影響主線的同時繼續工作。在很多版本控制系統中,這是個昂貴的過程,常常需要建

OSPFOSPF LSA

ospf lsa詳解 forwarding address OSPF LSA詳解OSPF V2版本中常用的主要有6類LSA,分別是Router-LSA、Network-LSA、Network-summary-LSA、ASBR-summary-LSA、AS-External-LSA、NSSA-LSA,接

大資料hdfsput許可權剖析與常用命令

–無論是對於hdfs的讀和寫,對於使用者來說都是無感知的、透明的操作,使用者並不關心資料如何讀出來如何寫進去的,只要返回一個結果告訴使用者資料讀出來了或寫進去了,至於怎麼讀怎麼寫,使用者並不關心 補充: 讀:hdfs dfs -ls / = hdfs dfs

ORACLE PL/SQL程式設計PL/SQL流程控制語句(不給規則,不成方圓)

DECLARE    v_first_name employees.first_name%TYPE;    v_job_id employees.job_id%TYPE;    v_salary employees.salary%TYPE;    v_sal_raise NUMBER(3,2); B

Linux核心程序flush-x:y

上一篇文章《裝置檔案與裝置號》當然不是突然穿插而來的自言自語,而是理解本文的前提,下面來看。是一類程序,這在系列的上一篇文章裡已經講到過,系統的絕大部分的bdi裝置都會有對應的flush-x:y核心程序,而這個x:y是對應bdi裝置的裝置號。 先看一下系統當前掛載的檔案系統

Git應用第三講本地分支的重要操作

前言 前情提要:Git應用詳解第二講:Git刪除、修改、撤銷操作 分支是git最核心的操作之一,瞭解分支的基本操作能夠大大提高專案開發的效率。這一講就來介紹一些分支的常見操作及其基本原理。 一、分支概述 在開發當中,往往需要分工合作。比如:小紅開發A功能,小明開發B功能,小剛開發C功能。如何才能做到三者並

[團隊協作發 + 自動化部署 Git + Gitlab + Jenkins + K8S + Docker]Git進階編

文件地址:https://download.csdn.net/download/panpan2018/10329739下期更新至:[團隊協作發 + 自動化部署 Git + Gitlab + Jenkins + K8S + Docker]之四:Git高階編

Git——Feature Branching最流行的工作流

在前面的 Git入門——團隊工作的基本工作模型 中 ,介紹了一種最基本的團隊工作模型。在這種模型里,所有人都工作在 master 上,寫完了的 commit 可以通過 push 來發送到中央倉庫,並且可以使用 pull 來獲取到 別人的最新 commit s。   這種工作模

Git——Feature Branching最流行的工作流

就是 上進 div 無法 進入 簡介 核心內容 同步 分享 在前面的 Git入門——團隊工作的基本工作模型 中 ,介紹了一種最基本的團隊工作模型。在這種模型裏,所有人都工作在 master 上,寫完了的 commit 可以通過 push 來發送到中央倉庫,並且可以使用 pu

Git 四 (標籤,分支

標籤 當某一個大版本完成之後,需要打一個標籤 作用: 記錄大版本 備份大版本程式碼 模擬經理打標籤 1.進入到經理的本地倉庫test007 cd Desktop/manager/test007/ 2.經理在本地打標籤 git tag -

GitGit內部原理

Git 內部原理 不管你是從前面的章節直接跳到了本章,還是讀完了其餘各章一直到這,你都將在本章見識 Git 的內部工作原理和實現方式。我個人發現學習這些內容對於理解 Git 的用處和強大是非常重要的,不過也有人認為這些內容對於初學者來說可能難以理解且過於複雜。正因如此我

Git基本原理

開發十年,就只剩下這套架構體系了! >>>   

Git開發第一講Git分割槽,配置與日誌

前言 曾經聽到過這樣一句話:不會git就不要敲程式碼了。細細品味確實有其中的道理,可能是當事人程式碼被強行覆蓋後的嘆息吧! 因此,為了避免這種情況,接下來我們就一起來好好學習git的相關知識吧!不怕你不會,就怕你不看! 一、git的三個分割槽: 工作區(working directory) 暫存區(st

Git應用第二講Git刪除、修改、撤銷操作

前言 前情提要:Git應用詳解第一講:Git分割槽,配置與日誌 在第一講中我們對Git進行了簡單的入門介紹,相信聰明的你已經瞭解Git的基本使用了。 這一講我們來進一步深入學習Git應用,著重介紹Git的一些常見操作,包括:刪除檔案、比較檔案、撤銷修改、修改註釋與檢視幫助文件。 一、刪除檔案 1.gi

第九課--09_01_磁盤及文件系統管理

lock 多系統 otl rtx 塊大小 ble 當前 part 文件 一、VFS (Virtual File System)1: 用戶模式--用戶空間--用戶進程進程以模式的形式運行在的空間--用戶空間2:內核模式--內核空間3:block size : 1024-1k,

[轉]javaCV開發5錄製音訊(錄製麥克風)到本地檔案/流媒體伺服器(基於javax.sound、javaCV-FFMPEG)

本文轉載自部落格:https://blog.csdn.net/eguid_1/article/details/52702385 ------------------------------------------------------------------------------------

ALSA音效卡驅動中的DAPMwidget-具備路徑和電源管理資訊的kcontrol

上一篇文章中,我們介紹了音訊驅動中對基本控制單元的封裝:kcontrol。利用kcontrol,我們可以完成對音訊系統中的mixer,mux,音量控制,音效控制,以及各種開關量的控制,通過對各種kcontrol的控制,使得音訊硬體能夠按照我們預想的結果進行工作。同時我

ALSA音效卡驅動中的DAPMdapm事件機制(dapm event)

前面的六篇文章,我們已經討論了dapm關於動態電源管理的有關知識,包括widget的建立和初始化,widget之間的連線以及widget的上下電順序等等。本章我們準備討論dapm框架中的另一個機制:事件機制。通過dapm事件機制,widget可以對它所關心的dapm事

ALSA音效卡驅動中的DAPM建立widget之間的連線關係

前面我們主要著重於codec、platform、machine驅動程式中如何使用和建立dapm所需要的widget,route,這些是音訊驅動開發人員必須要了解的內容,經過前幾章的介紹,我們應該知道如何在alsa音訊驅動的3大部分(codec、platform、machin

Oracle記憶體 Shared pool 共享池

一. Shared Pool 概述             在之前的blog對Oracle 的記憶體架構也做了一個概述,參考:             在網上搜到一篇介紹shared pool 非常詳細的pdf資料。 原文連結以找不到,但還是要感謝作者Kamus的辛勤