理解 Git

Git 如何保存文件

其它版本管理系統一般會保存全部文件及其歷次提交的差別(diff / revision),經過 merge 原始文件與各階段的差別就能獲取任何版本的狀態vue

而 Git 保存的是每一次提交時全部文件的快照(snapshot),對於發生改變(modified)的文件會生成新的快照,而對於未發生改變的文件,其新版本快照爲上一個版本的快照的索引(圖中虛線框所示),這樣能夠減少版本庫的體積git

這裏比較費解的是:快照到底是什麼?數據庫

簡單的理解:快照就是壓縮文件,只不過 git 會將文件內容壓縮爲 blob 格式,例如僅含一段 hello world 的 txt 文件壓縮後的內容爲:測試

7801 4bca c94f 5230 3462 c848 cdc9 c957
28cf 2fca 49e1 0200 4411 0689spa

全部文件快照都會被儲存在 .git 倉庫文件夾下的 objects 目錄中設計

經測試,一份 200k 的未經壓縮的代碼文件,其文件快照大小約 65k3d

文件名 eef...542 是根據內容生成的 40 位哈希字符串,文件名 + 文件自己就構成了一組鍵值對。全部文件都以這種形式保存,而 objects 目錄就是一個以鍵值對形式保存文件的數據庫指針

能夠想象,隨着版本不斷迭代,.git 倉庫目錄的體積每每會超過工做區全部文件的體積之和,由於哪怕只作了一丁點的改變,git 都會從新生成快照。以下圖所示,我僅僅刪掉了 vue.runtime.js 的一行註釋,而後執行 `git add -A`,.git 中就從新生成了一份快照對象

一個長期維護的代碼庫,其代碼總量可能只有幾 MB,但 .git 徹底可能大到以 G 計blog

比起其它版本管理系統僅僅記錄差別,git 的這種作法不是顯得更浪費空間嗎?git 之所這麼設計,是出於「空間換時間」的考慮。用過 SVN 的人都知道要從一個幾百 MB 的項目庫開出一個分支是多麼費時,而使用 git 開分支,不管體積有多大,都是一瞬間的事情,緣由就是兩者的「分支」的原理徹底不一樣

 

Git 如何保存文件版本

理解了 git 保存文件的方式,就很容易理解其保存版本的方式:採用一個樹對象來表示目錄結構與文件

root: {

  sub1: {

     hash

     hash

     ...

  }

  sub2: {

     hash

     hash

     ...

  }

}

根據文件索引就能夠直接從數據庫中取出文件,而後再按樹對象表徵的目錄結構進行組合排列,就很容易恢復出一套文件版本

每次 commit 除了保存樹對象之外,還會記錄提交的做者、批註、上一次提交的索引等信息,每一個 commit 都會根據內容生成一個 hash 做爲其惟一的索引

而每次 commit 就是一個版本記錄

能夠看到,全部的 commit 造成了一個鏈表,而這個鏈表有一個形象的名稱:分支

但咱們最好不要把分支這個概念當作是一條「鏈」,而應該當作是某個版本(commit)的索引,咱們說合並兩個分支,合併的不是兩條鏈,而是兩個版本(commit)

 

Git 開分支的原理

git 分支的本質,就是指向某個特定 commit 的指針,假設當前只有一個分支,默認就叫作 master,當前已是第三個提交了:

{

  master: commit-3

}

那麼開一個分支,無非就是新建立一個指針:

{

  master: commit-3

  dev: commit-3

}

當前用戶處於哪一個分支,須要用另外一個指針來表示:

{

  HEAD: master

}

執行 `git checkout dev` 切換分支後:

{

  HEAD: dev

}

在 dev 分支提交一次 commit 後:

{

  master: commit-3

  dev: commit-4

}

切回 master,執行 `git branch -d dev` 刪除分支:

{

  master: commit-3

}

master 分支其實並無什麼特殊之處,它和其它分支本質是同樣的,只不過它是初始化項目時的默認分支,同時在項目開發中約定做爲主分支

 

Git 合併分支的策略

兩個分支的合併只有兩種狀況:無分叉、有分叉

無分叉的情形最簡單,合併分支就把 master 指向的 commit 更換爲最新的 commit

{

  master: commit-3

  dev: commit-4

}

merge:

{

  master: commit-4

  dev: commit-4

}

這種策略被稱爲 fast forward

有分叉的狀況稍微麻煩一些,git 會將兩個分支的分叉點和頭部的 commit 作一次三方合併,而後造成一個新的 commit:

顯然第一種方式最簡便,那有沒有辦法在分叉的狀況下仍然採用 fast forward 的策略呢,有

在 experiment 分支上執行 `git rebase master`,首先會計算出分叉點與 experiment 分支頭部的兩個 commit 的差別,而後以 C3 爲新的基礎,整合以前計算出的差別,獲得一個新的 commit

    var patch = C4 - C2

  var C4` = C3 + patch

  C4`.parent = C3

rebase 就是改變基礎的意思。這下回到 master 分支執行 merge 操做,就能夠實現 fast forward 了

相關文章
相關標籤/搜索