41.3 稀疏检出和浅克隆

41.3.1 稀疏检出

从1.7.0版本开始Git提供稀疏检出的功能。所谓稀疏检出就是,本地版本库检出时不检出全部,只将指定的文件从本地版本库中检出到工作区,而其他未指定的文件则不予检出(即使这些文件存在于工作区,其修改也会被忽略)。

要想实现稀疏检出的功能,必须同时设置core.sparseCheckout配置变量,并存在文件.git/info/sparse-checkout。即首先要设置Git配置变量core.sparseCheckout为true,然后编辑.git/info/sparse-checkout文件,将要检出的目录或文件的路径写入其中。其中文件.git/info/sparse-checkout的格式就和.gitignore文件格式一样,路径可以使用通配符。

稀疏检出是如何实现的呢?实际上Git在index(即暂存区)中为每个文件提供一个名为skip-worktree的标志位,默认这个标志位处于关闭状态。如果开启该标志位,则无论工作区对应的文件存在与否,或者是否被修改,Git都认为工作区该文件的版本是最新的、无变化的。Git通过配置文件.git/info/sparse-checkout定义一个要检出的目录和/或文件列表,当前Git的git read-tree命令及其他基于合并的命令(git merge、git checkout等)能够根据该配置文件更新的index中文件的skip-worktree标志位,实现版本库文件的稀疏检出。

先在工作区/path/to/my/workspace中创建一个示例版本库sparse1,创建后的sparse1版本库中包含如下内容:


$ls-F

doc1/doc2/doc3/

$git ls-files-s-v

H 100644 ce013625030ba8dba906f756967f9e9ca394464a 0 doc1/readme.txt

H 100644 ce013625030ba8dba906f756967f9e9ca394464a 0 doc2/readme.txt

H 100644 ce013625030ba8dba906f756967f9e9ca394464a 0 doc3/readme.txt


即版本库sparse1中包含三个目录doc1、doc2和doc3。命令git ls-files的-s参数用于显示对象的SHA1哈希值及所处的暂存区的编号。而-v参数则显示工作区文件的状态,每一行命令输出的第一个字符即是文件状态:字母H表示文件已被暂存,如果是字母S则表示该文件skip-worktree的标志位已开启。

下面我们就来体验一下稀疏检出的功能。

(1)修改版本库的Git配置变量core.sparseCheckout,将其设置为true。


$git config core.sparseCheckout true


(2)设置.git/info/sparse-checkout的内容,如下:


$printf"doc1\ndoc3\n">.git/info/sparse-checkout

$cat.git/info/sparse-checkout

doc1

doc3


(3)执行git checkout命令后,会发现工作区中的doc2目录不见了。


$git checkout

$ls-F

doc1/doc3/


(4)这时如果用git ls-files命令查看,会发现doc2目录下的文件被设置了skip-worktree标志。


$git ls-files-v

H doc1/readme.txt

S doc2/readme.txt

H doc3/readme.txt


(5)修改.git/info/sparse-checkout的内容,如下:


$printf"doc3\n">.git/info/sparse-checkout

$cat.git/info/sparse-checkout

doc3


(6)执行git checkout命令后,会发现工作区中的doc1目录也不见了。


$git checkout

$ls-F

doc3/


(7)这时如果用git ls-files命令查看,会发现doc1和doc2目录下的文件都被设置了skip-worktree标志。


$git ls-files-v

S doc1/readme.txt

S doc2/readme.txt

H doc3/readme.txt


(8)修改.git/info/sparse-checkout的内容,使之包含一个星号,即在工作区检出所有的内容。


$printf "*\n">.git/info/sparse-checkout

$cat.git/info/sparse-checkout

*


(9)执行git checkout,会发现所有目录又都回来了。


$git checkout

$ls-F

doc1/doc2/doc3/


文件.git/info/sparse-checkout的格式类似于.gitignore的格式,也支持用感叹号实现反向操作。例如不检出目录doc2下的文件,而检出其他文件,可以使用下面的语法(注意顺序不能写反):


*

!doc2/


注意如果使用命令git checkout—<file>……,即不是切换分支而是用分支中的文件替换暂存区和工作区,则忽略skip-worktree标志。例如下面的操作中,虽然doc2被设置为不检出,但是执行git checkout.命令后,所有的目录还是都被检出了。


$git checkout.

$ls-F

doc1/doc2/doc3/

$git ls-files-v

H doc1/readme.txt

S doc2/readme.txt

H doc3/readme.txt


如果修改doc2目录下的文件,或者在doc2目录下添加新文件,Git会视而不见。


$echo hello>>doc2/readme.txt

$git status

On branch master

nothing to commit(working directory clean)


若此时通过取消core.sparseCheckout配置变量的设置而关闭稀疏检出,也不会改变目录doc2下的文件的skip-worktree标志。这种情况或者通过git update-index—no-skip-worktree—<file>……来更改index中对应文件的skip-worktree标志,或者重新启用稀疏检出更改相应文件的检出状态。

如果在克隆一个版本库时只希望检出部分文件或目录,可以在执行克隆操作的时候使用—no-checkout或-n参数,不进行工作区文件的检出。例如下面的操作从前面示例的sparse1版本库克隆到sparse2中,不进行工作区文件的检出。


$git clone-n sparse1 sparse2

Cloning into sparse2…

done.


检出完成后可以发现sparse2的工作区是空的,而且版本库中也不存在index文件。如果执行git status命令会看到所有文件都被标识为删除。


$cd sparse2

$git status-s

D doc1/readme.txt

D doc2/readme.txt

D doc3/readme.txt


如果希望通过稀疏检出的功能,只检出其中一个目录如doc2,可以用如下方法实现:


$git config core.sparseCheckout true

$printf "doc2\n">.git/info/sparse-checkout

$git checkout


之后看到工作区中检出了doc2目录,而其他文件被设置了skip-worktree标志。


$ls-F

doc2/

$git ls-files-v

S doc1/readme.txt

H doc2/readme.txt

S doc3/readme.txt