一文了解git底层原理
一、git相关区域
工作区(Working Directory): 工作区是平时编写文本文件的地方
暂存区(Stage/Index): 暂存区是提交文本文件到本地仓库的来源地,只有把工作区的文件添加至暂存区,才可以被提交至本地仓库。 (git add)
本地仓库(Repository): 本地仓库是保存每次文件更新的记录,包括提交人,提交时间,提交的内容等详细信息,方便追溯历史版本。(git commit)
远程仓库(Remote Repository):远程仓库算是本地仓库的一个副本,主要是方便合作伙伴之间的仓库文件同步。
二、git各类对象
blob:
blob对象只跟文本文件的内容有关,和文本文件的名称及目录无关,只要是相同的文本文件,会指向同一个blob。
tree:
tree对象记录文本文件内容和名称、目录等信息,每次提交都会生成一个顶层tree对象,它可以指向其引用的tree或blob。
commit:
commit对象记录本次提交的所有信息,包括提交人、提交时间,本次提交包含的tree及blob。
tag:
标签引用,它指向某一个commit。
三、验证git中三大对象commit、tree和blog之间的关系
1. 本地初始化一个git仓库
$ git init gittest
执行后 ls -a 可查看到.git目录。
2. 在.git同级目录下创建doc/readme.txt文件,内容为 hello world
执行 ls -a 查看到文件已新增
$ ls -a
.git doc/readme.txt
3. 在还没有进行add的前提下,使用命令 git status 查看当前仓库的状态,可以看到创建的文件夹和文件还没有添加到暂存区,所以接着使用命令:find .git/objects -type f 看不到有什么结果。
$ git status On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) doc/
$ find .git/objects -type f
4. git add 后,使用上述命令结果如下:
$ git status On branch master No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: doc/readme.txt
$ find .git/objects -type f .git/objects/3b/18e512dba79e4c8300dd
$ git cat-file -t 3b18e512dba79e4c8300
blob
$ git cat-file -p 3b18e512dba79e4c8300
hello world
使用 git cat-file -t ID号 查看object的类型,由上可知该object为blog,说明新的东西加入到暂存区,git会主动把暂存区的东西创建为blog。使用命令:git cat-file -p ID号 查看该blog的内容,可以得到内容就是readme中的内容。
5. 执行如下命令commit该新增文件
$ git commit -m "add readme"
再通过 find .git/objects -type f 可查看到四个object对象
$ find .git/objects -type f .git/objects/3b/18e512dba79e4c8300dd .git/objects/e7/a75e980c7adc0d5ab1c4 .git/objects/2e/9e6cd9fef232a6f0ecab .git/objects/7a/802cad5138b242c5ee6b $ git cat-file -t 3b18e512dba79e4c8300 blob $ git cat-file -p 3b18e512dba79e4c8300 hello world $ git cat-file -t e7a75e980c7adc0d5ab1 tree $ git cat-file -p e7a75e980c7adc0d5ab1 100644 blob 3b18e512dba79e4c8300dd readme.txt $ git cat-file -t 2e9e6cd9fef232a6f0ec tree $ git cat-file -p 2e9e6cd9fef232a6f0ec 040000 tree e7a75e980c7adc0d5ab1 doc $ git cat-file -t 7a802cad5138b242c5ee commit $ git cat-file -p 7a802cad5138b242c5ee tree 2e9e6cd9fef232a6f0ecab author test <test@gmail.com> 1591054691 +0800 committer test <test@gmail.com> 1591054691 +0800
由上可以得出,一个commit里面含有一个tree(这次commit时期包含的文件内容)。一个tree下面确实包含有tree和blog。
进入到某一具体的blog进行查看,验证是否一个blog代表一个文件内容(进入到具体的blog下,有的blog能直接解析打开,如html,txt等,有的不能,如图片等)。如下可以直接看到该blog所对应的readme.txt的文件内容。
6. 在.git 同级目录下新建second.txt文件,commit后得到3个新object如下:
$ git commit -m "second commit" [master b0da7df] second commit 1 file changed, 1 insertion(+) create mode 100644 second.txt $ find .git/objects -type f .git/objects/0c/e52f0e38439ed9833307 .git/objects/3b/18e512dba79e4c8300dd .git/objects/b0/da7dfb5c1f09e5c0b618 .git/objects/e7/a75e980c7adc0d5ab1c4 .git/objects/e0/19be006cf33489e2d017 .git/objects/2e/9e6cd9fef232a6f0ecab .git/objects/7a/802cad5138b242c5ee6b $ git cat-file -t 0ce52f0e38439ed983330d7 tree $ git cat-file -p 0ce52f0e38439ed983330d7 040000 tree e7a75e980c7adc0d5ab1 doc 100644 blob e019be006cf33489e2d0 second.txt $ git cat-file -t b0da7dfb5c1f09e5c0b618 commit $ git cat-file -p b0da7dfb5c1f09e5c0b618 tree 0ce52f0e38439ed98333077 parent 7a802cad5138b242c5ee author test <test@gmail.com> 1591016659 +0800 committer test <test@gmail.com> 1591016659 +0800 second commit $ git cat-file -t e019be006cf33489e2d0 blob $ git cat-file -p e019be006cf33489e2d0 second
当执行 git commit -m "second commit" 的时候就会在.git/objects/下新生成了1个tree对象和一个commit对象的文件夹(前两位为文件夹名称 -- 后38位为文本内容的哈希值)。对应上述的0ce52f0e38439ed98333077(tree对象), b0da7dfb5c1f09e5c0b61835e0ecc87877d0e853(commit对象)。
只生成一个与commit对象一一对应的顶层tree对象。由于本次提交doc目录下的readme.txt内容没有变化,所以上图的0ce52f0e38439ed98333077(tree对象)还会指向e7a75e980c7adc0d5ab1(tree对象)。这个时候HEAD游标指向的是当前master分支的second commit(HEAD索引向前移动),second commit 会指向上一次的提交即 parent指向first commit。
整个原理图如下:
注意: blob对象只对文件的内容有关,和文件名称无关,如果不同的文件名称,内容相同只会有一个blob对象,生成的新tree对象会指向该blob对象。例如third commit和four commit的内容一样,所以不会生成新的blob对象,新的tree对象只会指向同一个blob。