Git正解 脱水版 【10. 内部机制】

本文深入探讨了Git的底层命令和内部机制,包括Git对象(文件树对象、提交对象、对象存储)、Git引用(HEAD、标签、远程仓库引用)、打包压缩、远程映射、传输协议(只读和智能协议)以及维护和数据恢复。通过实例展示了如何使用底层命令创建、操作Git对象,以及如何理解和使用Git的引用、打包和传输过程。
摘要由CSDN通过智能技术生成

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 -0700

first 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 -0700

    third commit

 bak/test.txt | 1 +
 1 file changed, 1 insertion(+)
 
commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:14:29 2009 -0700

    second commit

 new.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 -0700

    first commit

 test.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目录下,可找到与校验值对应的简单名称,在初始项目中,该目录下,未包含标识名称,只有一个简单的目录结构,


                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值