Git内部存储原理分享

816 阅读4分钟

在三月九日的不正确的操作导致了release分支代码的丢失,让我认识到了自己对于Git的认识还不够清晰,所以写了这篇文章,总结和分享Git内部存储的知识,加深自己的印象,以及可以方便回忆,有不对的地方欢迎大家指出。

首先,我们可以通过git init命令行初始化,创建git仓库所需要的文件,其中基本目录结构如下

其中主要文件作用如下

    │──COMMIT_EDITMSG // 最近的一次提交的描述信息
    │
    │──config // git仓库的配置文件
    │
    │──description // git仓库的描述信息
    │
    │──HEAD // 包含了一个分支的引用,它是一个指向你正在工作中的本地分支的指针
    │
    │──index // 暂存区,二进制文件
    │
    ├─hooks // 存放shell脚本,可以通过一些特殊的命令触达脚本
    │      applypatch-msg.sample
    │      commit-msg.sample
    │      fsmonitor-watchman.sample
    │      post-update.sample
    │      pre-applypatch.sample
    │      pre-commit.sample
    │      pre-push.sample
    │      pre-rebase.sample
    │      pre-receive.sample
    │      prepare-commit-msg.sample
    │      update.sample
    │
    ├─info
    │      exclude // 存放git仓库的其他信息
    │
    ├─logs // 保存所有更新引用的记录
    │  │  HEAD // 保存最后一次提交的记录
    │  │
    │  └─refs
    │      ├─heads
    │      │      master
    │      │
    │      └─remotes
    │          └─origin
    │                  master
    │
    ├─objects // 所有对象的存储,对象的哈希串值的前两位是文件夹名称,后38位作为对象文件名,包括三类对象commit,tree和blob
    │ 
    └─refs // 引用
        ├─heads // 存储本地所有分支文件最后一次提交的记录
        │      master
        │
        ├─remotes // 存储远程仓库的分支文件最后一次提交的记录
        │  └─origin
        │          master
        │
        └─tags

再介绍一下工作区,暂存区,版本库的概念

概念

工作区:用来编辑保存项目文件的地方,也是用户能直接操作到的地方,在目录中,除了.git文件外的区域都是工作区。

暂存区:保存了下次将提交的文件列表信息,一般在Git仓库目录中,是一个叫index的文件,也叫暂存区域,可以通过 git add 将工作区的文件添加到暂存区。

版本库:文件中有一个.git隐藏文件,这个不算工作区,而是Git的版本库。也叫本地版本库,之所以说git快,是因为它是分布式版本控制系统,大部分提交都是对本地仓库而言的,不依赖网络,最后一次会推送的到远程仓库。

关系如下图

当对工作区修改(或新增)的文件执行 "git add" 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。

当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。

结合几个比较常见的场景

git add .

echo 'hello' > add.txt
echo 'test' > README
git add .
git commit -m 'init'

查看object文件,可以看到objct对象库中新增了4个文件,分别为两个文件blob对象,一个commit对象,一个tree对象,可以知道git根据文件的信息生成的hash值,并且前两位作为目录名。可以通过git cat-file -p/t去查看对象库中的内容和类型,输入命令行cat .git/HEAD根据HEAD指针可以看到refs/heads/master下的hash值存储着commit对象,再查看tree对象的内容,为blob对象,存储着提交的文件的信息。

这几个对象的关系如下

git stash

这个算是一个比较常见的操作了,在基于一个分支进行操作的时候,添加了许多代码,但是我们又要切换到另一个分支去做其他,就可以通过stash暂时的将这些代码存储起来了。

echo 'hello world' >> add.txt
git stash

可以看到refs文件中生成了stash文件,其实git stash也是生成了一个commit对象,查看该对象,存储了修改后add.txt文件,和父节点。git stash生成的commit对象有两个parent,一个是前面一次git commit命令生成的commit,另一个对应于保存到stage中的commit.

回滚代码的正确姿势 git reset

echo 'wrong something' >> add.txt
git add .
git commit -m 'wrong'
git log
git reset --hard [commitId]

可以使用git log找出需要回退commit哈希值,可以使用--hard使得修改的内容不被保存在暂存区里面,可以看到HEAD也是指向回退的commit哈希值。

对象的存储

Git object是通过下面的方式处理并存储在git内部的文件系统中的:

1)首先创建一个header,header的值为 “对象类型 内容长度\0”

2)将header和文件内容连接起来,计算得到其hash值

3)将连接得到的内容压缩

4)将压缩后的内容写入到以 “hash值前两位命令的目录/hash值后38位命令的文件” 中

总结

对于git的三种对象,blob可以理解为存储文件的内容,tree理解为目录的结构,commit理解为存储每一次提交信息。git内部通过object对象,来实现git对版本的控制,理解了git内部的存储可以更好的去管理我们的代码。