Git Pro深刻淺出(三)

7、本身定義Git

前面已經闡述了Git基本的運做機制和使用方式,介紹了不少Git提供的工具來幫助你簡單且有效地使用它。本部分將演示怎樣藉助Git的一些重要的配置方法和鉤子機制,來知足本身定義的需求。javascript

1. 配置Git

(1)配置Git
可以用git config 配置Git。java

$ git config --global user.name "ligang"
$ git config --global user.email "ligang@example.com"

高速回顧下:Git使用一系列配置文件來保存你本身定義的行爲。node

  • 假設傳遞 –system 選項給git config。它就會讀寫 /etc/gitconfig 文件;
  • 假設傳遞 –global 選項給git config。會查找每一個用戶的 ~/.gitconfig 文件。
  • 假設不傳遞不論什麼選項給git config,會查找你正在操做的版本號庫所相應的Git文件夾下的.git/config配置文件。

以上三個層次中每層的配置(系統、全局、本地)都會覆蓋掉上一層次的配置:本地 > 全局 > 系統
(2)Git中的着色
Git會本身主動着色大部分輸出內容。但假設你不喜歡花花綠綠,也可以關掉。git

# 這個設置的默認值是auto
$ git config --global color.ui false
# 詳細設置某項,diff的輸出信息以藍色前景、黑色背景和粗體顯示
$ git config --global color.diff.meta "blue black bold"

(3)格式化與多餘的空白字符
如你正在 Windows 上敲代碼,而你的同伴用的是其它系統(或相反),你可能會遇到CRLF問題。這是因爲Windows使用回車(CR)和換行(LF)兩個字符來結束一行,而 Mac 和 Linux 僅僅使用換行(LF)一個字符。 儘管這是小問題。但它會極大地擾亂跨平臺協做。
假設是在Windows系統上,把它設置成 true。這樣在檢出代碼時,換行會被轉換成回車和換行:github

$ git config --global core.autocrlf true

假設使用以換行做爲行結束符的 Linux 或 Mac,設置成input來告訴Git在提交時把回車和換行轉換成換行。檢出時不轉換:sql

$ git config --global core.autocrlf input

假設,所有人使用一樣的系統開發,則可以取消該功能:數據庫

$ git config --global core.autocrlf false

Git 預先設置了一些選項來探測和修正多餘空白字符問題。ruby

它提供了六種處理多餘空白字符的主要選項。bash

選項 說明 是否默認開啓
blank-at-eol 查找行尾的空格 默認開啓
blank-at-eof 盯住文件底部的空行 默認開啓
space-before-tab 警戒行頭tab前面的空格 默認開啓
indent-with-non-tab 揪出以空格而非tab開頭的行 默認關閉
tab-in-indent 監視在行頭表示縮進的 tab 默認關閉
cr-at-eol 告訴 Git 忽略行尾的回車 默認關閉

多個選項以逗號切割,經過逗號切割的鏈中去掉選項或在選項前加-來關閉。markdown

# 開啓indent-with-non-tab,cr-at-eol模式
$ git config --global core.whitespace "indent-with-non-tab,cr-at-eol"
# 在打上補丁前本身主動修正此問題
$ git apply --whitespace=fix <patch>

2. Git屬性

你也可以針對特定的路徑配置某些設置項,這樣Git就僅僅對特定的子文件夾或子文件集運用它們。

這些基於路徑的設置項被稱爲Git屬性,可以在你的文件夾下的 .gitattributes 文件內進行設置(通常是你的項目的根文件夾)。假設不想讓這些屬性文件與其它文件一同提交,你也可以在 .git/info/attributes 文件裏進行設置。

經過使用屬性。你可以對項目中的文件或文件夾單獨定義不一樣的合併策略,讓Git知道怎樣比較非文本文件。或者讓Git在提交或檢出前過濾內容。


(1)導出版本號庫
當歸檔的時候。可以設置Git不導出某些文件和文件夾。

假設你不想在歸檔中包括某個子文件夾或文件,但想把它們歸入項目的版本號管理中。你可以在export-ignore屬性中指定它們。

# 在項目導出的壓縮包中,不包括test/文件夾下文件
test/ export-ignore

(2)合併策略
經過Git屬性,你還能對項目中的特定文件指定不一樣的合併策略。

一個很實用的選項就是,告訴Git當特定文件發生衝突時不要嘗試合併它們,而是直接使用你這邊的內容。

# 合併分支時,確保database.xml選擇咱們的,不被覆蓋
database.xml merge=ours # 定義一個虛擬的合併策略,叫作ours $ git config --global merge.ours.driver true $ git merge topic Auto-merging database.xml Merge made by recursive.

3. Git鉤子

Git 能在特定的重要動做發生時觸發本身定義腳本。 有兩組這種鉤子:client的和server端的。
client鉤子由諸如提交和合並這種操做所調用,而server端鉤子做用於諸如接收被推送的提交這種聯網操做。
鉤子都被存儲在.git/hooks文件夾下,默認以.sample結尾。

假設你想啓用它們,得先移除這個後綴。
圖 鉤子1.png

3.1 client鉤子

client鉤子可分爲:提交工做流鉤子電子郵件工做鉤子其它鉤子


(1)提交工做流鉤子

  1. pre-commit 鉤子在鍵入提交信息前執行。

    它用於檢查即將提交的快照。好比。檢查是否有所遺漏。確保測試執行,以及覈查代碼。


    假設該鉤子以非零值退出,Git將放棄這次提交,只是你可以用 git commit --no-verify 來繞過這個環節。

  2. prepare-commit-msg
    鉤子在啓動提交信息編輯器以前。默認信息被建立以後執行。你可以結合提交模板來使用它,動態地插入信息。
  3. commit-msg
    鉤子接收一個參數。此參數即上文提到的,存有當前提交信息的暫時文件的路徑。可以對提交信息是否遵循指定的模板校驗。
  4. post-commit 鉤子在整個提交過程完畢後執行。該鉤子通常用於通知之類的事情。

(2)電子郵件工做流鉤子
都是由 git am 命令調用的。

applypatch-msg --> pre-applypatch --> post-applypatch

(3) 其它client鉤子

  1. pre-rebase 鉤子執行於變基以前
  2. post-rewrite 鉤子替換提交記錄的命令調用
  3. post-checkout 鉤子checkout 成功執行後調用
  4. post-merge 鉤子merge 成功執行後調用
  5. pre-push 鉤子push 執行後調用
3.2 服務端鉤子

服務端鉤子在推送到server以前和以後執行

  1. pre-receive
    處理來自client的推送操做時,假設它以非零值退出,所有的推送內容都不會被接受。可以用這個鉤子阻止對引用進行非快進(non-fast-forward)的更新,或者對該推送所改動的所有引用和文件進行訪問控制。
  2. update 和pre-receive 腳本十分類似。不一樣之處在於它會爲每一個準備更新的分支各執行一次。

  3. post-receive 在整個過程完結之後執行,可以用來更新其它系統服務或者通知用戶。

8、Git與其它系統

假設你的代碼眼下不在Git上。但是想遷移到Git。可以參考如下兩個地址:
Git與其它系統 - 做爲client的Git
Git與其它系統 - 遷移到Git

9、Git內部原理

瞭解Git內部原理對於理解Git的用途和強大相當重要。
Git是一個內容尋址(content-addressable)文件系統。並在此之上提供了一個版本號控制系統的用戶界面。

1. 底層命令和高層命令

以前章節講述的友好的命令,基本都是「高層」命令。


底層命令得以讓你窺探Git內部的工做機制,也有助於說明Git是怎樣完畢工做的,以及它爲什麼如此運做。多數底層命令並不面向終於用戶:它們更適合做爲新命令和本身定義腳本的組成部分。

當在一個新文件夾或已有文件夾執行git init時,Git會建立一個.git文件夾。

這個文件夾包括了差點兒所有Git存儲和操做的對象。如若想備份或複製一個版本號庫,僅僅需把這個文件夾拷貝至還有一處就能夠。

$ cd .git
$ ls -F1
HEAD            # 指示眼下被檢出的分支
index           # 保存暫存區信息
config*         # 包括項目特有的配置選項
description     # 僅供GitWeb程序使用。咱們無需關心
hooks/          # 包括client或服務端的鉤子腳本
info/           # 包括一個全局性排除文件
objects/        # 存儲所有數據內容
refs/           # 存儲指向數據(分支)的提交對象的指針

2. Git對象

Git是一個內容尋址文件系統。

Git的核心部分是一個簡單的鍵值對數據庫(key-value data store)。你可以向該數據庫插入隨意類型的內容。它會返回一個鍵值,經過該鍵值可以在隨意時刻再次檢索(retrieve)該內容。

2.1 數據對象

(1)向Git中存入內容

# 因此數據內容都存在objects中
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
# 往Git數據庫存入一些文本
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4

顯示40個字符的校驗和:前兩個字符用於命名子文件夾,餘下的38個字符則用做文件名稱。

  • -w 選項指示 hash-object 命令存儲數據對象;若不指定此選項,則該命令僅返回相應的鍵值。
  • –stdin 選項則指示該命令從標準輸入讀取內容。若不指定此選項,則須在命令尾部給出待存儲文件的路徑。
# 查看objects中的文件
$ find .git/objects -type f
.git/objects//d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
# 查看文件內容
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
  • -p 選項可指示該命令本身主動推斷內容的類型,併爲咱們顯示格式友好的內容

(2)從Git中取出內容

# 建立一個新文件並將其內容存入數據庫
$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30
# 接着。向文件裏寫入新內容,並再次將其存入數據庫
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
$ find .git/objects -type f
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
# 把內容恢復到第一個版本號或第二個版本號
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt 
version 2

(3)查看文件存儲對象的類型

$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob # 數據對象
2.2 樹對象

使用「數據對象」存儲,其記住文件的每一個版本號所相應的SHA-1值並不現實,且文件名稱並無被保存,僅保了文件的內容。
「樹對象」能解決文件名稱保存的問題,也贊成咱們將多個文件組織到一塊兒。


所有內容均以樹對象和數據對象的形式存儲:樹對象相應文件夾項,數據對象則大體上相應inodes或文件內容。

$ git cat-file -p master^{tree}
100644 blob a906cb2a4a904a152e80877d4088654daad0c859      README
100644 blob 8f94139338f9404f26296befa88755fc2598c289      Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0      lib

master^{tree} 語法表示 master 分支上最新的提交所指向的樹對象。


lib子文件夾並不是一個數據對象。而是一個指針,其指向的是還有一個樹對象。

$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b      simplegit.rb

簡化版的Git數據模型.png

(1)建立本身的樹對象

$ echo 'new file' > new.txt
$ git update-index test.txt
$ git update-index --add new.txt
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt

3. Git引用

3.1 Git引用

咱們可以藉助類似於 git log 1a410e 這種命令來瀏覽完整的提交歷史,但爲了能遍歷那段歷史從而找到所有相關對象,你仍須記住1a410e是最後一個提交。

咱們需要一個文件來保存SHA-1值。即「引用(references,或縮寫爲 refs)」

$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/heads/master
.git/refs/remotes
.git/refs/remotes/origin
.git/refs/remotes/origin/master
.git/refs/tags
# 僅僅查看文件
$ find .git/refs type -f
.git/refs/heads/master
.git/refs/remotes/origin/master

$ git log --oneline master
21b61e0 Second commit
1b63b62 First commit
$ more .git/refs/heads/master 
21b61e04b11b0bb6e8aedd8d21132974a23630be

(1)更新某個引用

# 方式一
$ echo "1b63b62c89014812fb7d00c6c47b80abcec286e0" > .git/refs/heads/master
# 方式二
$ git update-ref refs/heads/master 1b63b62c89014812fb7d00c6c47b80abcec286e0

注意:不提倡直接編輯引用文件。!!

(2)依據某提交創建分支(好!!

!)

# 在第一次提交上建立一個分支「test」
$ git update-ref refs/heads/test 1b63b62c89014812fb7d00c6c47b80abcec286e0 $ git log --oneline master 21b61e0 Second commit 1b63b62 First commit $ git log --oneline test 1b63b62 First commit

注意:master分支並未收到不論什麼影響!!。

3.2 HEAD引用

HEAD 文件是一個符號引用(symbolic reference),指向眼下所在的分支。所謂符號引用,意味着它並不像普通引用那樣包括一個SHA-1值,它是一個指向其它引用的指針。

$ cat .git/HEAD
ref: refs/heads/master
# 切換到test分支
$ git checkout test
$ cat .git/HEAD
ref: refs/heads/test

當咱們執行git commit時,該命令會建立一個提交對象,並用HEAD文件裏那個引用所指向的 SHA-1值設置其父提交字段。

# 查看HEAD引用相應的值
$ git symbolic-ref HEAD
refs/heads/master
# 設置HEAD引用的值 
$ git symbolic-ref HEAD refs/heads/test
$ cat .git/HEAD
refs/heads/test
# 查看當前分支
$ git branch -a  
  master
* test
  remotes/origin/master

經過上述方式,也達到了切換分支的目的。。

3.3 標籤引用

標籤對象(tag object)很類似於一個提交對象——它包括一個標籤建立者信息、一個日期、一段凝視信息。以及一個指針。基本的差異在於,標籤對象一般指向一個提交對象。而不是一個樹對象。

它像是一個永不移動的分支引用——永遠指向同一個提交對象,僅僅只是給這個提交對象加上一個更友好的名字罷了。
(1)建立一個輕量標籤

$ git update-ref refs/tags/v1.0.0 21b61e04b11b0bb6e8aedd8d21132974a23630be

(2)查看建立標籤對象的SHA-1值

$ cat .git/refs/tags/v1.0.0
21b61e04b11b0bb6e8aedd8d21132974a23630be
3.4 遠程引用

加入了一個遠程版本號庫並對其執行過推送操做。Git 會記錄下近期一次推送操做時每一個分支所相應的值。並保存在 refs/remotes 文件夾下。

# origin 遠程版本號庫的 master 分支所相應的 SHA-1 值。就是近期一次與server通訊時本地 master 分支所相應的 SHA-1 值
$ cat .git/refs/remotes/origin/master
1b63b62c89014812fb7d00c6c47b80abcec286e0

遠程引用和分支(位於 refs/heads 文件夾下的引用)之間最基本的差異在於,遠程引用是僅僅讀的。儘管可以git checkout 到某個遠程引用,但是Git並不會將HEAD引用指向該遠程引用。所以,你永遠不能經過commit命令來更新遠程引用。Git將這些遠程引用做爲記錄遠程server上各分支最後已知位置狀態的書籤來管理。

4. 包文件

# 查看生成的樹對象
$ git cat-file -p master^{tree}
100644 blob eea6e07591843cfb79d894c44b978519af8f07df    .gitignore
040000 tree 61621bd52b3ac5ddf5277f06755c13ed6fe76c50    .idea
100644 blob 83c831f0b085c70509b1fbb0a0131a9a32e691ac    README.md
100644 blob f73f3093ff865c514c6c51f867e35f693487d0d3    new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a    test.txt
# 查看某個對象大小
$ git cat-file -s f73f3093ff865c514c6c51f867e35f693487d0d3
5
# 改動文件
$ echo 'testing' >> new.txt
$ git commit -am "modified new.txt"
$ git cat-file -p master^{tree}
100644 blob eea6e07591843cfb79d894c44b978519af8f07df    .gitignore
040000 tree 61621bd52b3ac5ddf5277f06755c13ed6fe76c50    .idea
100644 blob 83c831f0b085c70509b1fbb0a0131a9a32e691ac    README.md
100644 blob 76e35462261c5d088d07de3c936458ecd20f4514    new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a    test.txt

new.txt相應一個與以前全然不一樣的數據對象,這意味着,儘管你僅僅是在一個文件後面加入一行新內容,Git也會用一個全新的對象來存儲新的文件內容:

$ git cat-file -s 76e35462261c5d088d07de3c936458ecd20f4514
13

你的磁盤上現在有兩個差點兒全然一樣的對象。假設Git僅僅完整保存當中一個。再保存還有一個對象與以前版本號的差別內容,豈不更好?

其實Git可以那樣作。Git最初向磁盤中存儲對象時所使用的格式被稱爲「鬆散(loose)」對象格式。但是,Git會時不時地將多個這些對象打包成一個稱爲「包文件(packfile)」的二進制文件。以節省空間和提升效率。

當版本號庫中有太多的鬆散對象。或者你手動執行git gc命令,或者你向遠程server執行推送時,Git都會這樣作。

# 對對象進行打包
$ git gc

圖 gc.png

# 再查看 objects 文件夾,你會發現大部分的對象都不見了,與此同一時候出現了一對新文件
$ find .git/objects -type f
.git/objects/8c/6c1746924fa81ab1e7a7bc79c0c2e9cd34ffd5
.git/objects/info/packs
.git/objects/pack/pack-9032188022086c91c3810adf1900ec5d2014e7d3.idx     # 索引
.git/objects/pack/pack-9032188022086c91c3810adf1900ec5d2014e7d3.pack    # 包文件

包文件包括了剛纔從文件系統中移除的所有對象的內容。

索引文件包括了包文件的偏移信息。咱們經過索引文件就可以高速定位隨意一個指定對象。經過打包對象可以下降大約 ⅔ 的磁盤佔用空間。

**Git是怎樣作到這點的?**Git打包對象時,會查找命名及大小相近的文件。並僅僅保存文件不一樣版本號之間的差別內容。你可以查看包文件,觀察它是怎樣節省空間的。

# 查看已打包的內容
$ git verify-pack -v .git/objects/pack/pack-9032188022086c91c3810adf1900ec5d2014e7d3.idx

圖 verify-pack.png

5. 引用規格

(1)引用規格
引用規格的格式由一個可選的 + 號和緊隨其後的 <src>:<dst> 組成,當中 <src> 是一個模式(pattern),表明遠程版本號庫中的引用;<dst> 是那些遠程引用在本地所相應的位置。

+ 號告訴 Git 即便在不能快進的狀況下也要(強制)更新引用。
默認狀況下,引用規格由 git remote add 命令本身主動生成, Git 獲取server中 refs/heads/ 如下的所有引用。並將它寫入到本地的 refs/remotes/origin/ 中。

$ git remote add origin https://github.com/381510688/test.git

$ cat .git/config 
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[remote "origin"]
    url = https://github.com/381510688/test.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
    remote = origin
    merge = refs/heads/master

因此,假設server上有一個 master 分支。咱們可以在本地經過如下這種方式來訪問該分支上的提交記錄:

$ git log origin/master
$ git log remotes/origin/master
$ git log refs/remotes/origin/master

注意:上面的三個命令做用一樣。因爲 Git 會把它們都擴展成 refs/remotes/origin/master

假設想讓 Git 每次僅僅拉取遠程的 master 分支,而不是所有分支,可以把(引用規格的)獲取那一行改動爲:

fetch = +refs/heads/master:refs/remotes/origin/master

注意:這僅是針對該遠程版本號庫的 git fetch 操做的默認引用規格。

也可以在配置文件裏指定多個用於獲取操做的引用規格。

假設想在每次獲取時都包括 master 和 experiment 分支,加入例如如下兩行:

[remote "origin"]
    url = https://github.com/schacon/simplegit-progit
    fetch = +refs/heads/master:refs/remotes/origin/master
    fetch = +refs/heads/experiment:refs/remotes/origin/experiment

注意:不能在模式中使用部分通配符,因此像如下這種引用規格是不合法的

fetch = +refs/heads/qa*:refs/remotes/origin/qa*     # 不合法
fetch = +refs/heads/*:refs/remotes/origin/*         # 合法

(2)引用規格推送
把master分支推送到遠程server的 qa/master 分支上

$ git push origin master:refs/heads/qa/master

假設他們但願 Git 每次執行 git push origin 時都像上面這樣推送,可以在他們的配置文件里加入一條 push 值:

[remote "origin"]
    url = https://github.com/schacon/simplegit-progit
    fetch = +refs/heads/*:refs/remotes/origin/*
    push = refs/heads/master:refs/heads/qa/master

(3)刪除引用

$ git push origin :topic

因爲引用規格(的格式)是 <src>:<dst>,因此上述命令把 <src> 留空。意味着把遠程版本號庫的 topic 分支定義爲空值。也就是刪除它。

6. 維護與數據恢復

(1)維護
Git會不定時地本身主動執行一個叫作「auto gc」的命令。大多數時候,這個命令並不會產生效果。

然而。假設有太多鬆散對象(不在包文件裏的對象)或者太多包文件,Git會執行一個完整的git gc命令。

「gc」 表明垃圾回收。這個命令會作如下事情:收集所有鬆散對象並將它們放置到包文件裏。將多個包文件合併爲一個大的包文件,移除與不論什麼提交都不相關的陳舊對象。

# 手動執行本身主動垃圾回收
$ git gc --auto

就像上面提到的,這個命令一般並不會產生效果。大約需要7000個以上的鬆散對象或超過50個的包文件才幹讓Git啓動一次真正的gc命令。

你可以經過改動gc.autogc.autopacklimit的設置來改動這些數值。
(2)數據恢復
在你使用Git的時候。你可能會意外丟失一次提交。一般這是因爲你強制刪除了正在工做的分支。但是最後卻發現你還需要這個分支;亦或者硬重置了一個分支,放棄了你想要的提交。

假設這些事情已經發生,該怎樣找回你的提交呢?

$ git log --pretty=oneline
f991403ee78279300170ee9d192931668d2645d3 modified new.txt
21b61e04b11b0bb6e8aedd8d21132974a23630be Second commit 1b63b62c89014812fb7d00c6c47b80abcec286e0 First commit # 將master分支硬重置到第二次提交 $ git reset --hard 21b61e04b11b0bb6e8aedd8d21132974a23630be HEAD is now at 21b61e0 Second commit $ git log --pretty=oneline 21b61e04b11b0bb6e8aedd8d21132974a23630be Second commit 1b63b62c89014812fb7d00c6c47b80abcec286e0 First commit

現在頂部的提交已經丟失了-沒有分支指向這些提交。你需要找出最後一次提交的SHA-1而後添加一個指向它的分支。竅門就是找到最後一次的提交的SHA-1-但是預計你記不起來了。對嗎?最方便。也是最常用的方法。是使用一個名叫git reflog 的工具。

$ git reflog
21b61e0 HEAD@{0}: reset: moving to 21b61e04b11b0bb6e8aedd8d21132974a23630be
f991403 HEAD@{1}: commit: modified new.txt
21b61e0 HEAD@{2}: commit: Second commit
1b63b62 HEAD@{3}: commit: First commit

爲了使顯示的信息更加實用,咱們可以執行 git log -g

$ git branch recover-branch f991403
$ git checkout recover-branch
$ git log --pretty=oneline
f991403ee78279300170ee9d192931668d2645d3 modified new.txt
21b61e04b11b0bb6e8aedd8d21132974a23630be Second commit 1b63b62c89014812fb7d00c6c47b80abcec286e0 First commit

(3)移除對象
git clone 會下載整個項目的歷史,包括每一個文件的每一個版本號。假設所有的東西都是源碼那麼這很好,因爲Git被高度優化來有效地存儲這種數據。然而。假設某我的在以前向項目加入了一個大小特別大的文件,即便你將這個文件從項目中移除了,每次克隆仍是都要強制的下載這個大文件。

之因此會產生這個問題。是因爲這個文件在歷史中是存在的,它會永遠在那裏。


因此,你必須找到並移除這些大文件。警告:這個操做對提交歷史的改動是破壞性的。它會從你必須改動或移除一個大文件引用最先的樹對象開始重寫每一次提交。假設你在導入倉庫後。在不論什麼人開始基於這些提交工做前執行這個操做,那麼將不會有不論什麼問題;不然,你必須通知所有的貢獻者他們需要將他們的成果變基到你的新提交上。

# 加入一個大文件到倉庫中
curl https://github.com/381510688/javascript_test.git > git.tgz
$ git add git.tgz 
$ git commit -m "add big file"
# 移除這個大文件
$ git rm git.tgz 
$ git commit -m "oops - remove big file"
# 執行 gc 來查看數據庫佔用了多少空間
$ git gc
Counting objects: 31, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (20/20), done.
Writing objects: 100% (31/31), done.
Total 31 (delta 5), reused 26 (delta 4)
# 也可以執行 count-objects 命令來高速的查看佔用空間大小
$ git count-objects -v
count: 1
size: 4
in-pack: 31
packs: 1
size-pack: 4
prune-packable: 0
garbage: 0
size-garbage: 0
# 找出哪一個文件或哪些文件佔用瞭如此多的空間,而後對輸出內容的第三列(即文件大小)進行排序
git verify-pack -v .git/objects/pack/pack-d3e9607420577f00bdd580919f4d70835010c9a9.idx | sort -k 3 -n | tail -3;
7246fb5e4a10799d0f37c296a0de4d89d907aef4 blob   178 121 2435
9b8415079b0023a650395f070be3556d96d1e9d1 commit 227 155 12
7246fb5e4a10799d0f37c296a0de4d89d907aef4 commit 256 171 768

# 找出數據對象的名字
git rev-list --objects --all | grep 7246fb5e4a10799d0f37c296a0de4d89d907aef4
7246fb5e4a10799d0f37c296a0de4d89d907aef4 git.tgz

# 從過去所有的樹中移除這個文件
$ git log --oneline --branches -- git.tgz
9b84150 oops - remove big file
5a17b55 add big file
$ git filter-branch --index-filter \
  'git rm --ignore-unmatch --cached git.tgz' -- 5a17b55^..

歷史中將再也不包括對那個文件的引用。

只是。你的引用日誌和你在 .git/refs/original 經過 filter-branch 選項加入的新引用中還存有對這個文件的引用,因此你必須移除它們而後又一次打包數據庫。 在又一次打包前需要移除不論什麼包括指向那些舊提交的指針的文件:

$ rm -Rf .git/refs/original
$ rm -Rf .git/logs/
$ git gc

這個大文件還在你的鬆散對象中,並無消失;但是它不會在推送或接下來的克隆中出現。這纔是最重要的。 假設真的想要刪除它。可以經過有 –expire 選項的 git prune 命令來全然地移除那個對象:

$ git prune --expire now

做爲一套內容尋址文件系統,Git 不只僅是一個版本號控制系統。它同一時候是一個很強大且易用的工具。

相關文章
相關標籤/搜索