日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Git详解之九 Git内部原理

發布時間:2023/11/27 生活经验 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Git详解之九 Git内部原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

以下內容轉載自:http://www.open-open.com/lib/view/open1328070620202.html

?

?

Git 內部原理

?

不管你是從前面的章節直接跳到了本章,還是讀完了其余各章一直到這,你都將在本章見識 Git 的內部工作原理和實現方式。我個人發現學習這些內容對于理解 Git 的用處和強大是非常重要的,不過也有人認為這些內容對于初學者來說可能難以理解且過于復雜。正因如此我把這部分內容放在最后一章,你在學習過程中可以先閱 讀這部分,也可以晚點閱讀這部分,這完全取決于你自己。

既然已經讀到這了,就讓我們開始吧。首先要弄明白一點,從根本上來講 Git 是一套內容尋址 (content-addressable) 文件系統,在此之上提供了一個 VCS 用戶界面。馬上你就會學到這意味著什么。

早期的 Git (主要是 1.5 之前版本) 的用戶界面要比現在復雜得多,這是因為它更側重于成為文件系統而不是一套更精致的 VCS 。最近幾年改進了 UI 從而使它跟其他任何系統一樣清晰易用。即便如此,還是經常會有一些陳腔濫調提到早期 Git 的 UI 復雜又難學。

內容尋址文件系統這一層相當酷,在本章中我會先講解這部分。隨后你會學到傳輸機制和最終要使用的各種庫管理任務。

?

9.1? 底層命令 (Plumbing) 和高層命令 (Porcelain)

本書講解了使用?checkout,?branch,?remote?等共約 30 個 Git 命令。然而由于 Git 一開始被設計成供 VCS 使用的工具集而不是一整套用戶友好的 VCS,它還包含了許多底層命令,這些命令用于以 UNIX 風格使用或由腳本調用。這些命令一般被稱為 “plumbing” 命令(底層命令),其他的更友好的命令則被稱為 “porcelain” 命令(高層命令)。

本書前八章主要專門討論高層命令。本章將主要討論底層命令以理解 Git 的內部工作機制、演示 Git 如何及為何要以這種方式工作。這些命令主要不是用來從命令行手工使用的,更多的是用來為其他工具和自定義腳本服務的。

當你在一個新目錄或已有目錄內執行?git init?時,Git 會創建一個?.git?目錄,幾乎所有 Git 存儲和操作的內容都位于該目錄下。如果你要備份或復制一個庫,基本上將這一目錄拷貝至其他地方就可以了。本章基本上都討論該目錄下的內容。該目錄結構如下:

$ ls
HEAD
branches/
config
description
hooks/
index
info/
objects/
refs/

該目錄下有可能還有其他文件,但這是一個全新的?git init?生成的庫,所以默認情況下這些就是你能看到的結構。新版本的 Git 不再使用branches?目錄,description?文件僅供 GitWeb 程序使用,所以不用關心這些內容。config?文件包含了項目特有的配置選項,info?目錄保存了一份不希望在 .gitignore 文件中管理的忽略模式 (ignored patterns) 的全局可執行文件。hooks?目錄包住了第六章詳細介紹了的客戶端或服務端鉤子腳本。

另外還有四個重要的文件或目錄:HEAD?及?index?文件,objects?及refs?目錄。這些是 Git 的核心部分。objects?目錄存儲所有數據內容,refs?目錄存儲指向數據 (分支) 的提交對象的指針,HEAD?文件指向當前分支,index?文件保存了暫存區域信息。馬上你將詳細了解 Git 是如何操縱這些內容的。

?

9.2? Git 對象

Git 是一套內容尋址文件系統。很不錯。不過這是什么意思呢?這種說法的意思是,從內部來看,Git 是簡單的 key-value 數據存儲。它允許插入任意類型的內容,并會返回一個鍵值,通過該鍵值可以在任何時候再取出該內容。可以通過底層命令hash-object?來示范這點,傳一些數據給該命令,它會將數據保存在?.git?目錄并返回表示這些數據的鍵值。首先初使化一個 Git 倉庫并確認objects?目錄是空的:

$ mkdir test
$ cd test
$ git init
Initialized empty Git repository in /tmp/test/.git/
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f
$

Git 初始化了?objects?目錄,同時在該目錄下創建了?pack?和?info?子目錄,但是該目錄下沒有其他常規文件。我們往這個 Git 數據庫里存儲一些文本:

$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4

參數?-w?指示?hash-object?命令存儲 (數據) 對象,若不指定這個參數該命令僅僅返回鍵值。--stdin?指定從標準輸入設備 (stdin) 來讀取內容,若不指定這個參數則需指定一個要存儲的文件的路徑。該命令輸出長度為 40 個字符的校驗和。這是個 SHA-1 哈希值──其值為要存儲的數據加上你馬上會了解到的一種頭信息的校驗和。現在可以查看到 Git 已經存儲了數據:

$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

可以在?objects?目錄下看到一個文件。這便是 Git 存儲數據內容的方式──為每份內容生成一個文件,取得該內容與頭信息的 SHA-1 校驗和,創建以該校驗和前兩個字符為名稱的子目錄,并以 (校驗和) 剩下 38 個字符為文件命名 (保存至子目錄下)。

通過?cat-file?命令可以將數據內容取回。該命令是查看 Git 對象的瑞士軍刀。傳入?-p?參數可以讓該命令輸出數據內容的類型:

$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content

可以往 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/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

再將文件恢復到第一個版本:

$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1

或恢復到第二個版本:

$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt
version 2

需要記住的是幾個版本的文件 SHA-1 值可能與實際的值不同,其次,存儲的并不是文件名而僅僅是文件內容。這種對象類型稱為 blob 。通過傳遞 SHA-1 值給cat-file -t?命令可以讓 Git 返回任何對象的類型:

$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob

tree (樹) 對象

接下去來看 tree 對象,tree 對象可以存儲文件名,同時也允許存儲一組文件。Git 以一種類似 UNIX 文件系統但更簡單的方式來存儲內容。所有內容以 tree 或 blob 對象存儲,其中 tree 對象對應于 UNIX 中的目錄,blob 對象則大致對應于 inodes 或文件內容。一個單獨的 tree 對象包含一條或多條 tree 記錄,每一條記錄含有一個指向 blob 或子 tree 對象的 SHA-1 指針,并附有該對象的權限模式 (mode)、類型和文件名信息。以 simplegit 項目為例,最新的 tree 可能是這個樣子:

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

master^{tree}?表示?branch?分支上最新提交指向的 tree 對象。請注意?lib?子目錄并非一個 blob 對象,而是一個指向別一個 tree 對象的指針:

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

從概念上來講,Git 保存的數據如圖 9-1 所示。


圖 9-1. Git 對象模型的簡化版

你可以自己創建 tree 。通常 Git 根據你的暫存區域或 index 來創建并寫入一個 tree 。因此要創建一個 tree 對象的話首先要通過將一些文件暫存從而創建一個 index 。可以使用 plumbing 命令update-index?為一個單獨文件 ── test.txt 文件的第一個版本 ──????創建一個 index????。通過該命令人為的將 test.txt 文件的首個版本加入到了一個新的暫存區域中。由于該文件原先并不在暫存區域中 (甚至就連暫存區域也還沒被創建出來呢) ,必須傳入--add?參數;由于要添加的文件并不在當前目錄下而是在數據庫中,必須傳入?--cacheinfo?參數。同時指定了文件模式,SHA-1 值和文件名:

$ git update-index --add --cacheinfo 100644 \83baae61804e65cc73a7201a7252750c76066a30 test.txt

在本例中,指定了文件模式為?100644,表明這是一個普通文件。其他可用的模式有:100755?表示可執行文件,120000?表示符號鏈接。文件模式是從常規的 UNIX 文件模式中參考來的,但是沒有那么靈活 ── 上述三種模式僅對 Git 中的文件 (blobs) 有效 (雖然也有其他模式用于目錄和子模塊)。

現在可以用?write-tree?命令將暫存區域的內容寫到一個 tree 對象了。無需?-w?參數 ── 如果目標 tree 不存在,調用write-tree?會自動根據 index 狀態創建一個 tree 對象。

$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30      test.txt

可以這樣驗證這確實是一個 tree 對象:

$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree

再根據 test.txt 的第二個版本以及一個新文件創建一個新 tree 對象:

$ echo 'new file' > new.txt
$ git update-index test.txt
$ git update-index --add new.txt

這時暫存區域中包含了 test.txt 的新版本及一個新文件 new.txt 。創建 (寫) 該 tree 對象 (將暫存區域或 index 狀態寫入到一個 tree 對象),然后瞧瞧它的樣子:

$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt

請注意該 tree 對象包含了兩個文件記錄,且 test.txt 的 SHA 值是早先值的 “第二版” (1f7a7a)。來點更有趣的,你將把第一個 tree 對象作為一個子目錄加進該 tree 中。可以用read-tree?命令將 tree 對象讀到暫存區域中去。在這時,通過傳一個?--prefix?參數給?read-tree,將一個已有的 tree 對象作為一個子 tree 讀到暫存區域中:

$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579      bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt

如果從剛寫入的新 tree 對象創建一個工作目錄,將得到位于工作目錄頂級的兩個文件和一個名為?bak?的子目錄,該子目錄包含了 test.txt 文件的第一個版本。可以將 Git 用來包含這些內容的數據想象成如圖 9-2 所示的樣子。


圖 9-2. 當前 Git 數據的內容結構

commit (提交) 對象

你現在有三個 tree 對象,它們指向了你要跟蹤的項目的不同快照,可是先前的問題依然存在:必須記往三個 SHA-1 值以獲得這些快照。你也沒有關于誰、何時以及為何保存了這些快照的信息。commit 對象為你保存了這些基本信息。

要創建一個 commit 對象,使用?commit-tree?命令,指定一個 tree 的 SHA-1,如果有任何前繼提交對象,也可以指定。從你寫的第一個 tree 開始:

$ echo 'first commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d

通過?cat-file?查看這個新 commit 對象:

$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon  1243040974 -0700
committer Scott Chacon  1243040974 -0700first commit

commit 對象有格式很簡單:指明了該時間點項目快照的頂層樹對象、作者/提交者信息(從 Git 設理發店的?user.nameuser.email中獲得)以及當前時間戳、一個空行,以及提交注釋信息。

接著再寫入另外兩個 commit 對象,每一個都指定其之前的那個 commit 對象:

$ echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
$ echo 'third commit'  | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9

每一個 commit 對象都指向了你創建的樹對象快照。出乎意料的是,現在已經有了真實的 Git 歷史了,所以如果運行?git log?命令并指定最后那個 commit 對象的 SHA-1 便可以查看歷史:

$ git log --stat 1a410e
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Author: Scott Chacon 
Date:   Fri May 22 18:15:24 2009 -0700third commitbak/test.txt |    1 +1 files changed, 1 insertions(+), 0 deletions(-)commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon 
Date:   Fri May 22 18:14:29 2009 -0700second commitnew.txt  |    1 +test.txt |    2 +-2 files changed, 2 insertions(+), 1 deletions(-)commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Author: Scott Chacon 
Date:   Fri May 22 18:09:34 2009 -0700first committest.txt |    1 +1 files changed, 1 insertions(+), 0 deletions(-)

真棒。你剛剛通過使用低級操作而不是那些普通命令創建了一個 Git 歷史。這基本上就是運行????git add?和?git commit命令時 Git 進行的工作????──保存修改了的文件的 blob,更新索引,創建 tree 對象,最后創建 commit 對象,這些 commit 對象指向了頂層 tree 對象以及先前的 commit 對象。這三類 Git 對象 ── blob,tree 以及 tree ── 都各自以文件的方式保存在.git/objects?目錄下。以下所列是目前為止樣例中的所有對象,每個對象后面的注釋里標明了它們保存的內容:

$ find .git/objects -type f
.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1

如果你按照以上描述進行了操作,可以得到如圖 9-3 所示的對象圖。


圖 9-3. Git 目錄下的所有對象

對象存儲

之前我提到當存儲數據內容時,同時會有一個文件頭被存儲起來。我們花些時間來看看 Git 是如何存儲對象的。你將看來如何通過 Ruby 腳本語言存儲一個 blob 對象 (這里以字符串 “what is up, doc?” 為例) 。使用irb?命令進入 Ruby 交互式模式:

$ irb
>> content = "what is up, doc?"
=> "what is up, doc?"

Git 以對象類型為起始內容構造一個文件頭,本例中是一個 blob。然后添加一個空格,接著是數據內容的長度,最后是一個空字節 (null byte):

>> header = "blob #{content.length}\0"
=> "blob 16\000"

Git 將文件頭與原始數據內容拼接起來,并計算拼接后的新內容的 SHA-1 校驗和。可以在 Ruby 中使用?require?語句導入 SHA1 digest 庫,然后調用Digest::SHA1.hexdigest()?方法計算字符串的 SHA-1 值:

>> store = header + content
=> "blob 16\000what is up, doc?"
>> require 'digest/sha1'
=> true
>> sha1 = Digest::SHA1.hexdigest(store)
=> "bd9dbf5aae1a3862dd1526723246b20206e5fc37"

Git 用 zlib 對數據內容進行壓縮,在 Ruby 中可以用 zlib 庫來實現。首先需要導入該庫,然后用Zlib::Deflate.deflate()?對數據進行壓縮:

>> require 'zlib'
=> true
>> zlib_content = Zlib::Deflate.deflate(store)
=> "x\234K\312\311OR04c(\317H,Q\310,V(-\320QH\311O\266\a\000_\034\a\235"

最后將用 zlib 壓縮后的內容寫入磁盤。需要指定保存對象的路徑 (SHA-1 值的頭兩個字符作為子目錄名稱,剩余 38 個字符作為文件名保存至該子目錄中)。在 Ruby 中,如果子目錄不存在可以用FileUtils.mkdir_p()?函數創建它。接著用File.open?方法打開文件,并用?write()?方法將之前壓縮的內容寫入該文件:

>> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38]
=> ".git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37"
>> require 'fileutils'
=> true
>> FileUtils.mkdir_p(File.dirname(path))
=> ".git/objects/bd"
>> File.open(path, 'w') { |f| f.write zlib_content }
=> 32

這就行了 ── 你已經創建了一個正確的 blob 對象。所有的 Git 對象都以這種方式存儲,惟一的區別是類型不同 ── 除了字符串 blob,文件頭起始內容還可以是 commit 或 tree 。不過雖然 blob 幾乎可以是任意內容,commit 和 tree 的數據卻是有固定格式的。

?

9.3? Git References

你可以執行像?git log 1a410e?這樣的命令來查看完整的歷史,但是這樣你就要記得?1a410e?是你最后一次提交,這樣才能在提交歷史中找到這些對象。你需要一個文件來用一個簡單的名字來記錄這些 SHA-1 值,這樣你就可以用這些指針而不是原來的 SHA-1 值去檢索了。

在 Git 中,我們稱之為“引用”(references 或者 refs,譯者注)。你可以在?.git/refs?目錄下面找到這些包含 SHA-1 值的文件。在這個項目里,這個目錄還沒不包含任何文件,但是包含這樣一個簡單的結構:

$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
$ find .git/refs -type f
$

如果想要創建一個新的引用幫助你記住最后一次提交,技術上你可以這樣做:

$ echo "1a410efbd13591db07496601ebc7a059dd55cfe9" > .git/refs/heads/master

現在,你就可以在 Git 命令中使用你剛才創建的引用而不是 SHA-1 值:

$ git log --pretty=oneline  master
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

當然,我們并不鼓勵你直接修改這些引用文件。如果你確實需要更新一個引用,Git 提供了一個安全的命令?update-ref

$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9

基本上 Git 中的一個分支其實就是一個指向某個工作版本一條 HEAD 記錄的指針或引用。你可以用這條命令創建一個指向第二次提交的分支:

$ git update-ref refs/heads/test cac0ca

這樣你的分支將會只包含那次提交以及之前的工作:

$ git log --pretty=oneline test
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

現在,你的 Git 數據庫應該看起來像圖 9-4 一樣。


圖 9-4. 包含分支引用的 Git 目錄對象

每當你執行?git branch (分支名稱)?這樣的命令,Git 基本上就是執行?update-ref?命令,把你現在所在分支中最后一次提交的 SHA-1 值,添加到你要創建的分支的引用。

HEAD 標記

現在的問題是,當你執行?git branch (分支名稱)?這條命令的時候,Git 怎么知道最后一次提交的 SHA-1 值呢?答案就是 HEAD 文件。HEAD 文件是一個指向你當前所在分支的引用標識符。這樣的引用標識符——它看起來并不像一個普通的引用——其實并不包含 SHA-1 值,而是一個指向另外一個引用的指針。如果你看一下這個文件,通常你將會看到這樣的內容:

$ cat .git/HEAD
ref: refs/heads/master

如果你執行?git checkout test,Git 就會更新這個文件,看起來像這樣:

$ cat .git/HEAD
ref: refs/heads/test

當你再執行?git commit?命令,它就創建了一個 commit 對象,把這個 commit 對象的父級設置為 HEAD 指向的引用的 SHA-1 值。

你也可以手動編輯這個文件,但是同樣有一個更安全的方法可以這樣做:symbolic-ref。你可以用下面這條命令讀取 HEAD 的值:

$ git symbolic-ref HEAD
refs/heads/master

你也可以設置 HEAD 的值:

$ git symbolic-ref HEAD refs/heads/test
$ cat .git/HEAD
ref: refs/heads/test

但是你不能設置成 refs 以外的形式:

$ git symbolic-ref HEAD test
fatal: Refusing to point HEAD outside of refs/

Tags

你剛剛已經重溫過了 Git 的三個主要對象類型,現在這是第四種。Tag 對象非常像一個 commit 對象——包含一個標簽,一組數據,一個消息和一個指針。最主要的區別就是 Tag 對象指向一個 commit 而不是一個 tree。它就像是一個分支引用,但是不會變化——永遠指向同一個 commit,僅僅是提供一個更加友好的名字。

正如我們在第二章所討論的,Tag 有兩種類型:annotated 和 lightweight 。你可以類似下面這樣的命令建立一個 lightweight tag:

$ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d

這就是 lightweight tag 的全部 —— 一個永遠不會發生變化的分支。 annotated tag 要更復雜一點。如果你創建一個 annotated tag,Git 會創建一個 tag 對象,然后寫入一個指向指向它而不是直接指向 commit 的 reference。你可以這樣創建一個 annotated tag(-a?參數表明這是一個 annotated tag):

$ git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m 'test tag'

這是所創建對象的 SHA-1 值:

$ cat .git/refs/tags/v1.1
9585191f37f7b0fb9444f35a9bf50de191beadc2

現在你可以運行?cat-file?命令檢查這個 SHA-1 值:

$ git cat-file -p 9585191f37f7b0fb9444f35a9bf50de191beadc2
object 1a410efbd13591db07496601ebc7a059dd55cfe9
type commit
tag v1.1
tagger Scott Chacon  Sat May 23 16:48:58 2009 -0700test tag

值得注意的是這個對象指向你所標記的 commit 對象的 SHA-1 值。同時需要注意的是它并不是必須要指向一個 commit 對象;你可以標記任何 Git 對象。例如,在 Git 的源代碼里,管理者添加了一個 GPG 公鑰(這是一個 blob 對象)對它做了一個標簽。你就可以運行:

$ git cat-file blob junio-gpg-pub

來查看 Git 源代碼倉庫中的公鑰. Linux kernel 也有一個不是指向 commit 對象的 tag —— 第一個 tag 是在導入源代碼的時候創建的,它指向初始 tree (initial tree,譯者注)。

Remotes

你將會看到的第四種 reference 是 remote reference(遠程引用,譯者注)。如果你添加了一個 remote 然后推送代碼過去,Git 會把你最后一次推送到這個 remote 的每個分支的值都記錄在refs/remotes?目錄下。例如,你可以添加一個叫做origin?的 remote 然后把你的?master?分支推送上去:

$ git remote add origin git@github.com:schacon/simplegit-progit.git
$ git push origin master
Counting objects: 11, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 716 bytes, done.
Total 7 (delta 2), reused 4 (delta 1)
To git@github.com:schacon/simplegit-progit.gita11bef0..ca82a6d  master -> master

然后查看?refs/remotes/origin/master?這個文件,你就會發現?origin?remote 中的master?分支就是你最后一次和服務器的通信。

$ cat .git/refs/remotes/origin/master
ca82a6dff817ec66f44342007202690a93763949

Remote 應用和分支主要區別在于他們是不能被 check out 的。Git 把他們當作是標記這些了這些分支在服務器上最后狀態的一種書簽。

?

9.4? Packfiles

我們再來看一下 test Git 倉庫。目前為止,有 11 個對象 ── 4 個 blob,3 個 tree,3 個 commit 以及一個 tag:

$ find .git/objects -type f
.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
.git/objects/95/85191f37f7b0fb9444f35a9bf50de191beadc2 # tag
.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1

Git 用 zlib 壓縮文件內容,因此這些文件并沒有占用太多空間,所有文件加起來總共僅用了 925 字節。接下去你會添加一些大文件以演示 Git 的一個很有意思的功能。將你之前用到過的 Grit 庫中的 repo.rb 文件加進去 ── 這個源代碼文件大小約為 12K:

$ curl http://github.com/mojombo/grit/raw/master/lib/grit/repo.rb > repo.rb
$ git add repo.rb
$ git commit -m 'added repo.rb'
[master 484a592] added repo.rb3 files changed, 459 insertions(+), 2 deletions(-)delete mode 100644 bak/test.txtcreate mode 100644 repo.rbrewrite test.txt (100%)

如果查看一下生成的 tree,可以看到 repo.rb 文件的 blob 對象的 SHA-1 值:

$ git cat-file -p master^{tree}
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 9bc1dc421dcd51b4ac296e3e5b6e2a99cf44391e      repo.rb
100644 blob e3f094f522629ae358806b17daf78246c27c007b      test.txt

然后可以用?git cat-file?命令查看這個對象有多大:

$ git cat-file -s 9bc1dc421dcd51b4ac296e3e5b6e2a99cf44391e
12898

稍微修改一下些文件,看會發生些什么:

$ echo '# testing' >> repo.rb
$ git commit -am 'modified repo a bit'
[master ab1afef] modified repo a bit1 files changed, 1 insertions(+), 0 deletions(-)

查看這個 commit 生成的 tree,可以看到一些有趣的東西:

$ git cat-file -p master^{tree}
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 05408d195263d853f09dca71d55116663690c27c      repo.rb
100644 blob e3f094f522629ae358806b17daf78246c27c007b      test.txt

blob 對象與之前的已經不同了。這說明雖然只是往一個 400 行的文件最后加入了一行內容,Git 卻用一個全新的對象來保存新的文件內容:

$ git cat-file -s 05408d195263d853f09dca71d55116663690c27c
12908

你的磁盤上有了兩個幾乎完全相同的 12K 的對象。如果 Git 只完整保存其中一個,并保存另一個對象的差異內容,豈不更好?

事實上 Git 可以那樣做。Git 往磁盤保存對象時默認使用的格式叫松散對象 (loose object) 格式。Git 時不時地將這些對象打包至一個叫 packfile 的二進制文件以節省空間并提高效率。當倉庫中有太多的松散對象,或是手工調用git gc?命令,或推送至遠程服務器時,Git 都會這樣做。手工調用?git gc?命令讓 Git 將庫中對象打包并看會發生些什么:

$ git gc
Counting objects: 17, done.
Delta compression using 2 threads.
Compressing objects: 100% (13/13), done.
Writing objects: 100% (17/17), done.
Total 17 (delta 1), reused 10 (delta 0)

查看一下 objects 目錄,會發現大部分對象都不在了,與此同時出現了兩個新文件:

$ find .git/objects -type f
.git/objects/71/08f7ecb345ee9d0084193f147cdad4d2998293
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/info/packs
.git/objects/pack/pack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.idx
.git/objects/pack/pack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.pack

仍保留著的幾個對象是未被任何 commit 引用的 blob ── 在此例中是你之前創建的 “what is up, doc?” 和 “test content” 這兩個示例 blob。你從沒將他們添加至任何 commit,所以 Git 認為它們是 “懸空” 的,不會將它們打包進 packfile 。

剩下的文件是新創建的 packfile 以及一個索引。packfile 文件包含了剛才從文件系統中移除的所有對象。索引文件包含了 packfile 的偏移信息,這樣就可以快速定位任意一個指定對象。有意思的是運行gc?命令前磁盤上的對象大小約為 12K ,而這個新生成的 packfile 僅為 6K 大小。通過打包對象減少了一半磁盤使用空間。

Git 是如何做到這點的?Git 打包對象時,會查找命名及尺寸相近的文件,并只保存文件不同版本之間的差異內容。可以查看一下 packfile ,觀察它是如何節省空間的。git verify-pack?命令用于顯示已打包的內容:

$ git verify-pack -v \.git/objects/pack/pack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.idx
0155eb4229851634a0f03eb265b69f5a2d56f341 tree   71 76 5400
05408d195263d853f09dca71d55116663690c27c blob   12908 3478 874
09f01cea547666f58d6a8d809583841a7c6f0130 tree   106 107 5086
1a410efbd13591db07496601ebc7a059dd55cfe9 commit 225 151 322
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a blob   10 19 5381
3c4e9cd789d88d8d89c1073707c3585e41b0e614 tree   101 105 5211
484a59275031909e19aadb7c92262719cfcdf19a commit 226 153 169
83baae61804e65cc73a7201a7252750c76066a30 blob   10 19 5362
9585191f37f7b0fb9444f35a9bf50de191beadc2 tag    136 127 5476
9bc1dc421dcd51b4ac296e3e5b6e2a99cf44391e blob   7 18 5193 1
05408d195263d853f09dca71d55116663690c27c \ab1afef80fac8e34258ff41fc1b867c702daa24b commit 232 157 12
cac0cab538b970a37ea1e769cbbde608743bc96d commit 226 154 473
d8329fc1cc938780ffdd9f94e0d364e0ea74f579 tree   36 46 5316
e3f094f522629ae358806b17daf78246c27c007b blob   1486 734 4352
f8f51d7d8a1760462eca26eebafde32087499533 tree   106 107 749
fa49b077972391ad58037050f2a75f74e3671e92 blob   9 18 856
fdf4fc3344e67ab068f836878b6c4951e3b15f3d commit 177 122 627
chain length = 1: 1 object
pack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.pack: ok

如果你還記得的話,?9bc1d?這個 blob 是 repo.rb 文件的第一個版本,這個 blob 引用了?05408?這個 blob,即該文件的第二個版本。命令輸出內容的第三列顯示的是對象大小,可以看到05408?占用了 12K 空間,而?9bc1d?僅為 7 字節。非常有趣的是第二個版本才是完整保存文件內容的對象,而第一個版本是以差異方式保存的 ── 這是因為大部分情況下需要快速訪問文件的最新版本。

最妙的是可以隨時進行重新打包。Git 自動定期對倉庫進行重新打包以節省空間。當然也可以手工運行?git gc?命令來這么做。

?

9.5? The Refspec

這本書讀到這里,你已經使用過一些簡單的遠程分支到本地引用的映射方式了,這種映射可以更為復雜。 假設你像這樣添加了一項遠程倉庫:

$ git remote add origin git@github.com:schacon/simplegit-progit.git

它在你的?.git/config?文件中添加了一節,指定了遠程的名稱 (origin), 遠程倉庫的URL地址,和用于獲取操作的 Refspec:

[remote "origin"]url = git@github.com:schacon/simplegit-progit.gitfetch = +refs/heads/*:refs/remotes/origin/*

Refspec 的格式是一個可選的?+?號,接著是?:?的格式,這里?是遠端上的引用格式,?是將要記錄在本地的引用格式。可選的?+?號告訴 Git 在即使不能快速演進的情況下,也去強制更新它。

缺省情況下 refspec 會被?git remote add?命令所自動生成, Git 會獲取遠端上?refs/heads/?下面的所有引用,并將它寫入到本地的refs/remotes/origin/. 所以,如果遠端上有一個?master?分支,你在本地可以通過下面這種方式來訪問它的歷史記錄:

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

它們全是等價的,因為 Git 把它們都擴展成?refs/remotes/origin/master.

如果你想讓 Git 每次只拉取遠程的?master?分支,而不是遠程的所有分支,你可以把 fetch 這一行修改成這樣:

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

這是?git fetch?操作對這個遠端的缺省 refspec 值。而如果你只想做一次該操作,也可以在命令行上指定這個 refspec. 如可以這樣拉取遠程的master?分支到本地的?origin/mymaster?分支:

$ git fetch origin master:refs/remotes/origin/mymaster

你也可以在命令行上指定多個 refspec. 像這樣可以一次獲取遠程的多個分支:

$ git fetch origin master:refs/remotes/origin/mymaster \topic:refs/remotes/origin/topic
From git@github.com:schacon/simplegit! [rejected]        master     -> origin/mymaster  (non fast forward)* [new branch]      topic      -> origin/topic

在這個例子中,?master?分支因為不是一個可以快速演進的引用而拉取操作被拒絕。你可以在 refspec 之前使用一個?+?號來重載這種行為。

你也可以在配置文件中指定多個 refspec. 如你想在每次獲取時都獲取?master?和?experiment?分支,就添加兩行:

[remote "origin"]url = git@github.com:schacon/simplegit-progit.gitfetch = +refs/heads/master:refs/remotes/origin/masterfetch = +refs/heads/experiment:refs/remotes/origin/experiment

但是這里不能使用部分通配符,像這樣就是不合法的:

fetch = +refs/heads/qa*:refs/remotes/origin/qa*

但無論如何,你可以使用命名空間來達到這個目的。如你有一個QA組,他們推送一系列分支,你想每次獲取?master?分支和QA組的所有分支,你可以使用這樣的配置段落:

[remote "origin"]url = git@github.com:schacon/simplegit-progit.gitfetch = +refs/heads/master:refs/remotes/origin/masterfetch = +refs/heads/qa/*:refs/remotes/origin/qa/*

如果你的工作流很復雜,有QA組推送的分支、開發人員推送的分支、和集成人員推送的分支,并且他們在遠程分支上協作,你可以采用這種方式為他們創建各自的命名空間。

推送 Refspec

采用命名空間的方式確實很棒,但QA組成員第1次是如何將他們的分支推送到?qa/?空間里面的呢?答案是你可以使用 refspec 來推送。

如果QA組成員想把他們的?master?分支推送到遠程的?qa/master?分支上,可以這樣運行:

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

如果他們想讓 Git 每次運行?git push origin?時都這樣自動推送,他們可以在配置文件中添加?push?值:

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

這樣,就會讓?git push origin?缺省就把本地的?master?分支推送到遠程的?qa/master?分支上。

刪除引用

你也可以使用 refspec 來刪除遠程的引用,是通過運行這樣的命令:

$ git push origin :topic

因為 refspec 的格式是?:, 通過把?部分留空的方式,這個意思是是把遠程的topic?分支變成空,也就是刪除它。

?

9.6? 傳輸協議

Git 可以以兩種主要的方式跨越兩個倉庫傳輸數據:基于HTTP協議之上,和?file://,?ssh://, 和git://?等智能傳輸協議。這一節帶你快速瀏覽這兩種主要的協議操作過程。

啞協議

Git 基于HTTP之上傳輸通常被稱為啞協議,這是因為它在服務端不需要有針對 Git 特有的代碼。這個獲取過程僅僅是一系列GET請求,客戶端可以假定服務端的Git倉庫中的布局。讓我們以 simplegit 庫來看看http-fetch?的過程:

$ git clone http://github.com/schacon/simplegit-progit.git

它做的第1件事情就是獲取?info/refs?文件。這個文件是在服務端運行了?update-server-info?所生成的,這也解釋了為什么在服務端要想使用HTTP傳輸,必須要開啟post-receive?鉤子:

=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949     refs/heads/master

現在你有一個遠端引用和SHA值的列表。下一步是尋找HEAD引用,這樣你就知道了在完成后,什么應該被檢出到工作目錄:

=> GET HEAD
ref: refs/heads/master

這說明在完成獲取后,需要檢出?master?分支。 這時,已經可以開始漫游操作了。因為你的起點是在?info/refs?文件中所提到的ca82a6?commit 對象,你的開始操作就是獲取它:

=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)

然后你取回了這個對象 - 這在服務端是一個松散格式的對象,你使用的是靜態的 HTTP GET 請求獲取的。可以使用 zlib 解壓縮它,去除其頭部,查看它的 commmit 內容:

$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
author Scott Chacon  1205815931 -0700
committer Scott Chacon  1240030591 -0700changed the version number

這樣,就得到了兩個需要進一步獲取的對象 -?cfda3b?是這個 commit 對象所對應的 tree 對象,和?085bb3?是它的父對象;

=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
(179 bytes of data)

這樣就取得了這它的下一步 commit 對象,再抓取 tree 對象:

=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
(404 - Not Found)

Oops - 看起來這個 tree 對象在服務端并不以松散格式對象存在,所以得到了404響應,代表在HTTP服務端沒有找到該對象。這有好幾個原因 - 這個對象可能在替代倉庫里面,或者在打包文件里面, Git 會首先檢查任何列出的替代倉庫:

=> GET objects/info/http-alternates
(empty file)

如果這返回了幾個替代倉庫列表,那么它會去那些地方檢查松散格式對象和文件 - 這是一種在軟件分叉之間共享對象以節省磁盤的好方法。然而,在這個例子中,沒有替代倉庫。所以你所需要的對象肯定在某個打包文件中。要檢查服務端有哪些打包格式文件,你需要獲取objects/info/packs?文件,這里面包含有打包文件列表(是的,它也是被?update-server-info?所生成的);

=> GET objects/info/packs
P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack

這里服務端只有一個打包文件,所以你要的對象顯然就在里面。但是你可以先檢查它的索引文件以確認。這在服務端有多個打包文件時也很有用,因為這樣就可以先檢查你所需要的對象空間是在哪一個打包文件里面了:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k of binary data)

現在你有了這個打包文件的索引,你可以看看你要的對象是否在里面 - 因為索引文件列出了這個打包文件所包含的所有對象的SHA值,和該對象存在于打包文件中的偏移量,所以你只需要簡單地獲取整個打包文件:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)

現在你也有了這個 tree 對象,你可以繼續在 commit 對象上漫游。它們全部都在這個你已經下載到的打包文件里面,所以你不用繼續向服務端請求更多下載了。 在這完成之后,由于下載開始時已探明HEAD引用是指向master?分支, Git 會將它檢出到工作目錄。

整個過程看起來就像這樣:

$ git clone http://github.com/schacon/simplegit-progit.git
Initialized empty Git repository in /private/tmp/simplegit-progit/.git/
got ca82a6dff817ec66f44342007202690a93763949
walk ca82a6dff817ec66f44342007202690a93763949
got 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Getting alternates list for http://github.com/schacon/simplegit-progit.git
Getting pack list for http://github.com/schacon/simplegit-progit.git
Getting index for pack 816a9b2334da9953e530f27bcac22082a9f5b835
Getting pack 816a9b2334da9953e530f27bcac22082a9f5b835which contains cfda3bf379e4f8dba8717dee55aab78aef7f4daf
walk 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
walk a11bef06a3f659402fe7563abf99ad00de2209e6

智能協議

這個HTTP方法是很簡單但效率不是很高。使用智能協議是傳送數據的更常用的方法。這些協議在遠端都有Git智能型進程在服務 - 它可以讀出本地數據并計算出客戶端所需要的,并生成合適的數據給它,這有兩類傳輸數據的進程:一對用于上傳數據和一對用于下載。

上傳數據

為了上傳數據至遠端, Git 使用?send-pack?和?receive-pack?進程。這個?send-pack?進程運行在客戶端上,它連接至遠端運行的?receive-pack?進程。

舉例來說,你在你的項目上運行了?git push origin master, 并且?origin?被定義為一個使用SSH協議的URL。 Git 會使用send-pack?進程,它會啟動一個基于SSH的連接到服務器。它嘗試像這樣透過SSH在服務端運行命令:

$ ssh -x git@github.com "git-receive-pack 'schacon/simplegit-progit.git'"
005bca82a6dff817ec66f4437202690a93763949 refs/heads/master report-status delete-refs
003e085bb3bcb608e1e84b2432f8ecbe6306e7e7 refs/heads/topic
0000

這里的?git-receive-pack?命令會立即對它所擁有的每一個引用響應一行 - 在這個例子中,只有?master?分支和它的SHA值。這里第1行也包含了服務端的能力列表(這里是report-status?和?delete-refs)。

每一行以4字節的十六進制開始,用于指定整行的長度。你看到第1行以005b開始,這在十六進制中表示91,意味著第1行有91字節長。下一行以003e起始,表示有62字節長,所以需要讀剩下的62字節。再下一行是0000開始,表示服務器已完成了引用列表過程。

現在它知道了服務端的狀態,你的?send-pack?進程會判斷哪些 commit 是它所擁有但服務端沒有的。針對每個引用,這次推送都會告訴對端的receive-pack?這個信息。舉例說,如果你在更新?master?分支,并且增加?experiment?分支,這個send-pack?將會是像這樣:

0085ca82a6dff817ec66f44342007202690a93763949  15027957951b64cf874c3557a0f3547bd83b3ff6 refs/heads/master report-status
00670000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d refs/heads/experiment
0000

這里的全’0’的SHA-1值表示之前沒有過這個對象 - 因為你是在添加新的 experiment 引用。如果你在刪除一個引用,你會看到相反的: 就是右邊是全’0’。

Git 針對每個引用發送這樣一行信息,就是舊的SHA值,新的SHA值,和將要更新的引用的名稱。第1行還會包含有客戶端的能力。下一步,客戶端會發送一個所有那些服務端所沒有的對象的一個打包文件。最后,服務端以成功(或者失敗)來響應:

000Aunpack ok

下載數據

當你在下載數據時,?fetch-pack?和?upload-pack?進程就起作用了。客戶端啟動?fetch-pack?進程,連接至遠端的?upload-pack?進程,以協商后續數據傳輸過程。

在遠端倉庫有不同的方式啟動?upload-pack?進程。你可以使用與?receive-pack?相同的透過SSH管道的方式,也可以通過 Git 后臺來啟動這個進程,它默認監聽在9418號端口上。這里fetch-pack?進程在連接后像這樣向后臺發送數據:

003fgit-upload-pack schacon/simplegit-progit.git\0host=myserver.com\0

它也是以4字節指定后續字節長度的方式開始,然后是要運行的命令,和一個空字節,然后是服務端的主機名,再跟隨一個最后的空字節。 Git 后臺進程會檢查這個命令是否可以運行,以及那個倉庫是否存在,以及是否具有公開權限。如果所有檢查都通過了,它會啟動這個upload-pack?進程并將客戶端的請求移交給它。

如果你透過SSH使用獲取功能,?fetch-pack?會像這樣運行:

$ ssh -x git@github.com "git-upload-pack 'schacon/simplegit-progit.git'"

不管哪種方式,在?fetch-pack?連接之后,?upload-pack?都會以這種形式返回:

0088ca82a6dff817ec66f44342007202690a93763949 HEAD\0multi_ack thin-pack \side-band side-band-64k ofs-delta shallow no-progress include-tag
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
003e085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 refs/heads/topic
0000

這與?receive-pack?響應很類似,但是這里指的能力是不同的。而且它還會指出HEAD引用,讓客戶端可以檢查是否是一份克隆。

在這里,?fetch-pack?進程檢查它自己所擁有的對象和所有它需要的對象,通過發送 “want” 和所需對象的SHA值,發送 “have” 和所有它已擁有的對象的SHA值。在列表完成時,再發送 “done” 通知upload-pack?進程開始發送所需對象的打包文件。這個過程看起來像這樣:

0054want ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0000
0009done

這是傳輸協議的一個很基礎的例子,在更復雜的例子中,客戶端可能會支持?multi_ack?或者?side-band?能力;但是這個例子中展示了智能協議的基本交互過程。

?

9.7? 維護及數據恢復

你時不時的需要進行一些清理工作 ── 如減小一個倉庫的大小,清理導入的庫,或是恢復丟失的數據。本節將描述這類使用場景。

維護

Git 會不定時地自動運行稱為 “auto gc” 的命令。大部分情況下該命令什么都不處理。不過要是存在太多松散對象 (loose object, 不在 packfile 中的對象) 或 packfile,Git 會進行調用git gc?命令。?gc?指垃圾收集 (garbage collect),此命令會做很多工作:收集所有松散對象并將它們存入 packfile,合并這些 packfile 進一個大的 packfile,然后將不被任何 commit 引用并且已存在一段時間 (數月) 的對象刪除。

可以手工運行 auto gc 命令:

$ git gc --auto

再次強調,這個命令一般什么都不干。如果有 7,000 個左右的松散對象或是 50 個以上的 packfile,Git 才會真正調用 gc 命令。可能通過修改配置中的gc.auto?和?gc.autopacklimit?來調整這兩個閾值。

gc?還會將所有引用 (references) 并入一個單獨文件。假設倉庫中包含以下分支和標簽:

$ find .git/refs -type f
.git/refs/heads/experiment
.git/refs/heads/master
.git/refs/tags/v1.0
.git/refs/tags/v1.1

這時如果運行?git gc,?refs?下的所有文件都會消失。Git 會將這些文件挪到?.git/packed-refs?文件中去以提高效率,該文件是這個樣子的:

$ cat .git/packed-refs
# pack-refs with: peeled
cac0cab538b970a37ea1e769cbbde608743bc96d refs/heads/experiment
ab1afef80fac8e34258ff41fc1b867c702daa24b refs/heads/master
cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.0
9585191f37f7b0fb9444f35a9bf50de191beadc2 refs/tags/v1.1
^1a410efbd13591db07496601ebc7a059dd55cfe9

當更新一個引用時,Git 不會修改這個文件,而是在?refs/heads?下寫入一個新文件。當查找一個引用的 SHA 時,Git 首先在refs?目錄下查找,如果未找到則到?packed-refs?文件中去查找。因此如果在?refs?目錄下找不到一個引用,該引用可能存到packed-refs?文件中去了。

請留意文件最后以?^?開頭的那一行。這表示該行上一行的那個標簽是一個 annotated 標簽,而該行正是那個標簽所指向的 commit 。

數據恢復

在使用 Git 的過程中,有時會不小心丟失 commit 信息。這一般出現在以下情況下:強制刪除了一個分支而后又想重新使用這個分支,hard-reset 了一個分支從而丟棄了分支的部分 commit。如果這真的發生了,有什么辦法把丟失的 commit 找回來呢?

下面的示例演示了對 test 倉庫主分支進行 hard-reset 到一個老版本的 commit 的操作,然后恢復丟失的 commit 。首先查看一下當前的倉庫狀態:

$ git log --pretty=oneline
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

接著將?master?分支移回至中間的一個 commit:

$ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9
HEAD is now at 1a410ef third commit
$ git log --pretty=oneline
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

這樣就丟棄了最新的兩個 commit ── 包含這兩個 commit 的分支不存在了。現在要做的是找出最新的那個 commit 的 SHA,然后添加一個指它它的分支。關鍵在于找出最新的 commit 的 SHA ── 你不大可能記住了這個 SHA,是吧?

通常最快捷的辦法是使用?git reflog?工具。當你 (在一個倉庫下) 工作時,Git 會在你每次修改了 HEAD 時悄悄地將改動記錄下來。當你提交或修改分支時,reflog 就會更新。git update-ref?命令也可以更新 reflog,這是在本章前面的 “Git References” 部分我們使用該命令而不是手工將 SHA 值寫入 ref 文件的理由。任何時間運行git reflog?命令可以查看當前的狀態:

$ git reflog
1a410ef HEAD@{0}: 1a410efbd13591db07496601ebc7a059dd55cfe9: updating HEAD
ab1afef HEAD@{1}: ab1afef80fac8e34258ff41fc1b867c702daa24b: updating HEAD

可以看到我們簽出的兩個 commit ,但沒有更多的相關信息。運行?git log -g?會輸出 reflog 的正常日志,從而顯示更多有用信息:

$ git log -g
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Reflog: HEAD@{0} (Scott Chacon )
Reflog message: updating HEAD
Author: Scott Chacon 
Date:   Fri May 22 18:22:37 2009 -0700third commitcommit ab1afef80fac8e34258ff41fc1b867c702daa24b
Reflog: HEAD@{1} (Scott Chacon )
Reflog message: updating HEAD
Author: Scott Chacon 
Date:   Fri May 22 18:15:24 2009 -0700modified repo a bit

看起來弄丟了的 commit 是底下那個,這樣在那個 commit 上創建一個新分支就能把它恢復過來。比方說,可以在那個 commit (ab1afef) 上創建一個名為recover-branch?的分支:

$ git branch recover-branch ab1afef
$ git log --pretty=oneline recover-branch
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

酷!這樣有了一個跟原來?master?一樣的?recover-branch?分支,最新的兩個 commit 又找回來了。接著,假設引起 commit 丟失的原因并沒有記錄在 reflog 中 ── 可以通過刪除recover-branch?和 reflog 來模擬這種情況。這樣最新的兩個 commit 不會被任何東西引用到:

$ git branch -D recover-branch
$ rm -Rf .git/logs/

因為 reflog 數據是保存在?.git/logs/?目錄下的,這樣就沒有 reflog 了。現在要怎樣恢復 commit 呢?辦法之一是使用git fsck?工具,該工具會檢查倉庫的數據完整性。如果指定?--ful?選項,該命令顯示所有未被其他對象引用 (指向) 的所有對象:

$ git fsck --full
dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4
dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b
dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9
dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293

本例中,可以從 dangling commit 找到丟失了的 commit。用相同的方法就可以恢復它,即創建一個指向該 SHA 的分支。

移除對象

Git 有許多過人之處,不過有一個功能有時卻會帶來問題:git clone?會將包含每一個文件的所有歷史版本的整個項目下載下來。如果項目包含的僅僅是源代碼的話這并沒有什么壞處,畢竟 Git 可以非常高效地壓縮此類數據。不過如果有人在某個時刻往項目中添加了一個非常大的文件,那們即便他在后來的提交中將此文件刪掉了,所有的簽出都會下載這個 大文件。因為歷史記錄中引用了這個文件,它會一直存在著。

當你將 Subversion 或 Perforce 倉庫轉換導入至 Git 時這會成為一個很嚴重的問題。在此類系統中,(簽出時) 不會下載整個倉庫歷史,所以這種情形不大會有不良后果。如果你從其他系統導入了一個倉庫,或是發覺一個倉庫的尺寸遠超出預計,可以用下面的方法找到并移除 大 (尺寸) 對象。

警告:此方法會破壞提交歷史。為了移除對一個大文件的引用,從最早包含該引用的 tree 對象開始之后的所有 commit 對象都會被重寫。如果在剛導入一個倉庫并在其他人在此基礎上開始工作之前這么做,那沒有什么問題 ── 否則你不得不通知所有協作者 (貢獻者) 去衍合你新修改的 commit 。

為了演示這點,往 test 倉庫中加入一個大文件,然后在下次提交時將它刪除,接著找到并將這個文件從倉庫中永久刪除。首先,加一個大文件進去:

$ curl http://kernel.org/pub/software/scm/git/git-1.6.3.1.tar.bz2 > git.tbz2
$ git add git.tbz2
$ git commit -am 'added git tarball'
[master 6df7640] added git tarball1 files changed, 0 insertions(+), 0 deletions(-)create mode 100644 git.tbz2

喔,你并不想往項目中加進一個這么大的 tar 包。最后還是去掉它:

$ git rm git.tbz2
rm 'git.tbz2'
$ git commit -m 'oops - removed large tarball'
[master da3f30d] oops - removed large tarball1 files changed, 0 insertions(+), 0 deletions(-)delete mode 100644 git.tbz2

對倉庫進行?gc?操作,并查看占用了空間:

$ git gc
Counting objects: 21, done.
Delta compression using 2 threads.
Compressing objects: 100% (16/16), done.
Writing objects: 100% (21/21), done.
Total 21 (delta 3), reused 15 (delta 1)

可以運行?count-objects?以查看使用了多少空間:

$ git count-objects -v
count: 4
size: 16
in-pack: 21
packs: 1
size-pack: 2016
prune-packable: 0
garbage: 0

size-pack?是以千字節為單位表示的 packfiles 的大小,因此已經使用了 2MB 。而在這次提交之前僅用了 2K 左右 ── 顯然在這次提交時刪除文件并沒有真正將其從歷史記錄中刪除。每當有人復制這個倉庫去取得這個小項目時,都不得不復制所有 2MB 數據,而這僅僅因為你曾經不小心加了個大文件。當我們來解決這個問題。

首先要找出這個文件。在本例中,你知道是哪個文件。假設你并不知道這一點,要如何找出哪個 (些) 文件占用了這么多的空間?如果運行?git gc,所有對象會存入一個 packfile 文件;運行另一個底層命令git verify-pack?以識別出大對象,對輸出的第三列信息即文件大小進行排序,還可以將輸出定向到?tail?命令,因為你只關心排在最后的那幾個最大的文件:

$ git verify-pack -v .git/objects/pack/pack-3f8c0...bb.idx | sort -k 3 -n | tail -3
e3f094f522629ae358806b17daf78246c27c007b blob   1486 734 4667
05408d195263d853f09dca71d55116663690c27c blob   12908 3478 1189
7a9eb2fba2b1811321254ac360970fc169ba2330 blob   2056716 2056872 5401

最底下那個就是那個大文件:2MB 。要查看這到底是哪個文件,可以使用第 7 章中已經簡單使用過的?rev-list?命令。若給?rev-list?命令傳入?--objects?選項,它會列出所有 commit SHA 值,blob SHA 值及相應的文件路徑。可以這樣查看 blob 的文件名:

$ git rev-list --objects --all | grep 7a9eb2fb
7a9eb2fba2b1811321254ac360970fc169ba2330 git.tbz2

接下來要將該文件從歷史記錄的所有 tree 中移除。很容易找出哪些 commit 修改了這個文件:

$ git log --pretty=oneline -- git.tbz2
da3f30d019005479c99eb4c3406225613985a1db oops - removed large tarball
6df764092f3e7c8f5f94cbe08ee5cf42e92a0289 added git tarball

必須重寫從?6df76?開始的所有 commit 才能將文件從 Git 歷史中完全移除。這么做需要用到第 6 章中用過的?filter-branch?命令:

$ git filter-branch --index-filter \'git rm --cached --ignore-unmatch git.tbz2' -- 6df7640^..
Rewrite 6df764092f3e7c8f5f94cbe08ee5cf42e92a0289 (1/2)rm 'git.tbz2'
Rewrite da3f30d019005479c99eb4c3406225613985a1db (2/2)
Ref 'refs/heads/master' was rewritten

--index-filter?選項類似于第 6 章中使用的?--tree-filter?選項,但這里不是傳入一個命令去修改磁盤上簽出的文件,而是修改暫存區域或索引。不能用rm file?命令來刪除一個特定文件,而是必須用?git rm --cached?來刪除它 ── 即從索引而不是磁盤刪除它。這樣做是出于速度考慮 ── 由于 Git 在運行你的 filter 之前無需將所有版本簽出到磁盤上,這個操作會快得多。也可以用--tree-filter?來完成相同的操作。git rm?的?--ignore-unmatch?選項指定當你試圖刪除的內容并不存在時不顯示錯誤。最后,因為你清楚問題是從哪個 commit 開始的,使用filter-branch?重寫自?6df7640?這個 commit 開始的所有歷史記錄。不這么做的話會重寫所有歷史記錄,花費不必要的更多時間。

現在歷史記錄中已經不包含對那個文件的引用了。不過 reflog 以及運行?filter-branch?時 Git 往?.git/refs/original?添加的一些 refs 中仍有對它的引用,因此需要將這些引用刪除并對倉庫進行 repack 操作。在進行 repack 前需要將所有對這些 commits 的引用去除:

$ rm -Rf .git/refs/original
$ rm -Rf .git/logs/
$ git gc
Counting objects: 19, done.
Delta compression using 2 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (19/19), done.
Total 19 (delta 3), reused 16 (delta 1)

看一下節省了多少空間。

$ git count-objects -v
count: 8
size: 2040
in-pack: 19
packs: 1
size-pack: 7
prune-packable: 0
garbage: 0

repack 后倉庫的大小減小到了 7K ,遠小于之前的 2MB 。從 size 值可以看出大文件對象還在松散對象中,其實并沒有消失,不過這沒有關系,重要的是在再進行推送或復制,這個對象不會再傳送出去。如果真的要完全把這個對象刪除,可以運行git prune --expire?命令。

?

9.8? 總結

現在你應該對 Git 可以作什么相當了解了,并且在一定程度上也知道了 Git 是如何實現的。本章覆蓋了許多 plumbing 命令 ── 這些命令比較底層,且比你在本書其他部分學到的 porcelain 命令要來得簡單。從底層了解 Git 的工作原理可以幫助你更好地理解為何 Git 實現了目前的這些功能,也使你能夠針對你的工作流寫出自己的工具和腳本。

Git 作為一套 content-addressable 的文件系統,是一個非常強大的工具,而不僅僅只是一個 VCS 供人使用。希望借助于你新學到的 Git 內部原理的知識,你可以實現自己的有趣的應用,并以更高級便利的方式使用 Git。

?

轉載于:https://www.cnblogs.com/yhaing/p/8473553.html

總結

以上是生活随笔為你收集整理的Git详解之九 Git内部原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

国内久久看 | 免费在线视频一区二区 | 天天干天天干天天 | 精品在线观看一区二区 | 日韩在线视频观看免费 | 六月激情婷婷 | 在线视频第一页 | 成人试看120秒 | 韩国av一区二区三区 | 国产片网站| 欧美日韩不卡在线观看 | 一区二区三区免费看 | 精品欧美一区二区在线观看 | 亚洲免费国产 | 久久99这里只有精品 | 亚洲国产中文字幕在线 | 中国精品少妇 | 99re6热在线精品视频 | 亚洲黄色免费观看 | 国产精品99久久久久久久久久久久 | 亚洲理论在线观看 | 成年人视频在线观看免费 | 免费成人av电影 | 精品福利在线视频 | 久草精品视频在线观看 | 久久久久久久久久久久国产精品 | 国产一线二线三线在线观看 | 精品一二三四五区 | 日本少妇视频 | 亚洲九九影院 | 999久久国精品免费观看网站 | 国产视频精品在线 | 中文字幕在线专区 | 日韩亚洲在线 | 91九色porny在线| 亚洲91中文字幕无线码三区 | www.五月婷婷.com| 欧美日韩视频在线观看一区二区 | av免费电影在线观看 | 国产成人一区二区三区在线观看 | 91麻豆免费视频 | 狠狠色狠狠色 | 免费高清无人区完整版 | 天天爱天天射天天干天天 | 99精品视频网 | 国产女人40精品一区毛片视频 | 日韩精品视频免费在线观看 | 久久婷婷一区二区三区 | 六月丁香婷婷网 | 在线观看一区视频 | 最新av网址大全 | 国产色女 | 国产1区在线 | 久久久国产影视 | 欧美一级免费片 | 亚洲专区欧美专区 | 日韩特黄av | 日韩一区二区三区免费电影 | 日本在线观看中文字幕无线观看 | 亚洲天天看 | 粉嫩av一区二区三区免费 | 99视频久 | 中文字幕美女免费在线 | 亚洲一级免费观看 | 国产亚洲精品久久久久久大师 | 91视频这里只有精品 | 黄色软件在线看 | 国产v在线播放 | 狠狠狠色狠狠色综合 | 日日操天天爽 | 精品国产91亚洲一区二区三区www | 91系列在线观看 | 久久人人爽人人爽人人片av免费 | 久久久久国产一区二区 | 四虎成人在线 | 91国内产香蕉 | 国产麻豆传媒 | 日韩黄色免费看 | 亚洲精品一区二区三区高潮 | 国产一级黄色免费看 | 在线观看第一页 | 日日夜夜人人精品 | 亚洲春色综合另类校园电影 | 欧美人体xx | 欧美精品久久久久久久久久白贞 | 欧美精品视 | 97碰碰精品嫩模在线播放 | 午夜精品视频一区二区三区在线看 | 国产精品成人一区二区三区吃奶 | 亚洲午夜不卡 | 久久成人国产 | 91视频91蝌蚪 | 国产精品免费视频观看 | 精品视频久久久久久 | 日韩在线精品 | h视频在线看 | 911av视频| 国产 欧美 日产久久 | 日韩有码专区 | 人人插人人射 | 免费观看视频的网站 | 久久视频在线观看 | 97天堂网| 亚洲狠狠丁香婷婷综合久久久 | 亚欧日韩av | 国产在线色 | 高清av在线| av资源在线看 | 国产高清综合 | 99999精品视频 | 久久久久麻豆v国产 | 久久97超碰| 国产高清在线a视频大全 | 亚洲乱亚洲乱妇 | 狂野欧美激情性xxxx | 99色网站| 国产午夜三级一区二区三 | 国产精品久久久久久久久久白浆 | 免费在线观看视频一区 | 99看视频在线观看 | 婷婷精品国产欧美精品亚洲人人爽 | 国产精品五月天 | 欧美日韩免费观看一区二区三区 | 六月激情网 | 国产亚洲精品久久久久久电影 | 91免费在线 | 伊人色综合久久天天网 | 日韩av免费在线看 | 丰满少妇在线观看资源站 | 特黄特黄的视频 | 缴情综合网五月天 | 在线观看mv的中文字幕网站 | 激情婷婷综合网 | 黄色大片日本免费大片 | 久久久久久久久国产 | 久久精品日韩 | 国内99视频 | ww视频在线观看 | 色之综合网 | 成人黄色毛片视频 | 在线观看免费成人 | 色综合天天综合网国产成人网 | 黄网站免费久久 | 中文在线√天堂 | 91麻豆免费视频 | 久草久视频 | 久久久国产精品电影 | 日本久草电影 | 91免费视频网站在线观看 | 中国精品一区二区 | 国产精品男女 | 久久99免费 | 国产亚州精品视频 | 日韩成人免费观看 | 不卡日韩av | 在线成人性视频 | 亚洲一区二区精品 | 亚洲国产中文字幕在线 | 午夜视频亚洲 | 97热视频| 操操操人人人 | 91在线免费观看国产 | 日韩精品资源 | 日韩免费二区 | 69视频在线| 成人a级黄色片 | 亚洲一区二区三区在线看 | 亚洲精品ww | 中文字幕视频一区 | 国产999久久久 | 国产精品18久久久久vr手机版特色 | 日韩一区正在播放 | 亚洲一区日韩精品 | 色av色av色av| 91视频91自拍 | 麻豆国产精品va在线观看不卡 | 免费观看9x视频网站在线观看 | 亚洲电影自拍 | 超碰人人超 | 日韩一级电影网站 | 奇米影视四色8888 | 国产在线欧美日韩 | 日韩精品一区不卡 | 国内偷拍精品视频 | 久久69精品久久久久久久电影好 | 国产一线二线三线在线观看 | 久久久久久综合 | 欧美网站黄色 | 在线视频一区二区 | 久久久性| 国内精品久久影院 | 亚洲国产精久久久久久久 | 亚洲精品日韩av | 99视频在线精品免费观看2 | 国产精品欧美激情在线观看 | 91看片淫黄大片一级在线观看 | 久热爱| 久久优| 国产999精品久久久久久麻豆 | av中文字幕第一页 | 久久久久久久久久影院 | 婷婷免费在线视频 | 97超碰在线久草超碰在线观看 | 一二三久久久 | 激情大尺度视频 | 免费观看一区二区 | 中文字幕亚洲欧美日韩2019 | 中文字幕资源在线观看 | 911香蕉视频 | 亚洲精品午夜久久久 | 日韩大片在线免费观看 | 手机av在线网站 | 日韩一级电影在线观看 | 国产91在线观看 | 欧美精品久久久久久久亚洲调教 | 亚洲精品乱码久久久久久蜜桃不爽 | 狠狠狠色丁香婷婷综合激情 | 免费观看国产精品视频 | 视频91| 国产高清久久久久 | 日韩久久一区 | 成人香蕉视频 | 久久香蕉影视 | 国产毛片久久 | 久久久福利 | 中文字幕最新精品 | 在线观看视频国产一区 | 曰本三级在线 | 久久婷亚洲五月一区天天躁 | 91久久丝袜国产露脸动漫 | 九色精品免费永久在线 | 96av视频| 天天干天天操天天射 | 国产麻豆视频免费观看 | 日韩精品一区二区三区免费观看 | 久久婷婷久久 | 国产免费一区二区三区网站免费 | 欧美日韩国产精品一区二区亚洲 | 国产精品久久久 | www五月天婷婷| 成全在线视频免费观看 | 国产又粗又猛又爽又黄的视频先 | 超碰在线观看99 | 久久久久久久久久久网 | 丁香婷婷激情啪啪 | 久久精品精品电影网 | 中文字幕丝袜 | 国产午夜精品一区二区三区 | 激情开心站 | 最新精品视频在线 | 亚洲成人国产精品 | 国产一级片不卡 | 欧美一区免费在线观看 | 亚洲精品视频偷拍 | 久久av观看 | 青青草在久久免费久久免费 | 欧美性粗大hdvideo | 国产专区视频在线观看 | 日本黄色大片免费看 | 久久不卡国产精品一区二区 | 五月婷婷色播 | 国产美女精品在线 | 亚洲高清激情 | 久久国产视频网站 | 国产69久久精品成人看 | 国产午夜亚洲精品 | av理论电影| 国产一区免费在线 | 色久网| 国产精品免费在线播放 | 国产精品视频在线观看 | 久久久久国产成人免费精品免费 | 国产精品1区2区3区 久久免费视频7 | 日韩动漫免费观看高清完整版在线观看 | 曰本三级在线 | 色久综合 | 五月婷婷色综合 | 色99久久| 亚洲女同ⅹxx女同tv | 亚洲激情视频在线 | 蜜臀91丨九色丨蝌蚪老版 | 亚洲黄色在线观看 | 欧美乱码精品一区 | 国产91av视频在线观看 | 国产婷婷 | av免费在线看网站 | 99精品在线观看视频 | av中文字幕在线看 | 日本久草电影 | 91视频在线国产 | 免费看的国产视频网站 | 色婷婷视频在线 | 久久久黄视频 | 麻豆传媒电影在线观看 | 亚洲久草网| 国产精品一区二区三区四 | 国产在线观看免 | 久久久不卡影院 | 久久精品视频网址 | 色狠狠操 | 国产又黄又爽无遮挡 | 99精品免费久久久久久日本 | 欧美成年网站 | 国产一级在线看 | 国产a国产a国产a | 不卡精品| 在线中文字幕观看 | 国产 亚洲 欧美 在线 | 韩国三级av在线 | 99久久爱 | h动漫中文字幕 | 亚洲精选99 | 亚洲精品国产麻豆 | 国产在线精品国自产拍影院 | 黄色中文字幕在线 | 91精品一区二区三区蜜桃 | 免费看一级特黄a大片 | 国产精品自产拍在线观看中文 | 91视频大全 | 色综合久久久 | 人人澡人 | 久久国产精品偷 | 中文字幕黄色av | 国产在线理论片 | 久久久久麻豆 | av在线播放一区二区三区 | 国产伦精品一区二区三区… | 91入口在线观看 | 欧美日韩高清国产 | 草久久av| 九九久久精品 | 国内精品视频在线播放 | 亚洲精品福利在线 | 99这里都是精品 | 91精彩视频在线观看 | 国产精品福利无圣光在线一区 | 精品xxx| 国产亚洲欧美在线视频 | 天天射天天干天天 | 色99视频 | 久久久黄色免费网站 | 免费日韩电影 | 在线观看免费黄色 | 欧美日韩亚洲第一 | 国产麻豆精品久久一二三 | 色综合久久五月 | 欧美大荫蒂xxx | 国产日本在线 | 中文字幕成人一区 | 在线国产中文字幕 | 高清一区二区 | 免费看黄在线网站 | 欧美精品久久久久久久久久丰满 | 免费看黄色大全 | 亚洲国产影院 | 91成人看片 | 成年人黄色免费看 | 麻豆国产精品永久免费视频 | www.com久久久 | 91成人天堂久久成人 | 精品视频在线播放 | 五月天激情视频在线观看 | 日韩区在线观看 | 国产日韩高清在线 | 99久久婷婷国产 | 国内外激情视频 | 亚洲国产久| 成人av在线一区二区 | 一区二区视频免费在线观看 | 久草视频免费播放 | 欧美性大胆 | 日韩99热| 国产999精品久久久影片官网 | 久久久久久久久久久免费av | av免费电影网站 | 日韩av黄 | 精品亚洲视频在线观看 | 欧美一区二区三区在线视频观看 | 免费色av | 久草在线久 | 成年人在线观看网站 | 国产99久久久欧美黑人 | 天天操天天操 | 色av男人的天堂免费在线 | www九九热 | 久草观看视频 | 97精品国产97久久久久久久久久久久 | 色综合久久久久 | 久久久久久久久久网 | 97夜夜澡人人爽人人免费 | av九九九| 最近日韩免费视频 | 超碰在线观看av.com | 亚洲精品在线视频网站 | 亚洲传媒在线 | 最新av观看 | 伊人五月在线 | 96久久欧美麻豆网站 | 国产精品亚洲综合久久 | 久久久噜噜噜久久久 | 美女网站视频免费都是黄 | 日韩一级精品 | 日韩av电影网站在线观看 | 色婷婷导航 | 色婷婷激情 | 色姑娘综合 | 婷婷草 | 99视频在线精品国自产拍免费观看 | 四虎成人精品 | 国内精品久久久久久久久久 | 超碰久热| 国产精品一码二码三码在线 | 丁香婷婷久久久综合精品国产 | 日韩偷拍精品 | 久久久国产99久久国产一 | 色婷婷丁香 | 久草观看视频 | 色无五月 | 欧美在线观看视频一区二区三区 | 亚洲日日夜夜 | 99免在线观看免费视频高清 | 成人小视频在线观看免费 | 欧美精品天堂 | 国产99中文字幕 | 久久久久久久久艹 | 免费黄色在线播放 | 日韩av免费一区二区 | 欧美日韩不卡一区 | 久久综合五月天 | 久久久国产精品亚洲一区 | 国产精品久久在线观看 | 中文字幕乱码一区二区 | 亚洲精品国产品国语在线 | 色婷婷综合激情 | 亚洲黄在线观看 | 日韩羞羞| 午夜色场 | 91成人精品国产刺激国语对白 | 欧美日韩久久不卡 | 久久久久亚洲最大xxxx | 中文字幕在线色 | 97免费公开视频 | 在线看不卡av | 美女网站黄免费 | 二区精品视频 | 日韩一区精品 | 国产二区视频在线观看 | 国内免费久久久久久久久久久 | 亚洲精品电影在线 | 激情综合久久 | 国产香蕉久久 | 欧美日韩在线免费视频 | 婷婷综合av | 又黄又爽又刺激视频 | 久草在线国产 | 天天干天天插 | 成人毛片一区 | 色多视频在线观看 | 日韩视频免费播放 | 99人成在线观看视频 | 天天爽天天爽夜夜爽 | 九九九电影免费看 | 国产h在线播放 | 97精品国产一二三产区 | 精品国产免费人成在线观看 | 91精品久久久久久综合五月天 | 亚洲视频免费在线看 | 在线观看中文字幕av | 中文字幕免费观看 | 在线亚洲欧美视频 | 国产视频99| 欧美一区二区三区在线播放 | 精品国产乱码久久久久久久 | 一区 二区电影免费在线观看 | 最新国产在线观看 | 久久久国产一区 | www.色的| 欧美精品久久久久久久久久久 | 成人av网站在线 | 国产视频一区精品 | 欧美精品v国产精品 | 亚洲成人一区 | 欧美一区二区在线免费看 | 久草电影在线观看 | 久草在线精品观看 | 日韩欧美高清一区二区 | 亚洲国产剧情av | 国产视频在线播放 | 91福利社区在线观看 | 国产午夜视频在线观看 | 操操日 | 欧美久久成人 | 99热在线免费观看 | 在线观看一区二区精品 | 在线免费色 | 91激情在线视频 | 久久夜色精品国产亚洲aⅴ 91chinesexxx | 国产免费又粗又猛又爽 | 亚洲精品久久久蜜桃 | 亚洲综合欧美精品电影 | 黄网av在线 | 久久精品国产一区二区 | 四虎在线视频免费观看 | 啪啪免费视频网站 | 亚洲精品国产精品国自产观看浪潮 | 韩国av免费在线观看 | 久久99深爱久久99精品 | 国产午夜精品一区 | 欧美三级在线播放 | 91精品久久久久久粉嫩 | 国产 欧美 日本 | 国产精品白虎 | 亚洲免费精品一区二区 | 五月婷婷色 | 中文字幕一区二区三区四区 | 九九九九九国产 | 中文字幕中文字幕在线中文字幕三区 | 伊人五月天 | 精品美女在线视频 | 色天堂在线视频 | 97福利在线观看 | 成人app在线免费观看 | av视屏在线| 成年人免费观看国产 | 国产欧美三级 | 97人人精品 | 免费观看一级视频 | 亚洲乱码精品久久久 | 日韩在线二区 | 最新中文字幕在线观看视频 | 天天撸夜夜操 | 中文字幕在线观看免费观看 | 91亚洲永久精品 | 91视频成人免费 | 亚洲美女精品区人人人人 | 色综合综合 | 日本中文字幕网 | 久久不射影院 | 日韩理论影院 | 99久久er热在这里只有精品66 | 韩国视频一区二区三区 | 在线看片a | 久久高清视频免费 | 国产精品毛片完整版 | 欧美久久久久久久久久 | 国产精品一区二区在线免费观看 | 欧美999| 玖玖玖在线| 91最新在线观看 | 成人精品国产 | 日韩久久精品一区二区三区下载 | 青青草久草在线 | 公开超碰在线 | 国产精品午夜久久 | 超碰在线免费97 | 久草免费在线视频观看 | 久久婷婷开心 | 欧美与欧洲交xxxx免费观看 | 91久久国产综合精品女同国语 | 婷婷久久五月天 | 一区二区三区四区免费视频 | 色综合久久天天 | 91久久久国产精品 | 日本在线观看中文字幕 | 国产精品乱码高清在线看 | 久久天天拍| 亚洲第五色综合网 | 五月婷婷久久丁香 | 色在线高清 | 99色免费视频 | 日本久久久久久 | 国产99视频在线观看 | 天天干 天天摸 天天操 | 亚洲干| 女人高潮特级毛片 | 在线视频你懂得 | 一区二区视频免费在线观看 | 在线免费观看一区二区三区 | av女优中文字幕在线观看 | 国产日韩欧美视频 | 中国老女人日b | 国产精品va最新国产精品视频 | 午夜视频黄 | 国产高清在线 | 国产色拍拍拍拍在线精品 | 最近中文字幕大全 | 午夜久久美女 | a级黄色片视频 | 97网| 二区视频在线 | 久久国产日韩 | 天天综合婷婷 | 999国产在线 | 欧美一级视频一区 | 欧美另类人妖 | 五月天久久综合 | 久久久午夜精品福利内容 | 毛片永久新网址首页 | 中文字幕制服丝袜av久久 | 中文字幕在线观看日本 | 在线观看中文字幕第一页 | 日本韩国精品在线 | 午夜精品一区二区三区免费 | 日韩av影视| 日本一区二区免费在线观看 | 久久中国精品 | 96久久欧美麻豆网站 | 草久在线观看 | 色综合久久久久综合体 | 久草精品免费 | 久久久久久国产精品亚洲78 | 又黄又爽又无遮挡免费的网站 | 国产传媒中文字幕 | 日韩免费久久 | 国内三级在线观看 | 国产精品一区二区三区久久久 | 日韩h在线观看 | 一区二区三区在线免费观看视频 | 亚洲国产精品成人女人久久 | 黄色www免费 | 天天激情在线 | 国产精品一区二区久久精品爱微奶 | 国产精品专区一 | 在线观看mv的中文字幕网站 | 97国产大学生情侣酒店的特点 | 在线免费观看亚洲视频 | 国产精品麻豆99久久久久久 | 国产精品一区二 | 成年人电影毛片 | 久久久在线观看 | 91精品电影 | 五月综合在线观看 | 麻豆传媒视频在线 | 蜜臀av.com| 国产精品美 | 亚洲va欧美 | 国产一级特黄电影 | 国产精品美女免费 | 久久免费观看少妇a级毛片 久久久久成人免费 | 日本中文一级片 | 伊人资源视频在线 | 国产精品6999成人免费视频 | 色婷婷伊人 | 摸阴视频 | 国产精品av在线 | 欧美一区二区精品在线 | 免费观看黄 | 亚洲成人资源网 | 亚洲精品久久久蜜臀下载官网 | 干狠狠| 69国产在线观看 | 亚洲码国产日韩欧美高潮在线播放 | 一区二区免费不卡在线 | 婷婷色资源 | 久久国产精品小视频 | 69国产盗摄一区二区三区五区 | 国产成人一区二区三区电影 | 欧美性生活久久 | 国产成人777777| 亚洲一区在线看 | 成 人 黄 色 免费播放 | 国产精品不卡在线 | 色多视频在线观看 | 日本女人的性生活视频 | 超碰免费av| 97色视频在线 | 欧美一级性生活 | 亚洲在线免费视频 | 欧美日韩在线免费视频 | 欧美一级片在线免费观看 | 黄在线免费看 | 国产精品国产三级国产专区53 | 天天插天天 | 丁香婷婷综合五月 | 91传媒在线观看 | www.色的| www.色com| 免费看的黄色录像 | av资源网在线播放 | 91人网站 | 99久久国产免费,99久久国产免费大片 | 日韩v欧美v日本v亚洲v国产v | 国产成人精品av | 97av免费视频| 日本久久中文 | 在线 国产 日韩 | 91精品在线播放 | 激情欧美国产 | 日韩免费在线观看 | 婷婷激情小说网 | 久久免费中文视频 | 亚洲精品在线一区二区 | 久草热久草视频 | 色a在线观看 | 99精品免费久久久久久久久日本 | 国产在线p | 国产在线视频一区二区三区 | 996久久国产精品线观看 | 又污又黄网站 | 亚洲成人精品在线观看 | 激情网色 | 天天曰天天曰 | 精品不卡av | 欧美日韩一区二区三区在线观看视频 | 精品在线一区二区 | 亚洲三级黄色 | 九九一级片 | 久久综合9988久久爱 | 成人h在线播放 | 日韩视频在线一区 | 国产欧美在线一区二区三区 | 99热这里只有精品免费 | 久久久精品视频成人 | 在线看中文字幕 | 久久理论视频 | 国产精品免费一区二区三区在线观看 | 毛片视频电影 | 97超碰资源 | 午夜性色 | 91视频在线观看大全 | 中文字幕在线观看国产 | 国产精品视频不卡 | 久久96国产精品久久99漫画 | 色婷婷激情 | 成人国产精品久久久 | 一区二区不卡高清 | 久久久久国产精品一区 | 狠狠色伊人亚洲综合网站色 | 亚洲精品人人 | 成人黄色资源 | 日韩高清精品免费观看 | 伊人热 | 91麻豆精品91久久久久同性 | 精品不卡视频 | av免费在线网 | 永久中文字幕 | 免费视频三区 | 伊人天天综合 | 日日摸日日添日日躁av | 亚洲精品国产日韩 | 9999在线视频| 香蕉影视 | 中文字幕在线播放一区二区 | 久久免费黄色大片 | 中文字幕在线观看网站 | 国产精品一区二区三区在线免费观看 | 在线观看精品黄av片免费 | 不卡的av中文字幕 | 免费观看xxxx9999片 | 久久精品影视 | 97夜夜澡人人双人人人喊 | 国产色啪 | 波多野结衣在线视频一区 | 伊人婷婷色 | 香蕉视频网址 | 在线观看国产一区 | www.97色.com| 色视频网站在线 | 国产精品第十页 | 五月婷婷欧美视频 | 91视频大全 | 国产一区视频免费在线观看 | 成人观看| 91污在线| 精品九九九 | 99综合电影在线视频 | 日韩中文字幕在线观看 | 黄色免费观看 | 免费三级网 | 香蕉视频国产在线观看 | 91在线视频| 亚洲国产三级在线 | 在线精品视频免费播放 | 日韩免费大片 | 久久亚洲欧美日韩精品专区 | 在线观看日韩视频 | 在线观看一二三区 | 国产精品1区2区3区 久久免费视频7 | 亚洲精品国久久99热 | 8x成人免费视频 | 天天狠狠操 | 久久a v电影 | 欧美一区二区三区在线看 | 亚洲综合精品在线 | 天堂网一区二区 | 在线观看黄色的网站 | 日本黄色免费在线 | 亚洲国产精品日韩 | 日韩理论电影在线观看 | 久久久国际精品 | 91黄色在线观看 | 国产精品免费在线播放 | 免费网站观看www在线观看 | 丁香激情五月 | 蜜桃av人人夜夜澡人人爽 | 国产一区二区三区免费在线观看 | 新av在线| 日韩在线免费电影 | 欧美日韩国产一区二区三区 | 免费在线黄色av | 久久夜夜夜 | 久保带人 | 狠狠色丁香| 久久综合九色综合97婷婷女人 | 美女国产网站 | 黄色av网站在线观看免费 | 91福利视频一区 | 欧美性脚交 | 欧美日韩高清不卡 | 久久99在线视频 | 在线亚洲播放 | 天天爽天天碰狠狠添 | 日韩在线观看第一页 | 99精品国产在热久久 | 日日夜夜精品视频天天综合网 | 日韩精品在线免费观看 | 婷婷六月天在线 | 午夜精品久久久久久久久久久久久久 | 99久久99久久免费精品蜜臀 | 久久精品中文字幕一区二区三区 | 一本到视频在线观看 | 操高跟美女 | 欧美国产日韩在线视频 | 久久亚洲私人国产精品va | 午夜视频在线观看欧美 | 国内精品久久久久久中文字幕 | 亚洲成人精品久久久 | 又粗又长又大又爽又黄少妇毛片 | 久久一区二区免费视频 | 香蕉视频在线播放 | 久久99深爱久久99精品 | 国产精品久久一区二区无卡 | 国产精品a级 | 99色资源 | 美女久久久久久久久久久 | 欧美激情精品久久 | 不卡av电影在线观看 | 91少妇精拍在线播放 | av一级片网站 | 亚洲国产中文字幕 | 最近中文字幕大全中文字幕免费 | 国产精品成人在线 | 97操操操 | 三级小视频在线观看 | 一区二区三区在线观看免费视频 | 亚洲每日更新 | 日本动漫做毛片一区二区 | 国产一区二三区好的 | 粉嫩av一区二区三区四区 | 久久久亚洲国产精品麻豆综合天堂 | 丝袜制服天堂 | 国产精品一区二区在线观看 | 久草免费手机视频 | 亚洲国产片色 | 一区二区精品久久 | 国语自产偷拍精品视频偷 | 成人午夜免费剧场 | 在线免费观看视频你懂的 | 夜夜干夜夜 | 91色一区二区三区 | 精品一区欧美 | 亚洲aaa毛片| 久久午夜网 | 亚洲国产精品电影 | 99精品视频在线播放观看 | 欧美日本日韩aⅴ在线视频 插插插色综合 | 成人免费在线网 | 最近久乱中文字幕 | 国产视频99 | 天天操比 | 91在线观看高清 | 亚洲日本国产精品 | 亚洲综合丁香 | 久久大视频 | 探花视频在线观看+在线播放 | 久久久久日本精品一区二区三区 | 91麻豆精品国产自产 | 亚洲黄色大片 | 成人黄色小说在线观看 | 91麻豆看国产在线紧急地址 | 亚洲精品国产拍在线 | 在线免费观看国产精品 | 97视频播放 | 免费看一级黄色大全 | 国产老妇av| 精品久久网站 | 国产福利精品一区二区 | 亚洲综合涩 | av激情五月| 国产精品美女www爽爽爽视频 | 最近最新中文字幕视频 | 国产成人a v电影 | www.天天色 | 天天干夜夜操视频 | 男女男视频 | 亚洲乱码中文字幕综合 | 夜夜婷婷| 黄色三级视频片 | 婷婷中文在线 | 黄色a一级视频 | 国产欧美精品一区二区三区四区 | 色婷婷www| 日本午夜在线观看 | 中文字幕欧美日韩va免费视频 | 亚洲精品久久激情国产片 | 精品资源在线 | 天堂网av 在线| 免费看的国产视频网站 | 91中文字幕永久在线 | 看片在线亚洲 | 最新成人av| 91污污视频在线观看 | 天天做日日爱夜夜爽 | 亚洲黑丝少妇 | 国产精品一区二区62 | 欧美日韩色婷婷 | 黄色激情网址 | 操操操日日日干干干 | 久久精品日产第一区二区三区乱码 | 国产精品一区二区三区免费视频 | 亚洲精品在线免费播放 | 国产区高清在线 | 人人网av | 亚洲精品国产综合99久久夜夜嗨 | 黄色成人av网址 | 国产精品久久久久高潮 | 久久这里只有精品1 | 日韩精品欧美专区 | 日韩精品一区二区三区在线视频 | 中文字幕一区二区三区精华液 | 日韩精品一区二区三区电影 | 亚洲极色 | 亚洲永久国产精品 | 麻豆一区在线观看 | 九色琪琪久久综合网天天 | 免费在线观看午夜视频 | 日韩在线电影观看 | 精品色综合 | 久久69精品 | 极品中文字幕 | 四虎在线视频 | 欧美在线日韩在线 | 亚洲成a人片在线观看网站口工 | 免费视频黄| 国产高清视频在线观看 | 久久99热精品这里久久精品 | 一级欧美一级日韩 | 亚洲精品乱码久久久久久蜜桃91 | 久久国内精品视频 | 亚洲成人麻豆 | 国产精品久久久久久av | 精品亚洲男同gayvideo网站 | 国产一区二区精品91 | 精品国偷自产国产一区 | 西西44人体做爰大胆视频 | 一区中文字幕电影 | 久久久精品网站 | 黄p在线播放 | 日本精品久久久久中文字幕5 | 久久精品99精品国产香蕉 | 天天综合视频在线观看 | 午夜视频在线观看一区二区 | 韩日电影在线 | 麻豆免费视频 | 国产在线看 | 91精品国产福利在线观看 | 99久久激情视频 | 日本精品一区二区 | 91亚色免费视频 | 午夜精品久久久久久久久久久久 | 一级黄色a视频 | 日本中文字幕系列 | 久久精品国产成人精品 | 99在线观看免费视频精品观看 | 天天夜夜操 | 久久免费美女视频 | 国产一区二区三区免费在线 | 制服丝袜在线91 | 啪啪动态视频 | 成年人免费在线观看 | 亚洲男男gaygay无套同网址 | 国产99在线播放 | 精品免费 | 久久久精品久久 | 国产精品美女久久久久久久 | 久久综合9988久久爱 | 国产精品久久久久高潮 | 久久成人资源 | 我爱av激情网 | 日韩电影一区二区三区在线观看 | 综合激情av | 亚洲电影第一页av | 最近乱久中文字幕 | 国产日产欧美在线观看 | 深爱激情婷婷网 | 久久久久久久久久久久电影 | 久久精品96| 天天操夜夜操 |