a.1 底层命令

之前大概介绍了30个Git常用命令,比如checkout/branch/remote等,由于Git的最初目标是一个工具集合,而不是VCS系统,因此包含了大量的底层命令,以便在类Unix系统的脚本中调用,所以这类命令被称为底层命令,而那些对用户更友好的命令,被称为封装命令.之前的介绍以封装命令为主,此刻将更多的介绍底层命令,这可方便用户理解Git的内部机制,同时这些命令并不适合在命令行中手动运行,而是应当放入脚本,实现一些新功能.

当用户在一个文件目录下,执行git init之后,Git将创建一个.git目录,并在此默认目录下,生成Git仓库所需的模板文件,之后用户可将远程仓库克隆到当前的本地目录,以下是初始化之后的.git目录,所包含的模板文件,

$ ls -F1
config
description
HEAD
hooks/
info/
objects/
refs/

不同的Git版本所生成的模板文件,将会稍有不同,description文件只能用于GitWeb工具,config文件包含了与项目有关的配置,info目录中保存了一个全局(对整个项目有效)文件,其中包含了可忽略文件的过滤模板(无法放入.gitignore文件),hooks目录中保存了客户端和服务端的hook脚本,剩余的部分都属于Git核心,objects目录保存了数据库的所有内容,refs目录保存了一些指针,用于指向不同引用(比如分支,标签,远程仓库等)的提交,HEAD文件将指向,用户已进入的当前分支,index文件保存了暂存区的信息.

a.2 Git对象

Git本质上是一个包含地址信息的文件系统,这意味着,Git核心使用了最简单的键值对(key-value pair),来保存相关数据,在Git中,可使用唯一的key,来获取所需的数据.底层命令git hash-object,可附带一些数据,并将这些数据,保存到.git/objects目录(即对象数据库)中,保存之后,将返回一个唯一的key,来标识这些数据.

首先用户需初始化一个新的Git仓库,并且objects目录为空,

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

在objects目录下,Git将生成pack和info子目录,此时两个子目录也为空,使用git hash-object,创建一个新的数据对象,手工保存到Git数据库,

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

上述数据已存入Git数据库,并且附带了一个唯一键值(校验值), -w选项,将数据对象写入Git数据库, --stdin选项,从stdin读取数据对象,同时上述git hash-object命令,还可附带一个文件名参数,用于标识数据对象,命令输出的信息即为40位校验值,查看Git保存的数据对象,

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

objects目录下,已包含了一个新文件,同时校验值的前2位为子目录名,并在该子目录下,包含了一个新文件,文件名为校验值的后38位.使用git cat-file -p,可查看数据对象的内容,

$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content

再次创建2个数据对象,

$ 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

如果删除text.txt文件,可利用Git进行恢复text的不同版本,如下,

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

应当注意,数据对象的校验值,在实际应用中并不方便,同时Git并不保存此类对象的文件名,而是保存文件内容,此类数据对象的类型,被称为blob,使用git cat-file -t,可查看数据对象的类型,

$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob

文件树对象

文件树对象可保存文件名,并允许将一组文件保存在一起,这类操作类似于Unix文件系统,当然更加简单,数据对象可保存成文件树对象,或保存成blob对象,文件树对应于Unix目录,blob对应于节点或文件内容,每个文件树对象可包含一个或多个blob或子文件树,以下是一个示例项目的文件树,

$ 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

注意,在使用master^{tree}格式时,可能会遇到问题,因为在Windows命令行中,^是转义字符,为了避免转义,可使用git cat-file -p master^^{tree},同时{}必须添加引号,以避免参数被错误解析,比如git cat-file -p ‘master^{tree}’,而在ZSH工具中,^将视为一个通配符,也需要使用引号,进行参数封闭,比如git cat-file -p “master^{tree}”.

Git内部的存储结构如下,

此时文件树对象已创建完成,通常情况下,Git将基于暂存区(index)的状态,生成一个文件树对象,因为为了创建一个文件树对象,用户首先需要暂存一些文件,配置index,可使用git update-index,该命令可将文件,添加到暂存区,同时必须附带 --add选项,用于文件添加, --cacheinfo选项,可添加数据库中保存的文件,而非工作区目录,同时必须附带,文件类型(100644),文件的校验码,文件名,

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

100644表示普通文件,100755表示可执行文件,120000表示符号链接,可参考Unix系统的文件类型,文件对象(blob)只能使用以上三种类型,而文件目录和子模块还可选择其他类型.

使用git write-tree可将暂存区,写入一个文件树对象,-w选项,可在文件树对象不存在时,自动创建一个文件树对象,

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

使用git cat-file -t,可查看文件树对象的类型,

$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree

使用test.txt文件的新版本,以及一个新文件,创建一个新的文件树对象,

$ echo 'new file' > new.txt
$ git update-index --add --cacheinfo 100644 \1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ git update-index --add new.txt

此时test.txt新版本和新文件new.txt都已被暂存,

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

注意test.txt新版本的校验码已修改为1f7a7a,这时可将之前的文件树对象,作为子目录,添加到暂存区,使用git read-tree,可将之前的文件树,读入暂存区, --prefix选项,可将读取的文件树对象,视为当前暂存器的子树,

$ 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

基于当前的暂存区,用户可创建一个工作区目录,new.txt和test.txt新版本,以及包含test.txt旧版本的子目录bak,将被放入工作区目录的顶层,Git的存储结构如下,

提交对象

这里使用了暂存区的不同文件树,子文件树(d8329f),初始文件树(0155eb),目标文件树(3c4e9c),描述项目的不同快照,同时快照之中,还需要保存一些信息,比如谁保存了快照,保存时间,保存原因,这些信息都由提交对象给出.

为了创建提交对象,可使用commit-tree,并附带一个文件树对象的校验码,当然文件树对象必须率先创建完成,这里使用首次创建的文件树对象(d8329f),

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

使用提交的校验码,可查看提交所包含的创建时间和作者信息,

$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700first commit

提交对象的格式很简单,它包含了当前项目快照的顶层文件树,作者/提交者的信息(user.name和user.email的配置,以及时间戳),一个空白行,以及提交描述.之后在送入2个提交对象,每个提交对象都指向之前的提交对象,这是一个链表结构,

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

同时每个提交对象,都可放入不同的文件树对象(之前的三个文件树),此时使用git log,可查看Git的提交历史,这里附带最新提交,

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

以上操作只使用了底层命令,构建一个Git仓库,而未使用任何前端命令,即git add或git commit,这些前端命令实现功能,都可使用底层命令来完成,同时包含了三类Git对象,blob/文件树/提交,之前的操作都将在.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

同时上述文件都可视为一个指针,可使用一个图形,来描述这些指针,

对象存储

之前已介绍,在存入Git对象数据库的每个对象中,都会包含一个对象头,用于描述对象,以下将讨论blob(文件内容)对象的保存方式.

首先Git将创建对象头,而对象头的首部将包含,

一个空格 + 文件内容的字节数 + 结束符(\0)

之后将合并对象头和文件内容,并计算文件内容的SHA-1校验值,同时Git将使用zlib工具,对文件内容进行压缩保存,最后写入到对象数据库,所有Git对象的保存方式类似,提交对象和文件树对象,也具有一个对象头,只是这两类对象所保存的内容,与blob对象不一致.

a.3 Git引用

如果用户需要基于提交,来查看仓库的提交历史,可使用git log 1a410e,而1a410e即为某个提交的校验值,并能查看该提交之前的提交历史,为了简化命令的输入,可使用提交的简单名称,而不是校验码,这类简单名称,则称为引用(refs),在.git/refs目录下,可找到与校验值对应的简单名称,在初始项目中,该目录下,未包含标识名称,只有一个简单的目录结构,

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

创建一个新引用,用于帮助最新提交的记忆,

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

基于标识名称master,则可查看对应的提交历史,

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

Git不鼓励用户,直接编辑引用标识,所以提供了一个更安全的命令git update-ref,可实现引用的更新,

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

其实master引用,即为Git分支,因此用户可基于第二个提交,创建一个新分支,

$ git update-ref refs/heads/test cac0ca

用户可直接查看test分支的提交历史,

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

这时Git数据库的整体结构如下,

当用户运行git branch <分支名>命令时,等同于执行update-ref命令,使用当前分支的最新提交的校验码,创建一个新的分支引用.

HEAD

HEAD文件是当前分支的符号引用,它并不是一个普通引用,因此不存在校验值,而是指向其他引用的一个指针,可查看HEAD文件,

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

如果运行git checkout test,Git将立即更新HEAD文件,

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

如果运行git cmmit,将生成一个新的提交对象,并能指向之前的提交对象,同时HEAD也将指向最新的提交对象,当然用户可以直接编辑HEAD文件,但推荐使用更安全的命令git 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

但是无法实现任意设定,

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

标签

除了之前的3种对象类型,还有第4种对象类型,即标签,它类似于提交对象,可包含标注者,日期,描述和指针,差异在于,标签只能指向提交,而不能指向文件树,同时标签无法移动,始终只能指向同一个提交,但是可以设定一个更友好的名称.

在之前的讨论中已知,可配置两种标签,简单标签和附注标签,首先创建一个简单标签,

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

附注标签更复杂,首先需要创建一个标签对象,再将一个引用,写入到标签对象,而不是直接指向提交对象,

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

附注标签的校验码已生成,可基于校验码,查看该附注标签,

$ cat .git/refs/tags/v1.1
9585191f37f7b0fb9444f35a9bf50de191beadc2$ git cat-file -p 9585191f37f7b0fb9444f35a9bf50de191beadc2
object 1a410efbd13591db07496601ebc7a059dd55cfe9
type commit
tag v1.1
tagger Scott Chacon <schacon@gmail.com> Sat May 23 16:48:58 2009 -0700test tag

object项,给出了所指向提交的校验码,注意,此处并不是真正的链接关系,只是标记了一个提交对象,比如在实际应用中,项目维护者添加了GPG公钥(存储为blob对象)之后,可附带一个附注标签.

远程仓库的引用

最后的引用类型,即远程仓库的应用,当用户添加了一个远程仓库之后,可将本地分支的更新,推送到远程仓库,即refs/remotes目录下的所有分支,比如添加一个origin远程仓库,并推送本地的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,

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

远程分支与本地分支的最大区别在于,具有只读属性,用户可以执行git checkout,切换到远程分支,但是Git无法移动HEAD,即无法切换到远程分支,同时用户也无法使用commit命令,直接在远程分支上生成提交,因此远程仓库的意义在于,保存当前项目最后的已知状态.

a.4 打包压缩

继续使用之前的示例,test仓库中,包含了11个对象,4个blob对象,3个文件树对象,3个提交对象和1个标签对象,

$ 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个字节,之后将在仓库中,添加更大尺寸的内容,以测试压缩功能,首先加入repo.rb文件,该文件的大小约为22k,

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

查看之后的文件树对象,确认repo.rb文件所对应的校验码,再使用git cat-file,确认repo.rb文件的大小,

$ git cat-file -p master^{tree}
100644 blob fa49b077972391ad58037050f2a75f74e3671e92    new.txt
100644 blob 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5    repo.rb
100644 blob e3f094f522629ae358806b17daf78246c27c007b    test.txt$ git cat-file -s 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5
22044

小改repo.rb文件并提交,查看之后的结果,

$ echo '# testing' >> repo.rb
$ git commit -am 'modified repo.rb a bit'
[master 2431da6] modified repo.rb a bit1 file changed, 1 insertion(+)

再次检查文件树对象的最新提交,

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

可见repo.rb文件的校验码已发生变化,再次确认该文件的大小,

$ git cat-file -s b042a60ef7dff760008df33cee372b945b6e884e
22054

这时硬盘上出现了两个几乎一样的22k文件,如果保存其中之一,而另一个只保存两个近似文件之间的差异呢,这正是Git的处理方式,Git保存对象的初始格式为loose(宽松),之后Git会将某些对象打包压缩成一个二进制文件,这被称为packfile(压缩)格式,这是为了节省空间和提高效率,如果项目中loose格式的对象太多,用户可手工运行git gc,或者推送到远程仓库,首先使用git gc命令,

$ git gc
Counting objects: 18, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (18/18), done.
Total 18 (delta 3), reused 0 (delta 0)

查看objects目录,可知已生成了一对新文件,

$ find .git/objects -type f
.git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/info/packs
.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.idx
.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.pack

从输出信息中可知,在objects目录中,只保留了未被提交指向的文件内容,即起始2行,因为用户未将其,添加到提交中,而生成的新文件为.pack和.idx, .pack文件包含之前的文件,而之前的文件已被删除, .idx则包含之前文件在.pack文件的存储位置(偏移量或索引),同时之前文件的大小约为15k,打包压缩之后,约为7k.

打包压缩的意义在于,简化文件的命令方式,同时减少文件的存储空间,后续的同名文件,只需保存与前一个版本的差异,使用git verify-pack命令,可查看打包文件的细节,

$ git verify-pack -v .git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.idx
2431da676938450a4d72e260db3bf7b0f587bbc1 commit  223  155 12
69bcdaff5328278ab1c0812ce0e07fa7d26a96d7 commit  214  152 167
80d02664cb23ed55b226516648c7ad5d0a3deb90 commit  214  145 319
43168a18b7613d1281e5560855a83eb8fde3d687 commit  213  146 464
092917823486a802e94d727c820a9024e14a1fc2 commit  214  146 610
702470739ce72005e2edff522fde85d52a65df9b commit  165  118 756
d368d0ac0678cbe6cce505be58126d3526706e54 tag     130  122 874
fe879577cb8cffcdf25441725141e310dd7d239b tree    136  136 996
d8329fc1cc938780ffdd9f94e0d364e0ea74f579 tree     36   46 1132
deef2e1b793907545e50a2ea2ddb5ba6c58c4506 tree    136  136 1178
d982c7cb2c2a972ee391a85da481fc1f9127a01d tree      6   17 1314 1 \deef2e1b793907545e50a2ea2ddb5ba6c58c4506
3c4e9cd789d88d8d89c1073707c3585e41b0e614 tree      8   19 1331 1 \deef2e1b793907545e50a2ea2ddb5ba6c58c4506
0155eb4229851634a0f03eb265b69f5a2d56f341 tree     71   76 1350
83baae61804e65cc73a7201a7252750c76066a30 blob     10   19 1426
fa49b077972391ad58037050f2a75f74e3671e92 blob      9   18 1445
b042a60ef7dff760008df33cee372b945b6e884e blob  22054 5799 1463
033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob      9   20 7262 1 \b042a60ef7dff760008df33cee372b945b6e884e
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a blob     10   19 7282
non delta: 15 objects
chain length = 1: 3 objects
.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.pack: ok

在上述输出中,033b4数据块为repo.rb文件的最新版本,需关联b042a数据块(repo.rb的初始版本),数据块列表的第三项,即为数据块的大小,可知b042a为22k,033b4则为9个字节,因此最新版本中,只包含了与初始版本的差异,同时任何时刻下,还可以重新打包,既可以是Git的自动操作,也可以是用户的手动操作.

a.5 远程映射

在之前的讨论中,用户可简单实现远程分支与本地分支之间的映射,但实际上对应的操作相当复杂,假定用户需要添加一个远程仓库,

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

上述命令执行之后,本地仓库的.git/config文件中,将加入对应的映射配置,

[remote "origin"]url = https://github.com/schacon/simplegit-progitfetch = +refs/heads/*:refs/remotes/origin/*

映射配置的格式为+<src>:<dst>,src为远程分支的路径,dst为本地分支的路径,+表示在推送远程分支时,如果出现冲突,不允许强制推送.

当运行git remote add origin命令时,Git会将refs/heads/下的所有数据,写入refs/remotes/origin/,如果远程仓库只有一条分支master,可使用以下等价的三个命令,访问master分支的运行日志,这三个命令所访问的最终路径,都为refs/remotes/origin/master,

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

当用户只需要获取远程分支master,可修改.git/config文件的fetch参数,

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

使用git fetch,可实现上述分支的默认更新,如果用户只需进行单次更新,可在fetch命令中,指定本地分支和远程分支,如下,

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

当然fetch命令中,也设定多条分支的映射,如下,

$ 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和refs/remotes/origin/mymaster存在冲突,所以获取操作被拒.

同时多条分支的映射,也可在.git/config文件中配置,但不允许在具体分支名中,使用通配符,

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

而在路径(可视为名字空间)中,允许使用通配符,等同于该路径下的所有分支,都将实现映射,如下,

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

推送

如果需将本地master分支,推送到远程分支qa/master,可使用,

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

当然推送操作也可在.git/config文件中配置,当运行git push origin时,将默认使用配置文件的设定,

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

应当注意,使用映射,无法默认实现从一个仓库获取,再推送到另一个仓库,必须由用户手动完成其中一个步骤的远程设定.

删除引用

使用映射关系,也可删除远程仓库的引用,如下,

$ git push origin :topic

由于映射关系的格式为<src>:<dst>,如果<src>参数为空,等同于远程分支topic的映射关系被取消,以达到变相删除的目的,从Git 1.7.0版本开始,也可使用以下命令,删除远程分支,

$ git push origin --delete topic

a.6 传输协议

Git可使用两种方式,实现远程仓库的数据传输,一种是只读协议,另一种是智能协议.

只读协议

如果远程仓库只需基于HTTP实现只读功能,这被称为只读协议,并且在数据传输过程中,服务器无需执行Git工具,因为获取操作即为一组HTTP get请求,同时客户端必须事先了解服务器上Git仓库的目录结构,值得注意,只读协议已很少使用,因为它的安全性很难保证,同时无法配置私有仓库,所以大多数Git主机(云主机和内部主机)都拒绝使用这类协议.

以下是只读协议的用法,首先克隆一个简单仓库,

$ git clone http://server/simplegit-progit.git

clone命令首先将获取info/refs文件(update-server-info命令可生成该文件),同时用户需使能post-receive hook脚本,以便HTTP传输能够正常工作.

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

这时用户将获得一个远程仓库引用(以及对应的SHA-1校验码)的列表,之后可查找HEAD引用,

=> GET HEAD
ref: refs/heads/master

基于HEAD引用,可知当前主分支为master,同时从info/refs文件可知,最新提交为ca82a6提交对象,克隆将从该提交开始,

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

基于一个静态HTTP get请求,获取到最新提交之后,才发现它是一个二进制数据,因此需要进行解压,才可查看提交的细节,

$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
author Scott Chacon <schacon@gmail.com> 1205815931 -0700
committer Scott Chacon <schacon@gmail.com> 1240030591 -0700changed the version number

从最新提交的细节中,又发现了两个远程引用,cfda3b为包含最新提交的文件树,085bb3为最新提交的父提交,继续获取父提交,

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

继续获取文件树对象,

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

获取操作失败,404表示未查找到文件树对象,原因可能是文件树对象放置在另一个备用仓库中,或者文件树对象是一个打包文件,首先获取Git的备用仓库列表,

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

列表为空,则表示未使用备用仓库,再检查打包文件,即获取objects/info/packs文件,其中包含了打包文件的列表,

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

此刻远程仓库中,只有一个打包文件,获取和检查对应的idx文件,是否存在文件树对象,如果存在多个打包文件,则需要逐个排查,

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

在idx文件中,发现文件树对象,以及文件树对象在pack打包文件的偏移量,获取pack打包文件,

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

从pack文件中,解压出文件树对象,至此master分支的本地克隆已完成.

智能协议

由于只读协议的效率过低,同时不支持远程仓库的写入操作,因此智能协议则是一个更通用的数据传输方案,但是在服务器上,需要执行Git工具,以完成相应的功能,这里将实现两组数据传输,即数据上传和数据下载.

数据上传

为了实现远程仓库的数据上传,需在客户端运行send-pack操作,在服务端运行receive-pack操作.

SSH

假定使用SSH协议,当客户端运行git push origin master命令后,将触发send-pack操作,首先将初始化与服务端的SSH连接,类似于以下命令,

$ ssh -x git@server "git-receive-pack 'simplegit-progit.git'"
00a5ca82a6dff817ec66f4437202690a93763949 refs/heads/master□report-status \delete-refs side-band-64k quiet ofs-delta \agent=git/2:2.1.1+github-607-gfba4028 delete-refs
0000

当客户端请求推送master分支的每个引用时,git-receive-pack命令都将立即响应,同时上述输出信息的第一行,列出了服务端可提供的服务,比如report-status,delete-refs等等,并且在输出信息的每行文本的起始处,都将包含四位16进制数,用于标识剩余的传输大小,比如第一行行首为00a5,16进制值为165,即后续的传输数据还剩余165字节,第二行为0000,即表示远程仓库的引用对象已更新完毕.

在send-pack操作中,还将确认服务端是否包含了特定提交,在更新每个远程引用时,send-pack会将相关信息,告知receive-pack,比如用户需要推送master分支,并新增一个experiment分支,send-pack将给出以下信息,

0076ca82a6dff817ec66f44342007202690a93763949  15027957951b64cf874c3557a0f3547bd83b3ff6
\ refs/heads/master report-status
006c0000000000000000000000000000000000000000  cdfdb42577e2506715f8cfeacdbabc092bf63e8d
\ refs/heads/experiment
0000

在更新远程引用时,会实现新旧校验码的替换,同样每行行首都会给出剩余传输数据的大小,同时全0校验值,表示这是一个新增的远程引用,如果删除一个远程引用,全0校验值将在右侧.

之后客户端会将所有需推送的对象(在服务端不存在),打包成一个压缩文件,最后服务端将给出数据传输的响应,即传输成功(或者传输失败),

000eunpack ok
HTTP(S)

基于HTTP的数据上传,和之前的操作类似,除了建立连接的握手方式有所不同,连接的初始化请求如下,

=> GET http://server/simplegit-progit.git/info/refs?service=git-receive-pack
001f# service=git-receive-pack
00ab6c5f0e45abd7832bf23074a333f739977c9e8188 refs/heads/master□report-status \delete-refs side-band-64k quiet ofs-delta \agent=git/2:2.1.1~vmg-bitmaps-bugaloo-608-g116744e
0000

上述命令将完成客户端和服务端的握手,之后客户端将发起另一个post请求,而数据由send-pack提供,

=> POST http://server/simplegit-progit.git/git-receive-pack

post请求中,包含send-pack的输出信息,以及打包文件,之后服务端将给出一个HTTP响应,即传输成功或传输失败.

数据下载

在获取数据时,将包fetch-pack含和upload-pack处理,客户端将初始化一个fetch-pack处理,并对接服务端的upload-pack处理.

SSH

如果基于SSH协议,获取数据,fetch-pack将使用以下命令,

$ ssh -x git@server "git-upload-pack 'simplegit-progit.git'"

fetch-pack建立连接后,upload-pack将影响以下信息,这与之前的上传十分类型,只是方向不同,fetch-pack收到服务端的响应后,可基于响应信息包含的校验码,再次请求所需的远程引用,这时upload-pack会将客户端所需的数据,打包成一个压缩文件,传递给客户端,

003cwant ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0009done
0000
HTTP(S)

基于HTTP协议的获取操作中,完成握手机制,需要两个HTTP请求,首个get请求,用于实现只读协议,如下,

=> GET $GIT_URL/info/refs?service=git-upload-pack
001e# service=git-upload-pack
00e7ca82a6dff817ec66f44342007202690a93763949 HEAD□multi_ack thin-pack \side-band side-band-64k ofs-delta shallow no-progress include-tag \multi_ack_detailed no-done symref=HEAD:refs/heads/master \agent=git/2:2.1.1+github-607-gfba4028
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
0000

第二个HTTP post请求,则可执行服务端的git-upload-pack工具,由该工具回传客户端所需的数据,

=> POST $GIT_URL/git-upload-pack HTTP/1.0
0032want 0a53e9ddeaddad63ad106860237bbf53411d11a7
0032have 441b40d833fdfa93eb2908e52742248faf0ee993
0000

同时服务端还可提供了数据请求的状态,请求成功或请求失败,以及请求成功时,所包含的打包文件.

a.7 维护及数据恢复

有时用户需要完成一些清理任务,比如创建一个更紧凑的仓库,清理一个已导入的仓库,或者恢复一些丢失的工作成果,以下将讨论这些问题.

维护

在gc命令中附带–auto选项,可自动实现仓库维护,但在大多数情况下,该命令形同虚设,如果仓库中包含过多的loose对象(未打包压缩)或是pack对象(已打包压缩),可使用git gc命令进行维护,gc的本意为垃圾收集,该命令可完成,收集loose对象进行打包压缩,将多个pack对象放入一个更大的pack对象,删除数月未被提交访问的对象.

用户可手动调用gc命令,同时还可设定gc.auto和gc.autopacklimit配置,当loose对象数和pack对象数超过配置值,gc将自动处理,另外gc命令,还可将仓库引用,打包到单个文件中,假定仓库包含了以下分支和标签,

$ 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,将其移入.git/packed-refs文件,

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

如果用户此时更新了上述引用,Git将不会编辑这些引用文件,而是在refs/heads写入一个新的引用文件,为了得到一个正确的校验值,Git将获取packed-refs中同一引用文件(旧版本)的校验值,并为新的引用文件,计算校验值,在上述输出的最后一行,^表示上一行是附注标签,^之后的提交校验值,即为上一行附注标签所指向的提交.

数据恢复

如果在管理过程中,造成提交的丢失,比如强制删除一个正处于工作状态的分支,或是强制移动分支指针,导致提交丢失,这些情况都会产生提交的丢失.以下示例是test仓库master分支的强制复位,首先查看master分支的原有提交,

$ git log --pretty=oneline
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first 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

比较输出信息可知,已丢失两个提交,如何恢复这些提交?首先提交之间存在一个链表,当然可以基于链表,恢复所有提交,但是这类方法效率太低,这时可使用git reflog命令,由于Git会备份HEAD指针的每次变动,因此每一次提交或切换分支,reflog都会进行记录,同时使用git update-ref命令,用户可以手工添加reflog的记录,所以git reflog具备恢复功能,

$ git reflog
1a410ef HEAD@{0}: reset: moving to 1a410ef
ab1afef HEAD@{1}: commit: modified repo.rb a bit
484a592 HEAD@{2}: commit: added repo.rb

从输出信息可知,两个丢失的提交已出现,使用git log -g也可以查看丢失提交的记录,

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

下一个提交已丢失,使用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

从输出信息可知,两个丢失提交已恢复,之后再挑战更高难度的恢复,将reflog记录全部删除,并将之前恢复的recover-branch也删除,回到两个提交丢失的初始状态,

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

由于reflog记录保存在.git/logs/目录中,那么只能使用git fsck进行恢复,该命令可检查数据库的完整性,如果附带–full选项,该命令可显示,未与其他对象关联的所有独立对象,

$ git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (18/18), done.
dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4
dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b
dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9
dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293

可见ab1afef提交已出现,由于484a592提交与ab1afef提交存在链表关联,将会一同恢复,使用之前的recover-branch命令,可以再次恢复提交.

移除对象

Git的一些功能,同样会引发问题,比如git clone可下载项目仓库的所有历史,如果项目只包含源代码,Git可实现数据的高效压缩,如果协作者将单个巨型文件,添加到项目中,那么每次克隆都必须下载该巨型文件,即使在后续提交中,移除该巨型文件也没有意义,因为巨型文件已包含在提交历史中,那么它将一直保存在项目仓库.在迁移SVN或Perforce仓库时,也存在相似的问题,因为用户无法获取这些系统的完整历史,如何处理那些未包含在提交历史的多余数据?

注意,以下方法会破坏提交历史,修改与巨型文件关联的所有提交,同时用户还需要通知所有的提交者,必须衍合这些修改后的新提交,这对于协作开发来说,是相当复杂的任务,为了演示这类问题,在test仓库中,添加一个巨型文件,并在后续提交中删除该文件,之后再从仓库中删除该巨型文件,如下,

$ curl https://www.kernel.org/pub/software/scm/git/git-2.1.0.tar.gz > git.tgz
$ git add git.tgz
$ git commit -m 'add git tarball'
[master 7b30847] add git tarball1 file changed, 0 insertions(+), 0 deletions(-)create mode 100644 git.tgz

再删除巨型文件git.tgz,

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

使用gc命令,查看数据库所占用的硬盘空间,

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

再运行count-objects,查看数据库中不同引用对象,所占用的硬盘空间,

$ git count-objects -v
count: 7
size: 32
in-pack: 17
packs: 1
size-pack: 4868
prune-packable: 0
garbage: 0
size-garbage: 0

size-pack给出了所有pack文件的大小,接近5Mbyte(单位为kbyte),经过比较可知,之前添加的巨型文件,还保存在pack文件中,可使用git verify-pack命令,从所有pack文件中,查看巨型文件的保存记录,如下,

$ git verify-pack -v .git/objects/pack/pack-29...69.idx \| sort -k 3 -n \| tail -3
dadf7258d699da2c8d89b09ef6670edb7d5f91b4 commit 229 159 12
033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob   22044 5792 4977696
82c99a3e86bb1267b236a4b6eff7868d97489af1 blob   4975916 4976258 1438

输出信息底部的blob对象为5Mbyte,再使用rev-list命令,可确认该blob对象的真实细节,

$ git rev-list --objects --all | grep 82c99a3
82c99a3e86bb1267b236a4b6eff7868d97489af1 git.tgz

之后用户需要删除所有文件树中,与巨型文件关联的信息,也就是查找与巨型文件有关的提交,

$ git log --oneline --branches -- git.tgz
dadf725 oops - removed large tarball
7b30847 add git tarball

修复上述提交,才可将巨型文件从仓库中,彻底删除,

$ git filter-branch --index-filter \
'git rm --ignore-unmatch --cached git.tgz' -- 7b30847^..
Rewrite 7b30847d080183a1ab7d18fb202473b3096e9f34 (1/2)rm 'git.tgz'
Rewrite dadf7258d699da2c8d89b09ef6670edb7d5f91b4 (2/2)
Ref 'refs/heads/master' was rewritten

–index-filter选项,只对暂存区(index)进行修改,git rm --cache可从暂存区中删除文件,而非硬盘,这种方法速度更快,只需将每个提交恢复到暂存区,而无需恢复项目的整个版本,–ignore-unmatch选项,可告知git rm命令,如果需删除的文件不存在,也无需报错,最后指定,从7b30847提交开始,修复整个提交历史,即剔除与巨型文件相关的记录.

这时巨型文件的记录,已从提交历史中清除,这时reflog记录需要清空,也需要在.git/refs/original中,添加新的引用集合,

$ rm -Rf .git/refs/original
$ rm -Rf .git/logs/
$ git gc
Counting objects: 15, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (11/11), done.
Writing objects: 100% (15/15), done.
Total 15 (delta 1), reused 12 (delta 0)

a.8 环境变量

Git通常会运行在bash shell中,因此可以设定一些shell环境变量,配置默认操作,以下将介绍,不同功能的常用环境变量,

全局配置

以下是Git系统所依赖的环境变量,

GIT_EXEC_PATH
Git查找子命令的路径,比如git-commit,git-diff等等,使用git --exec-path可检查该变量.

HOME
Git将使用该变量,查找全局配置文件,如果用户选择了解压版Git,需要在完成全局配置后,修改此变量.

PREFIX
与操作系统的配置很相似,比如$PREFIX/etc/gitconfig,可查找Git的全局配置文件.

GIT_CONFIG_NOSYSTEM
如果设定该变量,将失效操作系统层的配置,适用于系统层配置会干扰Git命令的场景.

GIT_PAGER
可控制Git命令的输出信息的分页,如果未设定该变量,将使用PAGER.

GIT_EDITOR
当用户需要编辑文本时(比如提交描述),Git将调用该变量,指定的文本编辑器,如果未设定该变量,将使用EDITOR.

仓库配置

以下将给出当前仓库的接口配置,

GIT_DIR
即为.git目录的保存路径,如果未设定该变量,Git将从~(用户根目录)或/(系统根目录)开始,查找.git目录.

GIT_CEILING_DIRECTORIES
可控制.git目录的查找,如果检索目录的载入速度过低(比如磁带机,低速网络目录),该变量可使Git放弃载入速度过低的目录检索.

GIT_WORK_TREE
即为仓库工作区根目录的保存路径,如果指定了 --git-dir命令选项或GIT_DIR变量,同时未指定 --work-tree命令选项,GIT_WORK_TREE变量(配置文件的变量core.worktree),当前工作目录将视为当前文件树的顶层.

GIT_INDEX_FILE
即为index文件的保存路径

GIT_OBJECT_DIRECTORY
即为.git/objects目录的保存路径

GIT_ALTERNATE_OBJECT_DIRECTORIES
这是一个以冒号分隔的列表,格式为/dir/one:/dir/two:…,Git可在该变量指定的路径中,检索未包含在GIT_OBJECT_DIRECTORY目录下的仓库对象,如果项目中包含了多个内容一致的大型文件,使用该变量,可避免这类情况的发生.

路径配置

用于指定Git工具所使用的路径,其中可包含通配符,

GIT_GLOB_PATHSPECS和GIT_NOGLOB_PATHSPECS
可控制路径配置中,通配符的默认用法,如果GIT_GLOB_PATHSPECS设为1,可使用通配符,如果GIT_NOGLOB_PATHSPECS设为1,只匹配单个文件,比如*.c只能匹配,文件名为*.c的文件,而不是以.c结尾的任意文件,同时此类变量还可限定,路径起始字符的大小写,比如:(glob)*.c

GIT_LITERAL_PATHSPECS
可失效GIT_GLOB_PATHSPECS和GIT_NOGLOB_PATHSPECS的配置,不使用通配符,以及路径前缀的限制.

GIT_ICASE_PATHSPECS
可忽略路径中字符的大小写.

提交配置

Git通常会使用git-commit-tree,生成最终的提交对象,该命令需要一些环境变量的支持,如下,

GIT_AUTHOR_NAME
给出author项的信息,即作者名

GIT_AUTHOR_EMAIL
给出author项的信息,即作者的邮箱地址

GIT_AUTHOR_DATE
给出author项的信息,即提交的修改日期

GIT_COMMITTER_NAME
给出committer项的信息,即提交者的名称

GIT_COMMITTER_EMAIL
给出committer项的信息,即提交者的邮箱地址

GIT_COMMITTER_DATE
给出committer项的信息,即提交日期

EMAIL
如果未设定user.email配置变量,将使用该变量,如果未设定该变量,提交将使用系统账号名和主机名

网络配置

Git使用curl库,实现Http的网络通讯,设定GIT_CURL_VERBOSE变量,可将curl库生成的所有消息,传递给Git,比如curl -v的输出信息.

GIT_SSL_NO_VERIFY
放弃SSL证书的验证,有时候该变量必须设定,比如用户使用自签名(self-signed,一种开放式签名(Open Sign)),或者在配置Git服务器的过程中,用户并未安装一个完整证书的情况下.

GIT_HTTP_LOW_SPEED_LIMIT和GIT_HTTP_LOW_SPEED_TIME
如果Http传输速率低于GIT_HTTP_LOW_SPEED_LIMIT的限制,或者传输时间超过GIT_HTTP_LOW_SPEED_TIME的限制,Git可中止当前操作,这些变量可覆盖配置变量http.lowSpeedLimit和http.lowSpeedTime.

GIT_HTTP_USER_AGENT
该变量可为Http传输,设定网络代理,默认值为git/2.0.0

差异与合并

GIT_DIFF_OPTS
可控制git diff命令,输出的文本行,等同于-u或 --unified=命令选项.

GIT_EXTERNAL_DIFF
可覆盖配置变量diff.external,如果设定该变量,在运行git diff时,将调用该变量指定的外部比对工具.

GIT_DIFF_PATH_COUNTER和GIT_DIFF_PATH_TOTAL
在GIT_EXTERNAL_DIFF或diff.external指定的外部比对工具中,可附带一个路径参数,同时该路径未被合并,GIT_DIFF_PATH_COUNTER可指定一个计数器(累加值为1),每对比完一个路径,计数器加1,GIT_DIFF_PATH_TOTAL可限定比对路径的数量.

GIT_MERGE_VERBOSITY
可控制递归合并的输出信息,可设定以下数值,默认值为2,

  • 0,无输出,除非出现错误信息
  • 1,只显示冲突信息
  • 2,显示文件的变更
  • 3,显示未变更(被跳过)的文件
  • 4,显示已处理的所有路径
  • 5,显示上述信息,以及调试信息

调试配置

Git提供了完整的跟踪操作,当用户需要时,可打开调试功能,以下变量可使用的配置值,

  • true/1/2,跟踪信息可写入stderr
  • 以/开头的绝对路径,跟踪信息可写入特定文件

GIT_TRACE
可控制通用的跟踪功能,不限跟踪类型,其中包含了假名扩展,子命令的调用,

$ GIT_TRACE=true git lga
20:12:49.877982 git.c:554         trace: exec: 'git-lga'
20:12:49.878369 run-command.c:341 trace: run_command: 'git-lga'
20:12:49.879529 git.c:282         trace: alias expansion: lga => 'log' '--graph' '--pretty=oneline' '--abbrev-commit' '--decorate' '--all'
20:12:49.879885 git.c:349         trace: built-in: git 'log' '--graph' '--pretty=oneline' '--abbrev-commit' '--decorate' '--all'
20:12:49.899217 run-command.c:341 trace: run_command: 'less'
20:12:49.899675 run-command.c:192 trace: exec: 'less'

GIT_TRACE_PACK_ACCESS
可控制pack文件的跟踪,在输出信息中,第一项为当前访问的pack文件,第二项为pack文件内的文件偏移量,

$ GIT_TRACE_PACK_ACCESS=true git status
20:10:12.081397 sha1_file.c:2088 .git/objects/pack/pack-c3fa...291e.pack 12
20:10:12.081886 sha1_file.c:2088 .git/objects/pack/pack-c3fa...291e.pack 34662
20:10:12.082115 sha1_file.c:2088 .git/objects/pack/pack-c3fa...291e.pack 35175
# [...]
20:10:12.087398 sha1_file.c:2088 .git/objects/pack/pack-e80e...e3d2.pack 56914983
20:10:12.087419 sha1_file.c:2088 .git/objects/pack/pack-e80e...e3d2.pack 14303666
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

GIT_TRACE_PACKET
使能网络数据包的跟踪,

$ GIT_TRACE_PACKET=true git ls-remote origin
20:15:14.867043 pkt-line.c:46 packet: git< # service=git-upload-pack
20:15:14.867071 pkt-line.c:46 packet: git< 0000
20:15:14.867079 pkt-line.c:46 packet: git< 97b8860c071898d9e162678ea1035a8ced2f8b1f HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag multi_ack_detailed no-done symref=HEAD:refs/heads/master agent=git/2.0.4
20:15:14.867088 pkt-line.c:46 packet: git< 0f20ae29889d61f2e93ae00fd34f1cdb53285702 refs/heads/ab/add-interactive-show-diff-func-name
20:15:14.867094 pkt-line.c:46 packet: git< 36dc827bc9d17f80ed4f326de21247a5d1341fbc refs/heads/ah/doc-gitk-config
# [...]

GIT_TRACE_PERFORMANCE
可创建性能数据的日志,其中将包含每条git命令的执行时间,

$ GIT_TRACE_PERFORMANCE=true git gc
20:18:19.499676 trace.c:414 performance: 0.374835000 s: git command: 'git' 'pack-refs' '--all' '--prune'
20:18:19.845585 trace.c:414 performance: 0.343020000 s: git command: 'git' 'reflog' 'expire' '--all'
Counting objects: 170994, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (43413/43413), done.
Writing objects: 100% (170994/170994), done.
Total 170994 (delta 126176), reused 170524 (delta 125706)
20:18:23.567927 trace.c:414 performance: 3.715349000 s: git command: 'git' 'pack-objects' '--keep-true-parents' '--honor-pack-keep' '--non-empty' '--all' '--reflog' '--unpack-unreachable=2.weeks.ago' '--local' '--delta-base-offset' '.git/objects/pack/.tmp-49190-pack'
20:18:23.584728 trace.c:414 performance: 0.000910000 s: git command: 'git' 'prune-packed'
20:18:23.605218 trace.c:414 performance: 0.017972000 s: git command: 'git' 'update-server-info'
20:18:23.606342 trace.c:414 performance: 3.756312000 s: git command: 'git' 'repack' '-d' '-l' '-A' '--unpack-unreachable=2.weeks.ago'
Checking connectivity: 170994, done.
20:18:25.225424 trace.c:414 performance: 1.616423000 s: git command: 'git' 'prune' '--expire' '2.weeks.ago'
20:18:25.232403 trace.c:414 performance: 0.001051000 s: git command: 'git' 'rerere' 'gc'
20:18:25.233159 trace.c:414 performance: 6.112217000 s: git command: 'git' 'gc'

GIT_TRACE_SETUP
可显示Git检索仓库和服务器环境,所得到的信息,

$ GIT_TRACE_SETUP=true git status
20:19:47.086765 trace.c:315 setup: git_dir: .git
20:19:47.087184 trace.c:316 setup: worktree: /Users/ben/src/git
20:19:47.087191 trace.c:317 setup: cwd: /Users/ben/src/git
20:19:47.087194 trace.c:318 setup: prefix: (null)
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

杂项配置

GIT_SSH
该变量可设定一个ssh应用工具,当Git连接ssh主机,将不会使用ssh命令,而会使用该变量指定的应用工具,比如$GIT_SSH [username@]host [-p <port>] <command>,注意,这不是一个封装ssh自定义命令的简单方法,它不支持命令行参数的扩展,或者用户在脚本中,配置GIT_SSH,再自定义命令,当然也可在~/.ssh/config文件中,封装ssh的自定义命令.

GIT_ASKPASS
可覆盖配置变量core.askpass,当Git向用户,询问验证证书时,可调用该变量指定的应用工具,同时可将文本传入应用工具,而应用工具的输出信息,可写入stdout.

GIT_NAMESPACE
可控制仓库应用的名字空间的访问权限,等同于–namespace选项,通常用于服务端,当管理员需要创建单个仓库的多个副本时,可以选择,只独立保存多个副本的仓库引用.

GIT_FLUSH
当增量写入stdout时,该变量可强制Git,使用无缓冲的IO操作,如果配置值为1,将触发Git的定期清空stdout,如果配置值为0,Git将不会清空stdout,如果未设定该变量,清空操作将依赖于当前的Git命令以及stdout的当前模式.

GIT_REFLOG_ACTION
可指定reflog的描述文本,如下,

$ GIT_REFLOG_ACTION="my action" git commit --allow-empty -m 'my message'
[master 9e3d55a] my message
$ git reflog -1
9e3d55a HEAD@{0}: my action: my message

剩余的附录部分, 并无太多参考价值,一并略过,

附录A简介了图形界面的Git工具,gitk和git-gui,以及Visual Studio,Visual Studio Code,Eclipse,Sublime Text,Bash
,Zsh,PowerShell中如何集成Git.

附录B简介了如何在程序开发中,嵌入Git功能,其中提供了多种编程语言可用的程序库,Libgit2,JGit,go-git,Dulwich.

附录C包含了Git常用命令的简介.

Git正解 脱水版 【10. 内部机制】相关推荐

  1. 为什么机器学习算法难以优化?一文详解算法优化内部机制

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 来源:数据派THU,编辑:黄继彦 本文约3500字,建议阅读9分钟本文介 ...

  2. Android Loader 异步加载详解二:探寻Loader内部机制

    Android Loader 异步加载详解二:探寻Loader内部机制 转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/7025991 ...

  3. Git详解之四 服务器上的Git

    Git详解之四 服务器上的Git 服务器上的 Git 到目前为止,你应该已经学会了使用 Git来完成日常工作.然而,如果想与他人合作,还需要一个远程的 Git仓库.尽管技术上可以从个人的仓库里推送和拉 ...

  4. 删除隐藏版本信息 版本回退_Git系列之-分布式版本控制Git详解

    课程简介: 课程目标:通过本课程的学习,将全面掌握Git版本管理工具的配置与使用,以适应工作的需要. 适用人群:具有一定开发基础的开发人员. 课程概述:Git (读音为/gɪt/)是一个开源的分布式版 ...

  5. SQL Server 内存中OLTP内部机制概述(二)

    ----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory ...

  6. Git详解之七 自定义Git

    Git详解之七 自定义Git 自定义 Git 到目前为止,我阐述了 Git基本的运作机制和使用方式,介绍了 Git提供的许多工具来帮助你简单且有效地使用它.在本章,我将会介绍 Git的一些重要的配置方 ...

  7. 创业合伙人时代,股权激励正解

    导读: 股权激励机制正在被越来越广泛地运用到公司员工基本层面.更多海外上市的中国公司,以及未上市的创业企业都在计划推行股权激励方案.作为这项激励措施的潜在受益方,很多公司员工往往习惯性地以对待公司福利 ...

  8. 品茗论道说广播(Broadcast内部机制讲解)(上)

    品茗论道说广播(Broadcast内部机制讲解)(上) 侯 亮 1 概述 我们在编写Android程序时,常常会用到广播(Broadcast)机制.从易用性的角度来说,使用广播是非常简单的.不过,这个 ...

  9. 异常处理器详解 Java多线程异常处理机制 多线程中篇(四)

    在Thread中有异常处理器相关的方法 在ThreadGroup中也有相关的异常处理方法 示例 未检查异常 对于未检查异常,将会直接宕掉,主线程则继续运行,程序会继续运行 在主线程中能不能捕获呢? 我 ...

  10. (原创) 对饱和状态NPN晶体管内部机制的理解分析

    对饱和状态NPN晶体管内部机制的理解分析 转载请注明来源:http://keendawn.blog.163.com/blog/static/88880743201111223949730/ 我对NPN ...

最新文章

  1. mysql 字符集和校对规则
  2. ps 2c语言程序,C语言基础(二)
  3. JavaScript表达式--掌握最全的表达式,一切尽在掌握中,让表达不再是难事
  4. android设置gradle位置,android studio gradle 位置更改
  5. 中石油训练赛 - 手机号码(简单分块+思维)
  6. iOS 开发者账号共用发布证书 (Distribution)问题
  7. 安装SQL2005只有配置工具或 错误码是29506 解决方案
  8. win7重置密码的方法
  9. java web 统计_Java web网站访问量的统计
  10. 大数据之-Hadoop之HDFS_hadoop集群中的安全模式_操作案例---大数据之hadoop工作笔记0075
  11. Leetcode--Java--212. 单词搜索 II
  12. 产品体验报告:百词斩————英语学习的领跑者
  13. nar神经网络_NAR 神经网络多步和单步预测
  14. 三天打鱼两天晒网问题
  15. android市场低迷,销量低迷 安卓厂商mini产品或面临策略调整
  16. openjudge666:放苹果
  17. Linux环境搭建 - update https://apt.repos.intel.com 报错
  18. 【408计算机考研】|【2018统考真题-41】| 给定一个含 n(n≥1)个整数的数组,请设计一个在时间上尽可能高效的算法,找出数组中未出现的最小正整数
  19. Xshell6软件分享
  20. win 8 安装

热门文章

  1. 【小月电子】ALTERA FPGA开发板系统学习教程-LESSON1点亮LED灯
  2. 04zookeeper场景应用-master选举
  3. 项目团队管理:有效管理团队的八个方法
  4. windows下网络测试工具
  5. 职称论文发表格式要求
  6. [开箱即用]一个用PHP实现的文字翻译,支持谷歌、百度、小牛翻译sdk(工厂抽象模式)
  7. 实例详解ISA防火墙策略元素:ISA2006系列之五
  8. clickhouse-client命令行参数及常用案例
  9. 更好的Google Glass:棱镜变长、Intel Atom处理器和外置电池组
  10. 几行代码,把你的小电影全部藏好了!