Git(12)-- Git 分支 - 分支簡介

2021AY發表於2021-08-22

@

[超詳細 Git 圖文版小白教程(持續更新)](https://blog.csdn.net/m0_46278037/article/details/119801677)

幾乎所有的版本控制系統都以某種形式支援分支。 使用分支意味著你可以把你的工作從開發主線上分離開來,以免影響開發主線。 在很多版本控制系統中,這是一個略微低效的過程——常常需要完全建立一個原始碼目錄的副本。對於大專案來說,這樣的過程會耗費很多時間。

有人把 Git 的分支模型稱為它的“必殺技特性”,也正因為這一特性,使得 Git 從眾多版本控制系統中脫穎而出。 為何 Git 的分支模型如此出眾呢? Git 處理分支的方式可謂是難以置信的輕量,建立新分支這一操作幾乎能在瞬間完成,並且在不同分支之間的切換操作也是一樣便捷。 與許多其它版本控制系統不同,Git 鼓勵在工作流程中頻繁地使用分支與合併,哪怕一天之內進行許多次。 理解和精通這一特性,你便會意識到 Git 是如此的強大而又獨特,並且從此真正改變你的開發方式。

1、分支簡介

為了真正理解 Git 處理分支的方式,我們需要回顧一下 Git 是如何儲存資料的。

Git 儲存的不是檔案的變化或者差異,而是一系列不同時刻的 快照(snapshot)

在進行提交操作時,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 就可以在需要的時候重現此次儲存的快照。

1.1、初始化並首次提交

新建testbranch資料夾,右鍵,點Git Bash Here
在這裡插入圖片描述
初始化為Git倉庫:

git init
git status

在這裡插入圖片描述
新建READMEtest.rbLICENSE三個檔案,此時這三個檔案內容都為空:
在這裡插入圖片描述

git status

在這裡插入圖片描述

新增到暫存區並提交:

git add README test.rb LICENSE
git status

在這裡插入圖片描述

git commit -m 'The initial commit of my project'

在這裡插入圖片描述

現在,Git 倉庫中有個物件:三個 blob 物件(儲存著檔案快照)、一個 物件 (記錄著目錄結構和 blob 物件索引)以及一個 提交 物件(包含著指向前述樹物件的指標和所有提交資訊)。

先放總結圖:

首次提交物件及其樹結構:

在這裡插入圖片描述

git中,blob與檔名無關,只與檔案內容有關,即檔案內容相同,在git中是同一個blob

用命令詳細檢視:

git log

在這裡插入圖片描述

gitcat-file 的命令用法:

git cat-file 的命令顯示版本庫物件的內容、型別、及大小資訊。

-t
Instead of the content, show the object type identified by object.
顯示物件的型別。

-s
Instead of the content, show the object size identified by object.
顯示物件的大小。

-e
Suppress all output; instead exit with zero status if object exists and is a valid object.
如果物件存在且有效,命令結束狀態返回值為0。

-p
Pretty-print the contents of object based on its type.
根據物件的型別,以優雅的方式顯示物件的內容

git cat-file -p f79e0ab69485aad8a166921f6113831b95f30ad1

在這裡插入圖片描述

git cat-file -p 5686f60e688ae43df28beea0f49055e605cfc0ba

在這裡插入圖片描述

再次強調:
git中,blob與檔名無關,只與檔案內容有關,即檔案內容相同,在git中是同一個blob

以上是第一次提交。做些修改後再次提交,那麼這次產生的提交物件會包含一個指向上次提交物件(父物件)的指標。

1.2、修改並第二次提交

修改三個檔案後再提交:

git status
git add README test.rb LICENSE
git commit -m 'Two commit of my project'

在這裡插入圖片描述

git log

在這裡插入圖片描述

git cat-file -p 4c01aba72850b2fc4839aa75cba3c92adcb7df02

在這裡插入圖片描述

git cat-file -p 8da9abb302345ac3855ab05ec79c52ce95c58feb

在這裡插入圖片描述

git cat-file -p 7153628ea778a52e8acb0b78701df26a8b4f19e4
git cat-file -p d2fb9a2df3a1cfd26f5941bd3f19f54d1da2d09a
git cat-file -p 35ce91f3f87d134161a22067d485c52cd9deb612

在這裡插入圖片描述

第二次提交物件及其樹結構:

在這裡插入圖片描述

1.3、修改並第三次提交

第三次修改並提交:

git status
git add README test.rb LICENSE

在這裡插入圖片描述

git status
git commit -m '3 commit'

在這裡插入圖片描述

git log

在這裡插入圖片描述

 git cat-file -p bce8f7b

在這裡插入圖片描述

git cat-file -p 455f296

在這裡插入圖片描述

git cat-file -p 11e6485
git cat-file -p 389ed9c
git cat-file -p 50c0fe6

在這裡插入圖片描述

提交物件及其父物件圖:

在這裡插入圖片描述

Git 的分支,其實本質上僅僅是指向提交物件的可變指標。 Git 的預設分支名字是 main。 在多次提交操作之後,你其實已經有一個指向最後那個提交物件的 main 分支。 main 分支會在每次提交時自動向前移動。

Git 的 main 分支並不是一個特殊分支。 它就跟其它分支完全沒有區別。 之所以幾乎每一個倉庫都有 main 分支,是因為git init 命令預設建立它,並且大多數人都懶得去改動它。

git log -p -1

在這裡插入圖片描述

git tag -a v1.0 -m "first version"
git tag

在這裡插入圖片描述

git log -p -1

在這裡插入圖片描述

分支及其提交歷史圖:

在這裡插入圖片描述

2、分支建立

Git 是怎麼建立新分支的呢? 很簡單,它只是為你建立了一個可以移動的新的指標。 比如,建立一個 testing 分支, 你需要使用 git branch 命令:

git branch testing

這會在當前所在的提交物件上建立一個指標。

兩個指向相同提交歷史的分支圖:

在這裡插入圖片描述

那麼,Git 又是怎麼知道當前在哪一個分支上呢? 也很簡單,它有一個名為 HEAD 的特殊指標。 請注意它和許多其它版本控制系統(如 SubversionCVS)裡的 HEAD 概念完全不同。 在 Git 中,它是一個指標,指向當前所在的本地分支(譯註:將 HEAD 想象為當前分支的別名)。 在本例中,你仍然在 main 分支上。 因為 git branch 命令僅僅 建立 一個新分支,並不會自動切換到新分支中去。

HEAD 指向當前所在的分支圖:

在這裡插入圖片描述
你可以簡單地使用 git log 命令檢視各個分支當前所指的物件。 提供這一功能的引數是 --decorate

git branch testing
git log --oneline --decorate

在這裡插入圖片描述
正如你所見,當前 maintesting 分支均指向校驗和以bce8f7b開頭的提交物件。

3、分支切換

要切換到一個已存在的分支,你需要使用 git checkout 命令。 我們現在切換到新建立的 testing 分支去:

git checkout testing

這樣 HEAD 就指向 testing 分支了。

HEAD 指向當前所在的分支圖:

在這裡插入圖片描述

git checkout testing
git log --oneline --decorate

在這裡插入圖片描述
那麼,這樣的實現方式會給我們帶來什麼好處呢? 現在不妨再提交一次:

git status
git commit -a -m 'made a change'
git log --oneline --decorate

在這裡插入圖片描述
如圖所示,你的 testing 分支向前移動了,但是 main分支卻沒有,它仍然指向執行 git checkout 時所指的物件:

HEAD 分支隨著提交操作自動向前移動圖:

在這裡插入圖片描述
現在我們切換回 main分支看看:

git checkout main
git log --oneline --decorate

在這裡插入圖片描述

檢出時 HEAD 隨之移動圖:

在這裡插入圖片描述

這條命令做了兩件事。 一是使 HEAD 指回 main 分支,二是將工作目錄恢復成 main 分支所指向的快照內容。 也就是說,你現在做修改的話,專案將始於一個較舊的版本。 本質上來講,這就是忽略 testing 分支所做的修改,以便於向另一個方向進行開發。

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

我們不妨再稍微做些修改並提交:

git status
git commit -a -m 'made other changes'
git log --oneline --decorate

在這裡插入圖片描述
現在,這個專案的提交歷史已經產生了分叉。 因為剛才你建立了一個新分支,並切換過去進行了一些工作,隨後又切換回 main分支進行了另外一些工作。 上述兩次改動針對的是不同分支:你可以在不同分支間不斷地來回切換和工作,並在時機成熟時將它們合併起來。 而所有這些工作,你需要的命令只有 branchcheckoutcommit

專案分叉歷史圖:

在這裡插入圖片描述
你可以簡單地使用 git log 命令檢視分叉歷史。 執行 git log --oneline --decorate --graph --all ,它會輸出你的提交歷史、各個分支的指向以及專案的分支分叉情況。
在這裡插入圖片描述
由於 Git 的分支實質上僅是包含所指物件校驗和(長度為 40SHA-1 值字串)的檔案,所以它的建立和銷燬都異常高效。 建立一個新分支就相當於往一個檔案中寫入 41 個位元組(40 個字元和 1 個換行符),如此的簡單能不快嗎?

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

建立新分支的同時切換過去: 通常我們會在建立一個新分支後立即切換過去,這可以用 git checkout -b<newbranchname> 一條命令搞定。

在這裡插入圖片描述