04、 git存儲原理以及分支創建與合併

大家好,我們在上上節留了三個問題,git的版本回退和gitignore在上一節做個簡單闡述,本節我們針對git分支管理展開介紹一下。
本節內容預告:

  1. 分支解決了什麼問題
  2. 分支常用命令介紹
  3. git存儲原理
  4. 分支合併以及可能衝突解決
  5. 總結回顧

1.分支解決了什麼問題?

看過我前面博客的話,相信對這個問題並不陌生,甚至已經有自己的答案。這裏還是簡單說一下,因爲帶着問題看東西,效率會相對來說高一點,學技術之前我們先要明白爲什麼學這個東西,而不是遇見一個東西就埋頭苦學。畢竟時間和精力都有限,好了,話不多少,進入正題。

日常開發中你很可能會遇見一個事情是這樣子的,提給開發人員的需求並不是一個一個來的,經常會遇見一種情況是兩個甚至多個需求同時過來,但是因爲這兩個需求的上線節點不一致等原因,你不能將功能開發到一塊,不然可能上線後有其他問題

這時候你可以選擇自己本地人爲的做個備份,即在一個項目開發需求 A,在複製一份代碼到其他地方開發需求B,兩個項目彼此獨立,這樣是可以滿足基本需求,但是很低級而且後面的需求B上線的時候需要在原來的基礎上加上已經上線的A的變更,如果都是人爲的話很花時間而且容易出錯!!!

所有就有了分支的概念,你可以針對不同的需求建立不同的分支,因爲兩個分支的內容互不影響,這樣可以實現需求A先上線且不會帶着開發到一半的需求B的內容。另外,到時候需求B需要在需求A的基礎上上線的時候,git也提供了很方便的方式可以方便快捷的將需求A的分支代碼合併到需求B的分支上面。

2.分支常用命令介紹

  1. git branch
    這個命令後面不帶任何參數的時候,顯示當前git有多少分支。

  2. git branch 新的分支名字
    這個命令會基於當前分支創建一個新的分支,因爲git初始化就有一個master分支,所以第一次創建分支肯定是從master上創建的一個分支
    在這裏插入圖片描述
    這裏可以看到,因爲我剛開始初始化了一個新的倉庫,裏面還沒有任何文件,這時候創建分支git會報一個錯,提示沒有有效的對象,隨便將一個文件加到版本庫後,重新創建分支branch1,然後通過命令git branch查看,發現當前有兩個分支,一個是git初始化就有的默認分支master主分支,還有一個是剛纔新建的分支branch1

  3. git checkout 分支名字
    可以看到,剛開始我們在master分支,當前目錄有一個之前創建好的test.txt文件,通過命令git checkout branch1切換到之前創建的分支branch1上面,因爲分支branch1是基於master創建的,所以目前兩個分支內容一致。
    然後我們在分支branch1上面新增一個文件test2.txt,可以看到這個目錄有兩個文件,並提交到版本庫上面,然後再切換回分支master,發現master分支的工作目錄下面仍然是一個文件,這印證了我們剛開始說的,通過分支,可以實現分支之間的隔離開發,兩個分支的內容可以完全不同,這樣我們就可以針對不同的需求創建不同的分支。

    在切換分支時,一定要注意你工作目錄裏的文件會被改變。 如果是切換到一個較舊的分支,你的工作目錄會恢復到該分支最後一次提交時的樣子。 如果 Git 不能幹淨利落地完成這個任務,它將禁止切換分支。
    在這裏插入圖片描述

  4. git branch -d 分支名字
    我們先使用git branch命令查看有哪些分支,然後創建一個新的分支branch2,執行命令git branch -d branch2後,git提示刪除成功,然後再執行命令git branch -d branch1,git提示刪除失敗,並說明原因是因爲分支1有沒合併的內容。這是因爲git默認是你刪除的分支內容肯定已經合併到其他分支,如果你刪除的分支有部分代碼沒合併,那麼git會有報錯提示,並告訴你可以用參數-D確認刪除,相當於給開發者一個二次確認的機會,防止因爲誤操作而刪除了部分沒有提交的內容。可以看到,使用-D後,提示分支刪除。日常開發中,建議我們使用-d,防止誤刪沒合併的文件。
    在這裏插入圖片描述

  5. git branch -v
    顯示當前分支最近一次的提交記錄

  6. git branch -b 分支名字
    相當於創建分支和切換分支組合使用命令
    在這裏插入圖片描述

3. git存儲原理

git分支的原理這塊比較深奧,一直在猶豫要不要引入這個話題,攤開了說太大,很繁瑣,說的少了說不清楚,不說的話下面涉及到分支合併可能讀者更是一頭霧水,所以權衡再三,還是在可控範圍內拋磚引玉。

Git 和其它版本控制系統(包括 Subversion 和近似工具)的主要差別在於 Git 對待數據的方法。 從概念上來說,其它大部分系統以文件變更列表的方式存儲信息,這類系統(CVS、Subversion、Perforce、Bazaar 等等) 將它們存儲的信息看作是一組基本文件和每個文件隨時間逐步累積的差異 (它們通常稱作 基於差異(delta-based) 的版本控制)。
在這裏插入圖片描述
Git 中所有的數據在存儲前都計算校驗和,然後以校驗和來引用。 這意味着不可能在 Git 不知情時更改任何文件內容或目錄內容。 這個功能建構在 Git 底層,是構成 Git 哲學不可或缺的部分。 若你在傳送過程中丟失信息或損壞文件,Git 就能發現。

Git 用以計算校驗和的機制叫做 SHA-1 散列(hash,哈希)。 這是一個由 40 個十六進制字符(0-9 和 a-f)組成的字符串,基於 Git 中文件的內容或目錄結構計算出來。 SHA-1 哈希看起來是這樣:24b9da6552252987aa493b52f8696cd6d3b00373

Git 中使用這種哈希值的情況很多,你將經常看到這種哈希值。 實際上,Git 數據庫中保存的信息都是以文件內容的哈希值來索引,而不是文件名。
在這裏插入圖片描述
Git 保存的不是文件的變化或者差異,而是一系列不同時刻的 快照 。

在進行提交操作時,Git 會保存一個提交對象(commit object)。 知道了 Git 保存數據的方式,我們可以很自然的想到——該提交對象會包含一個指向暫存內容快照的指針。 但不僅僅是這樣,該提交對象還包含了作者的姓名和郵箱、提交時輸入的信息以及指向它的父對象的指針。 首次提交產生的提交對象沒有父對象,普通提交操作產生的提交對象有一個父對象, 而由多個分支合併產生的提交對象有多個父對象,
爲了更加形象地說明,我們假設現在有一個工作目錄,裏面包含了三個將要被暫存和提交的文件。 暫存操作會爲每一個文件計算校驗和(使用我們在 起步 中提到的 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 對象索引)以及一個 提交 對象(包含着指向前述樹對象的指針和所有提交信息)。
在這裏插入圖片描述
做些修改後再次提交,那麼這次產生的提交對象會包含一個指向上次提交對象(父對象)的指針。
在這裏插入圖片描述
由於 Git 的分支實質上僅是包含所指對象校驗和(長度爲 40 的 SHA-1 值字符串)的文件,所以它的創建和銷燬都異常高效。 創建一個新分支就相當於往一個文件中寫入 41 個字符(40 個字符和 1 個換行符),如此的簡單能不快嗎?

這與過去大多數版本控制系統形成了鮮明的對比,它們在創建分支時,將所有的項目文件都複製一遍,並保存到一個特定的目錄。 完成這樣繁瑣的過程通常需要好幾秒鐘,有時甚至需要好幾分鐘。所需時間的長短,完全取決於項目的規模。 而在 Git 中,任何規模的項目都能在瞬間創建新分支。 同時,由於每次提交都會記錄父對象,所以尋找恰當的合併基礎(譯註:即共同祖先)也是同樣的簡單和高效。 這些高效的特性使得 Git 鼓勵開發人員頻繁地創建和使用分支。

上面內容是我從官網拉下來的一些內容,初看會有點繁瑣,而且有點難以理解,我這邊根據我的理解對一些內容做個說明:
git存儲我們提交到暫存區的每個文件都會有加密算法sha1根據文件內容生成一個id,這個id類似一個索引,我們可以根據id找到文件,然後每次提交的時候,git又會將每個子目錄的暫存區文件id和目錄結構等保存成一個樹對象,最後生成一個commit對象保存樹對象的指針。這個commit對象的sha1就是我們之前說過的commitid。

這個commit對象還會保存他的上一次commit對象的指針爲parent,第一次parent保存的parent是空的,這樣我們就能我才能夠當前對象回溯到前面的版本。

4.分支合併以及分支衝突解決

1. 分支快進合併

我們從master分支新拉一個分支branch3,這樣branch3實際上和master分支內容一樣,然後我們切換到分支branch3,修改一個文件,並提交。使用命令git merge 分支名字會將對應分支合併到當前分支,如果需要將branch3的內容合併到master上面,切換到master之後,執行命令git merge branch3,可以看到,git提示我們有個更新,特別留意這邊有個fast-forward。然後在master分支 查看test.txt 文件內容發現文件內容確實包含了分支branch3提交的內容。
在這裏插入圖片描述
在合併的時候,你應該注意到了“快進(fast-forward)”這個詞。 由於你想要合併的分支 branch3 所指向的提交 C4 是你所在的提交 C2 的直接後繼, 因此 Git 會直接將指針向前移動。換句話說,當你試圖合併兩個分支時, 如果順着一個分支走下去能夠到達另一個分支,那麼 Git 在合併兩者的時候, 只會簡單的將指針向前推進(指針右移),因爲這種情況下的合併操作沒有需要解決的分歧——這就叫做 “快進(fast-forward)”。
在這裏插入圖片描述
合併完之後master和branch3指向同一個提交
在這裏插入圖片描述
這裏需要留意,因爲快進提交只是一個指針的換位,默認是不會生成一個新的提交id。 可以和下面的情況多對比

2. 分支三路合併以及遞歸三路合併

上面說的情況是我們在從master創建分支branch3後,沒有在master上面進行過修改提交,如果master和branch3都修改了會怎麼樣呢?
我們在兩個分支修改了test.txt 文件的同一行,此時同樣執行合併命令
git merge 分支名字,git提示我們自動合併失敗,需要修改衝突,手動合併,這種情況也是我們會經常遇到的。在這裏插入圖片描述
通過git status看到git提示有一個爲合併的路徑,提示我們解決衝突並提交文件,解決完衝突後用git add 命令標記這個文件衝突解決完畢。
在這裏插入圖片描述
使用linux的命令vi test.txt編輯衝突文件test.txt可以看到:
在這裏插入圖片描述
可以看到 <<<<<<< 和=======之間的內容就是我們在branch3也就是當前分支做的修改 , =======和 >>>>>>>之間的內容是我們在被合併的分支master上面做的修改,這種衝突是git無法解決的,因爲git不知道用戶真實是想要那個版本的修改,所以需要用戶自行解決。

如果我們兩個版本的修改都想要的話,刪除三個標識行的特殊字符,然後esc健退出編輯模式,shift + :+ wq! 鍵保存退出解決衝突,使用git add添加到暫存區,這裏需要留意, 對每個文件使用 git add 命令來將其標記爲衝突已解決。 一旦暫存這些原本有衝突的文件,Git 就會將它們標記爲衝突已解決。
在這裏插入圖片描述
這和之前合併 branch3 分支的時候看起來有一點不一樣。 在這種情況下,你的開發歷史從一個更早的地方開始分叉開來(diverged)。 因爲,master 分支所在提交並不是 branch3 分支所在提交的直接祖先,Git 不得不做一些額外的工作。 出現這種情況的時候,Git 會使用兩個分支的末端所指的快照(C4 和 C3)以及這兩個分支的工作祖先(C2),做一個簡單的三方合併。
在這裏插入圖片描述
和之前將分支指針向前推進所不同的是,Git 將此次三方合併的結果做了一個新的快照並且自動創建一個新的提交指向它。 這個被稱作一次合併提交,它的特別之處在於他有不止一個父提交,然後當前分支指向最新的提交。
在這裏插入圖片描述
細心的話,會發現我在開始復現三方合併,修改文件的時候強調的是同一個文件,同一行,那麼其他情況呢?

這裏需要留意如果兩個分支修改的是同一個文件不同行,或者不同文件的話,是不是衝突的,git會默認幫我們做合併動作,但是三方合併還是會生成一個新的commit id,這裏就不羅列種種情況了,感興趣可以自己嘗試復現。

那麼遞歸三方合併又是什麼場景呢?
我們合併 ⑥ 和 ⑦ 的時候,我們將其 2 個公共祖先③ 和 ④ 進行 merge 爲 X ,在合併 ③ 和 ④時候 我們又需要找到 他們的公共祖先,此時可能又有多個公共祖先,我們又需要將他們先進行合併,如此就是遞歸了 也就是 recursive merge,
在這裏插入圖片描述

5.總結回顧

剛開始我們留了一個問題,git分支解決了什麼問題,通過後面對git數據存儲結構和分支存儲形式的瞭解,可以看到git分支的強大之處,後面學習了git分支的常見操作命令,最後通過舉例和畫圖的方式簡述了git分支的快進合併和三路合併,希望讀者還是能自己手動逐個實踐上面的命令,因爲只有自己敲了,纔會遇見實際的問題,通過解決一個個問題來逐漸熟悉git的使用,順便加深對命令的理解。

因爲筆者能力和精力有限,實際很難考慮完全使用過程中遇見的各種問題,也不可能針對遇見的每種情況都展開詳述,所以有什麼問題歡迎評論區討論。

最後,感謝閱讀,如有錯誤,請不吝指正
———————————————————————————————————
參考資料:

  1. git官網

  2. https://segmentfault.com/a/1190000021712743

  3. https://blog.csdn.net/longintchar/article/details/83049840

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章