1.Git底层命令
从根本上来讲 Git 是一个内容寻址(content-addressable)文件系统,一般可以通过各种Git交互指令进行Git本地操作。我们常见的add、commit等更友好的命令被称作“上层(porcelain)”命令。除此之外,它还包含了一部分用于完成底层工作的子命令, 这些命令被设计成能以 UNIX 命令行的风格连接在一起,抑或藉由脚本调用,来完成工作。 这部分命令一般被称作“底层(plumbing)”命令。
1.1 git hash-object 数据保存
该命令可将任意数据保存于
.git/objects
目录(即
对象数据库
),并返回指向该数据对象的唯一的键(HashValue),唯一键的格式为 40 个字符的校验和, 这是一个校验得到的 SHA-1 哈希值。
git hash-object [-t <type>] [-w] [--path=<file>|--stdin]
-t <type> : 指定存储文件的数据类型(默认为"blob"类型)
-w : 指示该命令将该对象写入对象数据库中,若不指定此选项则该命令仅返回对应的键值
--path=<file> : 待存储文件的路径
--stdin : 指示该命令从标准输入读取内容;若不指定此选项,则须在命令尾部给出待存储文件的路径。
该命令的使用实例如下:
$ git hash-object -w test.txt
d670460b4b4aece5915caf5c68d12f560a9fe3e4
1.2 git cat-file 数据查看
该命令用于提供存储库指定对象的内容或类型和大小等信息。
git cat-file [-t|-p|-s] [hashValue]
-t : 显示目标对象压缩文件的git数据类型
-p : 自动判断内容的类型,并为我们显示格式友好的大致内容
-s : 显示目标对象压缩文件的大小Size
该命令的使用实例如下:
$ git cat-file -t fde3abac99d282b2521c6dd08b3bf7afb165cb43 #查看数据对象的git类型
blob
$ git cat-file -p 507fc6d99dc99dac419043e1da8dfbe8d0d66656 #查看数据对象的内容(此为commit对象)
tree b3a0a80b15cff0a103c558c60a0f1ee5c695c21d
author Your Name <wangxin221@zju.edu.cn> 1655607264 +0800
committer Your Name <wangxin221@zju.edu.cn> 1655607264 +0800
$ git cat-file -s fde3abac99d282b2521c6dd08b3bf7afb165cb43 #查看数据对象的大小
4297
1.3 git ls-files
该命令用于提供当前暂存区index中的数据对象信息。
git ls-files -s|--stage #输出暂存区的内容(文件模式、对象hash键、暂存区编号、对象文件名)
$ git ls-files -s
100644 a0dddc6fb8c6b3feeeffa6e29bedca338e483382 0 .gitignore
100644 b8f015363e6781940b3d9c90d90353eb608c6bc3 0 README.md
100644 e9558405fdcc02f12d757acb308e02937a7444f1 0 babel.config.js
100644 7b08218b548c2e60c5ecf7412c2ee1d9b064e779 0 license
100644 8f892dde47396540608f9a34b1353fc58f5ed6aa 0 package-lock.json
其中文件模式码如下:
100644:表明这是一个普通文件;
100755:表示一个可执行文件;
120000:表示一个符号链接;
1.4 git update-index 添加暂存区
该命令用于将修改暂存区Index,将工作区目录的内容在暂存区进行注册。
git update-index
[--add] [--remove | --force-remove] <file>
[(--cacheinfo <mode>,<object>,<file>)…]
--add : 如果指定的文件不在索引中,则会添加该文件(首次添加文件到暂存区,当前目录下)。默认行为是忽略新文件。
--remove : 如果指定的文件在索引中但丢失,则会将其删除(更新暂存区删除不存在的文件,当前目录下)。默认行为是忽略删除的文件。
--cacheinfo : 直接向暂存区Index文件中插入信息(位于Git数据库中而不是位于当前目录下的任意数据),需要指定文件模式、SHA-1 与文件名
该命令的使用实例如下:
#直接插入内容
$ git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt
#指定文件插入
$git update-index --add test.txt
1.5 git write-tree 生成树对象
该命令用于从当前暂存区Index中创建生成树对象,返回tree对象存储的HashValue键。
$ git write-tree #生成当前暂存区的tree对象
d8329fc1cc938780ffdd9f94e0d364e0ea74f579 #对象哈希
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579 #查看对象类型
tree
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579 #查看对象内容(该tree包含两个blob节点)
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
1.6 git commit-tree 提交树对象
该命令用于创建新的提交commit对象,包括包裹的tree对象、父提交信息、提交说明、作者等信息,返回生成提交对象的HashValue键
git commit-tree [(-p <parent>)…] [-m <message>] <tree>
tree : 顶层树对象
-p <parent> : 当前提交对象的一个父提交对象hash;如果没有则省略,有多个(分支)则配置多条
-m <message> : 提交说明信息
该命令的使用实例如下:
$ git commit-tree -p fdf4fc3 -m "first commit" 0155eb
cac0cab538b970a37ea1e769cbbde608743bc96d
$ git cat-file -p cac0cab
tree 0155ebc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700
first commit
1.7 find .git/objects -type f 查看数据库列表
该命令用于查看
.git/objects
存储目录文件列表。 这就是 Git 存储内容的方式:一个文件对应一条内容, 以该内容加上特定头部信息一起的 SHA-1 校验和为文件命名。 校验和的前两个字符用于命名子目录,余下的 38 个字符则用作文件名。
$ find .git/objects -type f
#哈希值为0796cc7b383220a686523490dee4f51b5bae0db8
.git/objects/07/96cc7b383220a686523490dee4f51b5bae0db8
.git/objects/08/8962cd4fdc2da44e47f772c69416f795d0c21a
.git/objects/0d/e8313f0e565eb71e052c184fbe267d8e4c8a80
.git/objects/1b/744b65d862a17b76e81a01f86001bb8f5270e7
.git/objects/1d/66de0be0a8b0a445b6d88c79a6a3628aa7df0e
.git/objects/24/97d8b2fe0832c59b98c2fe20b0716825427ce2
1.8 一次底层提交过程
$ git init
Initialized empty Git repository in D:/codes/git-demo/.git/
# 1.第一次commit提交版本(包括main.txt,new.txt文件)
$ git hash-object main.txt
e87aea7df13d27519cd6855c18b4c9083759b1e9
$ git hash-object new.txt
2d8280f68c298a83e23bc90861a98e0344f25ac9
$ git update-index --add main.txt
$ git update-index --add new.txt
$ git write-tree
3ff30778b303080013894d64e482dfadad2af35a
$ git commit-tree -m "first commit" 3ff30778b
a9325babbbbf7e21330bc1d152a79b4b0e4e1528
# 2.新增tools.txt文件,第二次commit提交版本
$ git hash-object tools.txt
26cbaf609d6fac3cc6e0766bc225208cb945e5da
$ git update-index --add tools.txt
$ git write-tree
2c9ff217c4000ddabcf50a91d1854d82c56e464c
$ git commit-tree -p a9325babbb -m "second commit" 2c9ff217c4
f4687cae3d0d17bdac2d4be5fcce7c0c80644cee
$ git cat-file -p f4687ca
tree 2c9ff217c4000ddabcf50a91d1854d82c56e464c
parent a9325babbbbf7e21330bc1d152a79b4b0e4e1528
author Wangxin <1436218372@qq.com> 1658388672 +0800
committer Wangxin <1436218372@qq.com> 1658388672 +0800
second commit
2.Git目录结构
当在一个新目录或已有目录执行
git init
时,Git 会创建一个
.git
目录。 这个目录包含了几乎所有 Git 存储和操作的文件和数据。 如若想备份或复制一个版本库,只需把这个目录拷贝至另一处即可。
.git
目录的典型结构及说明如下:
.git/
├── hooks
├── info
│ └── exclude
├── logs
│ ├── refs
│ └── HEAD
├── objects
│ ├── info
│ └── pack
│ └── xxx
├── refs
│ ├── heads
│ └── tags
├── config
├── description
├── HEAD
└── index
hooks :
该目录包含客户端或服务端的钩子函数脚本文件(hook scripts),用于在 git 执行过程中进行一些额外的脚本操作(比如代码拉取前、代码推送前、代码推送后…)
info :
存放了一些仓库的信息文件。其中的 exclude 文件是一个全局性排除(global exclude)文件, 用以放置那些不希望被记录在
.gitignore
文件中的忽略模式(ignored patterns)
logs :
存放了所有更新的引用记录与操作日志(包括refs、HEAD)
objects :
存储所有Git的数据对象内容,主要包括blob、commit、tree、tag四种数据类型的object。Git 存储内容的方式为一个文件对应一条内容, 以该内容加上特定头部信息一起的 SHA-1 校验和为文件命名。 校验和的前两个字符用于命名子目录,余下的 38 个字符则用作文件名。其中info、pack目录用于存储大文件拆分后的信息和子数据。
refs :
该目录存储记录指向数据(分支、远程仓库和标签等)的提交对象的指针。
config :
该文件记录了所有的本地库相关配置,优先级为本地库配置 > 用户配置 > 系统配置
description :
供 GitWeb 程序使用(远程仓库),记录了仓库的描述信息
HEAD :
一个全局指针文件,用于指向目前被检出的分支
index :
暂存区(二进制)文件,用于保存和记录暂存数据
3.Git基本对象
Git的本质是一个内容寻址文件系统,其核心部分是一个简单的
键值对数据库(Key-Value DataBase)
。你可以向该数据库插入任意类型的内容,它会返回一个键值(HashValue),通过该键值可以在任意时刻再次检索该内容。Git中主要有三种主要的基本数据对象类型,分别为数据对象(blob object)、树对象(tree object)、提交对象(commit object),他们均存放于
.git/objects
目录(即对象数据库)下。这些对象的核心为:
blob对象对应文件的一个个版本,tree对象对应目录的一个个版本,commit对象对应项目的一个个版本。
3.1 blob object
blob object是Git系统中最基本的数据对象,对应文件的一个个版本。其对应的Key-Value格式中:Key为根据单个文件内容所计算出来的Hash散列值,Value为该文件内容经压缩后的二进制文件流。除此之外,文件名并没有被保存——我们仅保存了文件的内容。其相应的生成命令为:
git hash-object -w
$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a #查看blob对象类型
blob
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a #查看blob对象内容
version 2 #显示文件数据内容
3.2 tree object
tree object是基于blob object的上层组织对象,记录了该
目录的基本信息数据
(包含blob节点的Hash值、文件名以及嵌套子tree的Hash值、tree名等基本信息内容),对应目录的一个个版本。其对应的Key-Value格式中:Key为根据树内容所计算出来的Hash散列值,Value为该目录信息经压缩后的二进制数据流。
tree object允许我们在blob object的基础之上将多个文件组织到一起,解决文件名等信息保存的问题。并且一个树对象包含了一条或多条树对象记录(tree entry),
每条记录含有一个指向数据对象或者子树对象的 SHA-1 指针
,以及相应的模式、类型、文件名等信息。其相应的生成命令为:
git write-tree
$ git cat-file -t 3c4e9cd789d88d8d89c1073707c3585e41b0e614 #查看tree对象的类型
tree
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614 #查看tree对象的内容
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak #子tree
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
3.3 commit object
commit object是Git系统中的顶层数据对象,记录了该
项目一次完整提交的基本信息数据
(提交者和作者信息、顶层目录根树对象、提交注释、父提交对象等信息),对应项目的一个个版本(项目快照)。其对应的Key-Value格式中:Key为根据提交内容所计算出来的Hash散列值,Value为该提交内容经压缩后的二进制数据流。
在tree object的基础上,我们已经可以保存记录整个项目所有文件系统的数据,但是我们仍完全不知道是谁保存了这些快照,在什么时刻保存的,以及为什么保存这些快照等。 而以上这些,正是提交对象(commit object)能为你保存的基本信息。其相应的生成命令为:
git commit-tree
提交对象的格式很简单:它先指定一个顶层树对象,代表当前项目快照; 然后是可能存在的父提交(如果没有则不显示); 之后是作者/提交者信息(依据你的
user.name
和
user.email
配置来设定,外加一个时间戳); 留空一行,最后是提交注释。
$ git cat-file -p fdf4fc3 #查看commit对象内容
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700
first commit
3.4 Git数据的存储
文件压缩方式:
Git对数据进行存储的本质是通过SHA-1哈希算法,根据
文件内容
生成对应的40位哈希值,并将文件压缩,生成key-value键值对存入objects本地数据库。因此如果文件内容没改变,则其对应的生成哈希值也是不变的,不会新增存储数据;但如果文件内容发生改变,则会生成新的哈希,新增该文件对应的存储数据。
数据存储方式:
除此之外,对
Git本地存储库来说一般只会增加数据(实现版本可回溯的关键)
,而不会轻易从数据库中删除压缩数据;我们所做的任何操作(即使是回退代码),都在向 git 数据库中添加数据,这意味着 git 几乎不会执行任何可能导致文件不可恢复的操作。简而言之Git本地数据库内存一般是只增不减的,但由于使用的文件压缩算法,所以存储库本身内存占用并不是很大。如果遇到大文件,则Git会将文件进行
拆分压缩
,存入pack(拆分压缩数据)+info(拆分信息)目录下。