git的实现原理

76 阅读4分钟

git的三个区域

image.png

image.png

  • git clone 克隆远程资源到本地目录,作为工作目录;

  • 本地在新增/修改/删除之后,可以通过git status 查看文件的变动,还可以使用git diff对比文件的变化;

  • 然后使用git add添加修改的文件到暂存区;

  • 在添加之后,可以使用git commit 提交到本地git仓库并生成记录;

  • git push将本地的修改推送到远程的git服务器。

  • 如果远程修改了,需要同步远程的内容可以使用git fetch,当然也可以直接git pull同步并更新本地的文件;

  • 如果发现错误,可以使用git reset撤回本地的提交记录;

  • 如果发现远程记录错误,可以使用git revert 生成一个新的提交来撤销远程记录的变更内容

  • 你可以使用git branch来增删查改分支,使用git checkout来切换当前分支

  • 可以使用 git merge 来合并分支,也可以使用 git rebase来进行变基

存储实现原理

在Git中,一切都被视为对象。对象可以是文件内容、目录结构、提交信息、分支信息等。Git有三种主要类型的对象:blob(文件内容),tree(目录结构),commit(提交信息)。

目录结构

我们创建一个新目录,并执行git init初始化为一个git版本库,然后查看生成的隐藏目录.git

$ find .git/ -type d # 查看.git/下的所有目录
.git/
.git/hooks
.git/info
.git/objects
.git/objects/info
.git/objects/pack
.git/refs
.git/refs/heads
.git/refs/tags

git中所有生成的对象都存储在objects目录中

$ find .git/objects/ -type f # 查看.git/objects下的所有文件

1. git add原理

git add 会生成一个blob对象,然后将该对象添加到index区(即暂存区)

$ echo 'first add file'>readme.md

上面命令创建了一个文件readme.md,我们可以执行find .git/objects/ -type f看到 .git/objects 目录下面没有文件, 执行git status 看到 readme.md 文件是红色的,代表未加入index区;

接着我们执行

$ git add readme.md

$ find .git/objects/ -type f
.git/objects/6a/0b867bdc470c582c15906f264b9fec371cdbfc

objects目录下多了一个文件,git status发现文件已经变成绿色,代表已添加进index区了

文件路径中的 6a 加上文件名 0b867bdc470c582c15906f264b9fec371cdbfc 可以合成一个 40 字符的hash值,这个字符就是 readme.md 文件在Git 仓库中的 唯一键,一个将待存储的数据外加一个头部信息(header)一起做 SHA-1 校验运算而得到的校验和

值得一提的是只要内容没变那么hash值也不会变,也就是说不会生成新的对象

我们可以使用giit cat-file查看该目录下的对象,-t选项表示查看对象类型,-p表示查看对象内容

$ git cat-file -t 6a0b867bdc470c582c15906f264b9fec371cdbfc #可以只使用部分前缀,只要确能找到唯一对象即可。
blob
$ git cat-file -p 6a0b867bdc470c5
first add file

2. git commit原理

进行代码提交git commit时,需要根据暂存区的内容,先生成tree对象,再生成commit对象,然后会将记录记录到logs文件夹下

$ git commit -m "first commit"

$ find .git/objects/ -type f
.git/objects/37/043b77a8d35b29a7d385d7658d58bd649e4b18
.git/objects/6a/0b867bdc470c582c15906f264b9fec371cdbfc
.git/objects/d8/d965c56c04e851e3b47f524c6c52a24c396857

发现objects文件夹下多了两个对象,分别查看一下对象的类型和内容

$ git cat-file -t d8d965
tree

$ git cat-file -p d8d965
100644 blob 6a0b867bdc470c582c15906f264b9fec371cdbfc    readme.md

这是一个 tree对象 ,表示一个目录结构,目录下有一个 readme.md 的文件

  • 100644表示 readme.md 是文本类型的文件(如果是040000表示文件类型是目录)

  • blob 表示该对象类型

  • 6a0b867bdc470c582c15906f264b9fec371cdbfc 表示该文件的 hash

$ git cat-file -t 37043b
commit

$ git cat-file -p 37043b
tree d8d965c56c04e851e3b47f524c6c52a24c396857
author henry <henry@xxx.com> 1709619995 +0800
committer henry <henry@xxx.com> 1709619995 +0800

first commit

这是一个 commit对象 ,表示一个提交信息

  • 提交对象的内容先指定一个顶层tree对象,代表此次提交点的项目快照;然后是可能存在的父提交(即上一次提交);

  • 之后是作者/提交者信息(依据你的 user.name 和 user.email 配置来设定,外加一个当前时区的时间戳);

  • 留空一行,最后是提交的注释

除了上面两个对象,还发现 .git目录 下还多了一个 logs目录,查看一下里面的文件

$ find .git/logs/ -type f
.git/logs/HEAD
.git/logs/refs/heads/master

.git/logs/HEAD 保存了 HEAD 引用的历史信息

.git/logs/refs/heads 存放本地git仓库所有的分支历史信息文件,例如这里的 master分支文件

每个文件保存了以下信息:

  • 每次提交的详细信息: 包括提交的哈希值、作者、时间戳、提交消息等。
  • 分支移动的记录: 记录了每次分支移动(包括切换分支、合并分支等)的详细信息。

我们可以直接使用git log来查看记录

3. git branch/git tag原理

## 基于分支创建分支
$ git branch dev master
## 基于分支创建tag
$ git tag 1.0.0 master

$ git branch
dev 
* master

$ git tag
1.0.0

此时我们查看.git/refs/目录的文件

$ find .git/refs/ -type f
.git/refs/heads/dev
.git/refs/heads/master
.git/refs/tags/1.0.0

打开其中一个文件,可以发现内容一个 commit对象 的hash值

至此我们明白了branchtag都是基于commit对象生成的,他们指向了某一个提交对象

4. Head指针

.git文件夹下有个HEAD文件,内容是一个指针,要么指向分支,要么指向一个commit对象

在分支上时

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

在分离HEAD状态时(切换到tag也是分离HEAD状态)

$ git checkout 1.0.0

$ cat .git/HEAD
37043b77a8d35b29a7d385d7658d58bd649e4b18

指向分支其实最终也是指向一个commit对象

补充

当知道了上面这些原理,有时候我们使用git reset --hard不小心丢弃了本地的提交记录,可以使用git log查找commit_hash后,执行git reset --hard <commit_hash>来进行恢复

其他

.git/index:记录暂存区(stage)里文件的状态和内容哈希值,是二进制文件,不适合直接编辑或查看,可使用 git status 查看;

.git/info/exclude:用于配置不希望被 Git 跟踪的文件或文件夹。这个文件类似于 .gitignore 文件,但它是针对该仓库私有的,不会被提交到版本控制中;

.git/config:记录了仓库配置,可查看编辑;

.git/COMMIT_EDITMSG:仅用于临时存储提交信息,方便后续作为提交的一部分存储到提交记录中;

ORIG_HEAD:当你执行像 git reset 或者 git rebase 这样的操作时,Git 会移动 HEAD 到新的位置,并且旧的 HEAD 的位置会被保存在 ORIG_HEAD 中;如果你后悔了这个操作,可以使用 git reset --hard ORIG_HEAD 来恢复到原来的状态;

description:用于存储仓库的描述信息,非必需的文件,不影响git正常使用;