djk3000 / ME

4 stars 2 forks source link

Git学习2-Git内部原理 #95

Open djk3000 opened 2 years ago

djk3000 commented 2 years ago

Git最重要的概念就是工作区,暂存区,版本库。 要弄懂这些就先要看一下git的工作原理是什么,在我们用git init和git clone之后,在本地会生成一个目录,这个就是我们平时开发时候的项目目录,这是一个隐藏目录,这里面就是我们项目信息目录。我之前新建了一个本地仓库,进去看一下.git的目录:

$ tree -L 1
.
├── COMMIT_EDITMSG
├── HEAD  //指向一个分支,告诉你在哪个分支上
├── config  //项目的配置信息,通过git config命令可以改
├── description
├── hooks  
├── index  //索引文件
├── info  //.gitignore中的一些忽略文件
├── logs   //refs的历史信息
├── objects  //git仓库的所有对象
├── packed-refs
└── refs  //记录了每个分支所指向的commit

HEAD 文件、 objects 目录、index 文件、refs 目录。 它们都是 Git 的核心组成部分。

.git重要目录详解

HEAD

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

这里面就是当前头指针的引用,一般是分支引用,然后看到这个指向的refs文件夹中的master分支是一个40位的hash值,就是这个分支上最新提交的commit的hash值。

$ git checkout develop
Previous HEAD position was 95bf4cf read
$ cat .git/HEAD       
ref: refs/heads/develop

如果我们切换分支,那头指针就是指向分支:

$ git checkout 95bf4c
Note: switching to '95bf4c'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

HEAD is now at 95bf4cf read

$ cat .git/HEAD
95bf4cf2e9ba96a406760edbface75b8e4ccf6f9

如果处于分离头指针的状态下,他的值就是某一次的commit。 结论就是HEAD就是指向的分支指针,分支指针指向的就是最新的提交commit。 那HEAD指向的hash值对应的东西是什么呢,来看下一个objects的目录。

objects目录

其实git仓库的所有对象都保存在object文件夹里面,这个就是我们的仓库,我们的文件,图片等都存放在这里,每个文件都是一个二进制文件。

$ tree .git/objects 
.git/objects
├── 0c
│   └── 7ab14fd267bdf66a5a6fda202c15d75cb5860b
├── 18
│   └── 0c4bb3d7be1922d90625c65542600a2aa54585
├── 23
│   └── dc1033795c2202cddda85b7034386f3ab71f51
├── 2c
│   └── 63384a02e71b638cbdc472103a9143ff497b04
├── 9e
│   └── 6a2d7dc76d40fc5565be3a2f2781ed1c704184
...
├── info
└── pack

可以看到他是一个40位的hash值,这就是我们的commit对象,以前2位作为文件夹,后38位作为文件名的二进制文件名,在里面包含了4中git类型的对象blob、tree、 commit、tag,一个一个来看一下:

  1. commit对象

可以通过git cat-file -p hash值来再看一下他里面的内容,这个文件内部,看一下上面的master对应的最新commit。

$ git cat-file -p 9e6a2d                    
tree 6495523cd754f32cf26bbc52f0669dfa0a3d854c
parent 180c4bb3d7be1922d90625c65542600a2aa54585
author djk <djk3000@126.com> 1659510810 +0800
committer djk <djk3000@126.com> 1659510810 +0800

新建style文件夹

这就是我们提交的commit对象他里面记录了,他包含了:

然后通过这个tree来看文件夹里面有什么。

  1. tree对象
    $ git cat-file -p 64955 
    100644 blob 5d6b832ce2e5c143d784c866f5a5e3fb9cbfc793    .DS_Store
    100644 blob cc61845385359c84a84f557caa49b7adc608ad0e    readme.md
    040000 tree b5812c4ec497713ceb5ba0843c3242ea8debf4fa    style

    前面这个10064代表文件模式:

    • 040000: 目录
    • 100644: 常规非可执行文件
    • 100664: 常规不可执行的组可写文件
    • 100755: 常规可执行文件
    • 120000:符号链接
    • 160000:Gitlink

上面那个commit文件夹中包含了这个tree对象,这个tree对象是一个list,包含了:

继续往下看这个blob对象。

  1. blob对象
    $ git cat-file -p cc61845
    readme文件

    可以看到这个就是我具体文件的内容

这样就可以把上面整个流程给他串起来了: HEAD->master分支->objects中的hash值(commit id)->tree(文件夹)->tree or blob(嵌套文件夹或内容) 这就是你的一个分支包含的内容,所以不管是哪个分支,都是通过这样的流程找到你的代码。

还有一个tag对象,我也新建一个看一下:

  1. tag对象
    $ git tag -m'create tag' v1.0
    $ git cat-file -p v1.0
    object 9e6a2d7dc76d40fc5565be3a2f2781ed1c704184
    type commit
    tag v1.0
    tagger djk <djk3000@126.com> 1659512542 +0800

    tag就和写代码一样的我就在最新的commit上打了一个标签,指向的也是最新的commit对象。

index文件

git ls-files --stage
100644 cc61845385359c84a84f557caa49b7adc608ad0e 0   readme.md
100644 cc61845385359c84a84f557caa49b7adc608ad0e 0   style/readme2.md

git的暂存区就在这个index文件中,所以我们也可以把暂存区叫做index。 里面存放了与当前暂存内容相关的信息,包括暂存的文件名、文件内容的SHA1哈希串值和文件访问权限。这也是个二进制文件。具体内容我们看不见。 注:通过这个命令查看不代表我这两个文件在暂存区,他记录了这两个文件的信息。git status就是通过index文件中的内容和工作区以及版本库中的差异来提醒你那些是需要提交的。

refs目录

tree .git/refs 
.git/refs
├── heads
│   ├── develop
│   ├── master
│   └── modify-readme
└── tags
    └── v1.0

在上面HEAD的地方,可以看到HEAD就是我们切分支的引用在这个文件夹中,每一个文件都记录着一个hash值,对应的commit。 git就是通过HEAD和refs来判断所在分支和最新的提交。

工作区,暂存区,版本库

看完.git目录后,再来理解这三个重要的区域。

工作区

就是开发代码的目录,除去.git目录,剩下的就是工作区。

暂存区

暂存区其实就是这个.git/index文件。里面记录着目录和文件的引用hash值,而不是真正的文件对象。

版本库

版本库其实就是.git/objects目录

再来走一遍流程: 当我们git add的时候,其实是做了2件事:

  1. git hash-object //创建一个新的hash数据对象并将它存入你的.git/object
  2. git update-index --add //更新.git/index索引,使它指向新生成的objects目录下的文件

所以暂存区实际上就是一个包含文件索引的目录树,他记录了文件名、文件的状态信息(时间戳、文件长度等),实际文件在存在 Git 对象库(.git/objects)中。

然后git commit的时候,其实做了下面3件事:

  1. 生成一个commit对象
  2. 把暂存区目录写到版本库中,就是生成tree对象,tree的引用和index引用一样
  3. 更新refs的文件引用,指向最新的commit的hash。

参考资料

Git 内部原理 Git原理及使用