Git 内部工作原理
Git 本质上是一个内容寻址文件系统,最初是一套面向版本控制系统的工具集,而不是一个完整的用户友好的版本控制系统。因此它还包含了一些用于完成底层工作的命令,这些命令被称为“底层命令”,而那些更友好的命令则被当作“高层命令”来使用。
![](https://i-blog.csdnimg.cn/blog_migrate/27fbf4ef2d1d9beb865c76bb72751a24.jpeg)
Git 目录
当执行 git init 操作时会生成一个 .git 目录
![](https://i-blog.csdnimg.cn/blog_migrate/155b0d176e2dc873593a1ad265bf16f7.png)
这是一个全新的 git init 目录,这个目录后续可能还会添加其他文件。其中description文件仅供 GitWeb 程序使用,我们无需关心,info 包含全局性排除文件,用来放置那些不希望被记录在 .ignore中的忽略模式。hooks 放置钩子脚本。
config
Git 自带一个 git config工具来控制设置 git 外观和行为的配置变量,这些变量存储在三个不同的位置。
1. ./etc/git config 包含系统上每个用户及其仓库的通用配置。
git config --system 命令会从此位置读取配置变量。
2. ~/.gitconfig 或 ~/.config/git/config:只针对当前用户
git config --global 命令会从此位置读取配置变量
3. 当前使用仓库的 .git 目录中的 config 文件:只针对该仓库,一些分支的信息和远端仓库的信息以及 fetch,push操作的信息都有保存。
![](https://i-blog.csdnimg.cn/blog_migrate/166f09f055f13f814e4f6e565d151a56.png)
Objects(对象):保存所有数据内容
刚刚我们提到,git 是一个内容寻址文件系统,那么什么是内容寻址文件系统呢?其实 git 的核心部分是一个键值对数据库,它的值可以存储任何数据类型的内容,这个值对应返回一个键,通过这个键可以检索对应的内容。
接下来我们通过实验,来看看 git 在 add 和 commit 的过程中都做了什么
1.创建一个全新的 git 仓库
2.在仓库中添加一个文件,并执行 add 操作
此时我们可以在 objects 目录下看到多了一个文件夹
文件夹里面的东西是很么呢?
![](https://i-blog.csdnimg.cn/blog_migrate/ea86c56c18903f435e6f80054c02fc55.png)
其实这就是执行 git add 命令后生成的一个对象,git将对象的 hash 值的前两位 8d 作为文件夹名,其余38位作为文件名保存。
我们可以通过 git cat-file 命令可以从对象中取出数据:
git cat-file -p 8d0e41234f24b6da002d962a26c2495ea16a425f
![](https://i-blog.csdnimg.cn/blog_migrate/30109a82150ec89ebc1e658e0fec3d35.png)
控制台输出了 hello git,而文件 test.txt 中的内容正是 "hello git"
这个对象就是 git objects 中的数据对象 (blob object) ,我们可以用:
git cat-file -t 8d0e41234f24b6da002d962a26c2495ea16a425f 命令查看对象类型
![](https://i-blog.csdnimg.cn/blog_migrate/75d5596b9342bff314a6ab5eaefaf11d.png)
接下来我们继续 git commit 操作,
![](https://i-blog.csdnimg.cn/blog_migrate/cfaf96c7265ececf1241c01716f25d6e.png)
可以看到提交后又生成了两个对象。
![](https://i-blog.csdnimg.cn/blog_migrate/f80aa06df514d62777fe7ab9c6af78d3.png)
我们分别来看一下这两个对象的内容,
![](https://i-blog.csdnimg.cn/blog_migrate/e62ba480008b24fdd9052f5eb14f9dbb.png)
![](https://i-blog.csdnimg.cn/blog_migrate/20d99dbfdda070b260a3616c6073a43c.png)
里面保存的是 blob 对象 hash 值,文件名称。
![](https://i-blog.csdnimg.cn/blog_migrate/e8b28406d9d7107adccb5994d1088d4a.png)
查看它的类型可以看到控制台返回了 tree,这就是 git 对象中的树对象 (tree object)
如果我们多提交几次,大致就可以看到里面的内容其实是一个树形结构,
![](https://i-blog.csdnimg.cn/blog_migrate/4eecf6905f3154307eb26df8b7becaa2.jpeg)
再来看看另一个对象,
![](https://i-blog.csdnimg.cn/blog_migrate/4c2f3b6a683c601148bf9352af4d1866.png)
![](https://i-blog.csdnimg.cn/blog_migrate/13cf91a995b001a2234b6fff5458dc0f.png)
这个对象中的格式也很简单,它首先指定顶层树对象,代表当前项目快照,然后是作者信息和提交者信息,留一行空,接着是提交描述信息。
![](https://i-blog.csdnimg.cn/blog_migrate/a3373c2c1c8911effc527b35883becaf.png)
查看他的类型是 commit,这个对象其实就是提交对象 (commit object)。
如果我们多进行几次提交,将看到 .git/logs/HEAD 文件保存这样的内容
这个 HEAD 文件保存的是当前分支下的快照,里面记录了当前分支中的提交记录,我们可以发现每一行代表一个提交记录,后一个 commit 持有前一个 commit 的 hash 值,以此形成了一个链表结构。
以上我们所看到的就是 git 执行 add 和 commit 的实质工作,将被改写的文件保存为数据对象,更新暂存区,记录树对象,最后创建一个指明了顶层树对象和父提交的提交对象。
Refs(引用):保存指向分支的提交对象的指针
Refs 相当于最后一个提交的 commit hash 的一个别名,因为我们可以执行类似 git log 8d0e412来查看完整的提交历史,但是遍历那段历史从而找到相关对象,我们需要记住最后一个提交的 hash 值,git将这个 hash 值用文件保存起来,并给文件取一个简单的名字,用这个名字指针指向原始的 hash 值。
git 分支的本质其实基本上就是一个指向某一系列提交之首的指针或引用。
我们通过 git log --pretty=oneline master 命令查看到 master 分支下的提交记录
现在你若想在第二个提交上创建一个分支可这么做:
当我们用类似于 git branch <branchname> 的命令时,git 底层执行的是 update-ref 命令,取得当前所在分支最新提交的 hash 值,然后将其加入到我们创建的新引用中。这也是 git 创建分支比 svn 快得多的原因,git 创建一个分支只需要创建一个引用的时间。
refs 目录下包含 heads,tags,remotes 目录;
heads 目录:定义所有的本地引用;
tags 目录:标签引用;
remotes 目录:远程引用;
远程分支引用和本地分支引用最大的区别在于远程分支引用是只读的,虽然可以用 git checkout 到某个远程引用,但是 head 指针并不会指到远程引用上去,git 只是将这些远程引用作为指向远程仓库中最后一次提交的书签来管理。
Index:保存暂存区信息
HEAD:指向当前被检出的分支
我们发现这个引用和别的引用不一样,实际上他是指向其他引用的一个指针。当我们执行 git checkout <branchName> 命令时,实际上是将 HEAD 文件中的引用改为了需要检出分支的引用。
![](https://i-blog.csdnimg.cn/blog_migrate/6542cebc101257521b17d99271d19966.png)
总结
以上就是我所学习的 Git 进阶全部内容,因为 Git 还有很多其他操作,此处无法穷举。如果真的要完全理解 Git 的原理还是要在实际工作中多操作练习。
Git 内部工作原理
Git 本质上是一个内容寻址文件系统,最初是一套面向版本控制系统的工具集,而不是一个完整的用户友好的版本控制系统。因此它还包含了一些用于完成底层工作的命令,这些命令被称为“底层命令”,而那些更友好的命令则被当作“高层命令”来使用。
![](https://i-blog.csdnimg.cn/blog_migrate/27fbf4ef2d1d9beb865c76bb72751a24.jpeg)
Git 目录
当执行 git init 操作时会生成一个 .git 目录
![](https://i-blog.csdnimg.cn/blog_migrate/155b0d176e2dc873593a1ad265bf16f7.png)
这是一个全新的 git init 目录,这个目录后续可能还会添加其他文件。其中description文件仅供 GitWeb 程序使用,我们无需关心,info 包含全局性排除文件,用来放置那些不希望被记录在 .ignore中的忽略模式。hooks 放置钩子脚本。
config
Git 自带一个 git config工具来控制设置 git 外观和行为的配置变量,这些变量存储在三个不同的位置。
1. ./etc/git config 包含系统上每个用户及其仓库的通用配置。
git config --system 命令会从此位置读取配置变量。
2. ~/.gitconfig 或 ~/.config/git/config:只针对当前用户
git config --global 命令会从此位置读取配置变量
3. 当前使用仓库的 .git 目录中的 config 文件:只针对该仓库,一些分支的信息和远端仓库的信息以及 fetch,push操作的信息都有保存。
![](https://i-blog.csdnimg.cn/blog_migrate/166f09f055f13f814e4f6e565d151a56.png)
Objects(对象):保存所有数据内容
刚刚我们提到,git 是一个内容寻址文件系统,那么什么是内容寻址文件系统呢?其实 git 的核心部分是一个键值对数据库,它的值可以存储任何数据类型的内容,这个值对应返回一个键,通过这个键可以检索对应的内容。
接下来我们通过实验,来看看 git 在 add 和 commit 的过程中都做了什么
1.创建一个全新的 git 仓库
2.在仓库中添加一个文件,并执行 add 操作
此时我们可以在 objects 目录下看到多了一个文件夹
文件夹里面的东西是很么呢?
![](https://i-blog.csdnimg.cn/blog_migrate/ea86c56c18903f435e6f80054c02fc55.png)
其实这就是执行 git add 命令后生成的一个对象,git将对象的 hash 值的前两位 8d 作为文件夹名,其余38位作为文件名保存。
我们可以通过 git cat-file 命令可以从对象中取出数据:
git cat-file -p 8d0e41234f24b6da002d962a26c2495ea16a425f
![](https://i-blog.csdnimg.cn/blog_migrate/30109a82150ec89ebc1e658e0fec3d35.png)
控制台输出了 hello git,而文件 test.txt 中的内容正是 "hello git"
这个对象就是 git objects 中的数据对象 (blob object) ,我们可以用:
git cat-file -t 8d0e41234f24b6da002d962a26c2495ea16a425f 命令查看对象类型
![](https://i-blog.csdnimg.cn/blog_migrate/75d5596b9342bff314a6ab5eaefaf11d.png)
接下来我们继续 git commit 操作,
![](https://i-blog.csdnimg.cn/blog_migrate/cfaf96c7265ececf1241c01716f25d6e.png)
可以看到提交后又生成了两个对象。
![](https://i-blog.csdnimg.cn/blog_migrate/f80aa06df514d62777fe7ab9c6af78d3.png)
我们分别来看一下这两个对象的内容,
![](https://i-blog.csdnimg.cn/blog_migrate/e62ba480008b24fdd9052f5eb14f9dbb.png)
![](https://i-blog.csdnimg.cn/blog_migrate/20d99dbfdda070b260a3616c6073a43c.png)
里面保存的是 blob 对象 hash 值,文件名称。
![](https://i-blog.csdnimg.cn/blog_migrate/e8b28406d9d7107adccb5994d1088d4a.png)
查看它的类型可以看到控制台返回了 tree,这就是 git 对象中的树对象 (tree object)
如果我们多提交几次,大致就可以看到里面的内容其实是一个树形结构,
![](https://i-blog.csdnimg.cn/blog_migrate/4eecf6905f3154307eb26df8b7becaa2.jpeg)
再来看看另一个对象,
![](https://i-blog.csdnimg.cn/blog_migrate/4c2f3b6a683c601148bf9352af4d1866.png)
![](https://i-blog.csdnimg.cn/blog_migrate/13cf91a995b001a2234b6fff5458dc0f.png)
这个对象中的格式也很简单,它首先指定顶层树对象,代表当前项目快照,然后是作者信息和提交者信息,留一行空,接着是提交描述信息。
![](https://i-blog.csdnimg.cn/blog_migrate/a3373c2c1c8911effc527b35883becaf.png)
查看他的类型是 commit,这个对象其实就是提交对象 (commit object)。
如果我们多进行几次提交,将看到 .git/logs/HEAD 文件保存这样的内容
这个 HEAD 文件保存的是当前分支下的快照,里面记录了当前分支中的提交记录,我们可以发现每一行代表一个提交记录,后一个 commit 持有前一个 commit 的 hash 值,以此形成了一个链表结构。
以上我们所看到的就是 git 执行 add 和 commit 的实质工作,将被改写的文件保存为数据对象,更新暂存区,记录树对象,最后创建一个指明了顶层树对象和父提交的提交对象。
Refs(引用):保存指向分支的提交对象的指针
Refs 相当于最后一个提交的 commit hash 的一个别名,因为我们可以执行类似 git log 8d0e412来查看完整的提交历史,但是遍历那段历史从而找到相关对象,我们需要记住最后一个提交的 hash 值,git将这个 hash 值用文件保存起来,并给文件取一个简单的名字,用这个名字指针指向原始的 hash 值。
git 分支的本质其实基本上就是一个指向某一系列提交之首的指针或引用。
我们通过 git log --pretty=oneline master 命令查看到 master 分支下的提交记录
现在你若想在第二个提交上创建一个分支可这么做:
当我们用类似于 git branch <branchname> 的命令时,git 底层执行的是 update-ref 命令,取得当前所在分支最新提交的 hash 值,然后将其加入到我们创建的新引用中。这也是 git 创建分支比 svn 快得多的原因,git 创建一个分支只需要创建一个引用的时间。
refs 目录下包含 heads,tags,remotes 目录;
heads 目录:定义所有的本地引用;
tags 目录:标签引用;
remotes 目录:远程引用;
远程分支引用和本地分支引用最大的区别在于远程分支引用是只读的,虽然可以用 git checkout 到某个远程引用,但是 head 指针并不会指到远程引用上去,git 只是将这些远程引用作为指向远程仓库中最后一次提交的书签来管理。
Index:保存暂存区信息
HEAD:指向当前被检出的分支
我们发现这个引用和别的引用不一样,实际上他是指向其他引用的一个指针。当我们执行 git checkout <branchName> 命令时,实际上是将 HEAD 文件中的引用改为了需要检出分支的引用。
![](https://i-blog.csdnimg.cn/blog_migrate/6542cebc101257521b17d99271d19966.png)
总结
以上就是我所学习的 Git 进阶全部内容,因为 Git 还有很多其他操作,此处无法穷举。如果真的要完全理解 Git 的原理还是要在实际工作中多操作练习。