前言
做技术一定要知其然知其所以然,意思就是:知道它是这样的,更知道它为什么是这样的。我主要通过4块内容来简单介绍 Git 原理是什么样的。这4块内容如下:
- Git 存储目录结构介绍
- Git 是如何存储的
- Git 的对象
- Git引用
当然 Git 原理不仅仅包含这些,想要更深入了解请查看官方教程 https://git-scm.com/book/zh/v2/。
本文内容是我在 GitChat 分享关于Git 的Chat 《Git实用操作手册》 摘抄一个章节,关于《Git实用操作手册》其他内容请访问 https://gitbook.cn/gitchat/activity/5cb46e9dd877c443a183f9d4。
Git 存储目录结构介绍
首先我们先从 Git 存储目录说起,通过 git init 创建一个空的 Git 仓库,具体操作如下图:
![aad31e15b7117bbf4b96c7a32a36017c.png](https://i-blog.csdnimg.cn/blog_migrate/77289cd0699d7a93f0e264067e73a612.jpeg)
创建完成后进入 .git 目录,如下图所示:
![7d47512bbc07618d21fff5097b067e93.png](https://i-blog.csdnimg.cn/blog_migrate/817cc6595eebe4aeca8777dcecde23d5.jpeg)
- hooks 该目录用于配置 客户端执提交操作用于触发服务端的脚本配置,一般用于自动化部署使用
- info 该目录用于配置一些不希望被 Git 管控的文件。
- objects 该目录用于存储所有数据对象内容,这些数据内容类型有 commit tree blob tag
- refs 该目录用于存储 Git 本地以及远程分支的引用,当然还有一种特殊的引用标签引用
- config 该文件包含项目特有的配置选项,并且该配置仅对该 Git 仓库有效
- description 该文件仅供 GitWeb 程序使用
- HEAD 该文件表示当前 Git 仓库处于哪个分支
- index 该文件保存暂存区信息 (空仓库下该文件不会显示一旦执行 git add 操作该文件就会出现)
通过 git config --local 查看 config 文件的变化
我们通过 git config --local 配置仅对于 gitLearn 项目有效,用户名和邮箱配置如下图所示:
![713e0cef0a3e810ed726c2f2099514ea.png](https://i-blog.csdnimg.cn/blog_migrate/dbd4ae6b346004638a0257935e7569ca.jpeg)
查看 config 文件,会发现该文件新增用户信息配置。
Git 是如何存储的
Git 是一个内容寻址文件系统 其核心部分是一个简单的键值对数据库(key-value data store)。 你可以向该数据库插入任意类型的内容,它会返回一个 40 位字符串键,通过该 40 位字符串键可以在任意时刻再次检索(retrieve)该内容。
什么是内容寻址?
每次我们进行提交会通过 SHA-1 算法生成一个长度为 40 个字符的校验和(checksum hash)(也就是我们的 key)然后根据校验和去获取我们文件的内容。这种通过唯一标识的 key(也可以理解为内容的地址)去获取我们的内容的操作就是内容寻址。
Git 的对象
在 Git 中有四种对象分别为:
- blob 是具体的文件对象
- tree 是某个时刻提交目录的内容
- commit 执行一次 commit 就会产生一个 commit 对象
- tag 可以理解成 commit 的别名,一个 tag 对应一个 commit
了解 Git 的对象需要使用如下命令进行查看:
- git cat-file -p 对象 hash 值 查看对象的内容
- git cat-file -t 对象 hash值 查看对象的类型
- git ls-files --stage 查看 index 文件内容
- git hash-object 查看文件的 hash 值
查看 commit tree blob 三个对象演示
我们创建一个 first.txt 文件,并将其提交到暂存区中。
进入 .git 文件夹下会发现新增了一个 index 文件。
![1374941c7cfd9439271244ec6cab1931.png](https://i-blog.csdnimg.cn/blog_migrate/514ca6be3c62f1d18a3388ec8cc7c4fc.jpeg)
我们可以通过 git ls-files --stage 查看 index 文件的内容。
![56d10a2b139294a27e06eea21eeafff7.png](https://i-blog.csdnimg.cn/blog_migrate/84f457f8c4c0fa06ad9d44dc646295aa.jpeg)
进入 objects 目录发现 9c 文件夹名称+文件名称 和 index 文件中的一段字符串内容相同。
![344ffed40ba43bd492d128348c64294d.png](https://i-blog.csdnimg.cn/blog_migrate/8d002a6c628395fd695f860a49c89a88.jpeg)
我们通过 git cat-file -t 9c59e24b8393179a5d712de4f990178df5734d99 我查看该表示对象类型 如下图所示表示该标识对象类型是 blob。
![88b9d60816b3579ab4993455e3a1f725.png](https://i-blog.csdnimg.cn/blog_migrate/e1bc937cb7103e555b25dacd96531e87.jpeg)
执行 git commit -m 将 first.txt 文件提交到本地仓库中。
![4d45788ebd88c98c102bd9f25974dc5e.png](https://i-blog.csdnimg.cn/blog_migrate/c6ef0758b3de61bc06d703cc67137a73.jpeg)
执行 git log 查看我们的提交记录。
![b9681a63f2591117856380d2c077c182.png](https://i-blog.csdnimg.cn/blog_migrate/889f55abc12632e1e2bfec76a2febd50.jpeg)
如下图所示我们通过 git cat-file -p commitId 查看我们提交的内容。如下图所示:我们最新一次提交包含了一个 59b06 开头的 tree 对象。
![ac02b3f95fb2a8dcaa25494bcdd30ef8.png](https://i-blog.csdnimg.cn/blog_migrate/206df75c2cf883fbadf84e8d324d45cb.jpeg)
在 ./git/objects 目录中可以找到我们对应的文件。
我们通过 git cat-file -p tree对象哈希值,查看该 tree 对象的内容 。如下图所示显示就是我们 git add参生的 blob对象。
在通过 git cat-file -p blob对象哈希值,查看我们 blob对象内容,如下图所示 blob对象 内容就是我们 first.txt 文件的内容。
![7dd3d08657ec08e39707282b246eb416.png](https://i-blog.csdnimg.cn/blog_migrate/240c1a125e1fbedb6d09b270ff88a9d9.jpeg)
我们将 first.txt 文件提交到本地仓库 会产生一个 commit 一个 tree 和一个 blob 对象。
tag 对象原理演示
首先我们通过 git log 查看最新的提交是 add a.txt 注释的 commit 如下图所示。
![29a02d9f19fd326a7f753e2199df1d5d.png](https://i-blog.csdnimg.cn/blog_migrate/e8c7daa00f0408d07424f875f4aa233c.jpeg)
通过 git tag -a v1.1 -m ‘add a.txt tag’ 为该 commit 创建一个附注标签。
![3c9e11f418c0c9e1f5ebf6bdb0bb83c1.png](https://i-blog.csdnimg.cn/blog_migrate/1f2057aea123590f3ec1e57efe8768b4.jpeg)
在我们的 .git/refs/tags/ 目录下会新增 v1.1 文件。
v1.1 内容如下:
![40abaa7980b77f3ddb5bd9b67c0e0197.png](https://i-blog.csdnimg.cn/blog_migrate/e6124aeda0206fb0e8359dcc994bb718.jpeg)
看到这个你肯定想到了这是一个 Git 对象,我们通过 git cat-file -t 查看这个哈希值对象类型。如下图所示它是一个 tag。
![78ae8d77dfa6b73cb3fbbeea459b636f.png](https://i-blog.csdnimg.cn/blog_migrate/d4733f7c7cfd3867d7199787a106c78e.jpeg)
然后通过 git cat-file -p 查看它的内容,如下图所示,该tag包含了commit 对象和 标注的信息。
![2be5c48726d6282b5d544dfba65cd668.png](https://i-blog.csdnimg.cn/blog_migrate/3dcb881872e5c71181cb29551ce42c1f.jpeg)
Git 引用
什么是 Git 的引用?
这里的引用我们可以理解成一个书签,你在阅读一本书的时候可以为你读到的部分打上标签。然后你可以通过这个书签快速找到你之前阅读的位置。在 Git 中我们 创建新的 Commit 或者创建分支都会进行一次打标签的操作。我们各个不同 commit 之间的切换或分支的切换其实就是标签的切换。
Git 引用的三种类型
在 Git 中有三种类型的的引用 分别是:
- HEAD 引用:用来为我们的本地仓库打上标签使用。
- Tag 引用:用来为我们的 Git 仓库 tag 标签使用。
- 远程引用:用来为我们的远程仓库打标签使用。
HEAD 引用
HEAD 引用原理 我的个人理解是一个 Head 头指针+当前分支 指向当前最新提交的 commit 对象。我们也可以通过 git reset --hard 来切换我们commit 记录,切换后的 commit 以前的 commit 记录就没有了,不过我们可以通过 git reflog 查询操作记录将以前 commit 找回。
为了更好的理解 HEAD,引用建议大家访问 http://onlywei.github.io/explain-git-with-d3/#zen 执行 Git 相关的操作来理解什么是 HEAD引用。
如下图所示,我们的 Head头指针分支 指向 master分支 同时指向最新的 commit e137e。
![1e6b5248b607ae306a0a9525044053c2.png](https://i-blog.csdnimg.cn/blog_migrate/a2d39f07c18de242639df72a5f1e67f6.jpeg)
我们执行一次 commit Head头指针分支 + master分支 就指向的是最新的提交 896ee 上。
![ce7cb1ac3d6c54515609dd321bd042f7.png](https://i-blog.csdnimg.cn/blog_migrate/da44250f71097713a850f91c6c460c95.jpeg)
通过 git branch b1 创建新的分支名称为b1,如下图所示,我们发现 Head 头指针还是指向 master 。
![ad9cb828abe670b142e49e58c1ba321c.png](https://i-blog.csdnimg.cn/blog_migrate/e0e0c3a5c79863ca1fed239e803c637c.jpeg)
当执行 git checkout b1 的时候,如下图所示,此时 Head 指针指向分支 b1。
![25cd2e40f9e7b01f663d0e756f91ae1f.png](https://i-blog.csdnimg.cn/blog_migrate/69aa402f15c952deec4eafc3b1a0608f.jpeg)
我们在分支 b1 上执行 commit Head 指针指向了在该分支下的最新提交 3c7acb。
![4c5e25c37f9785c2c0d52b8cf99b4f84.png](https://i-blog.csdnimg.cn/blog_migrate/013ede477d60f0b2c3c151b2276cdea9.jpeg)
当执行 git checkout master 是 Head 指针指向了 master 上最新的 commit 896ee。
![10190725d184ffece816558f028c8fa7.png](https://i-blog.csdnimg.cn/blog_migrate/147e3f5784bec0e1a99743f00791cfb8.jpeg)
当执行 git merge b1 的时候,会将 b1分支最新 commit 合并到 master,我们 Head指针 + master 同时指向最新的 commit 3c7ba 上。
![c8e7caf6e8d095612ac39fe8c6301020.png](https://i-blog.csdnimg.cn/blog_migrate/9ea69d81a78ca1d6e757e4d8b3a643d7.jpeg)
通过上面的动态演示,这回对 Head 应用如何切换 commit 切换分支有了一定的理解。
这里非常建议你自己通过访问
http://onlywei.github.io/explain-git-with-d3/#zen
来亲自体验一下。
接下来我们来通过查看 .git 目录来介绍 Head 指针是如何实现的。进入 .git 目录你会发现有一个 HEAD 文件,它的内容是 ref: refs/heads/master,如下图所示。
![a87b5fd9fa689779393bee374355c569.png](https://i-blog.csdnimg.cn/blog_migrate/916d79bb043c9bf5f819ac474b432eda.jpeg)
![1c4cb52902f720c5ccd98fcff1aef053.png](https://i-blog.csdnimg.cn/blog_migrate/166467428a6a8396f47fd5d8f67fe19d.jpeg)
refs/heads/master 是具体文件路径,我们查 refs/heads 目录下的 master 文件内容,如下图所示,master 文件内容就是我们最新的 commit。
![8bc780d35651a209a462426363ff1885.png](https://i-blog.csdnimg.cn/blog_migrate/aa5f8e563680106d069e8b9a3f4c19fb.jpeg)
![2bfb966f3f4420842ef893533997e68c.png](https://i-blog.csdnimg.cn/blog_migrate/623e8e17437824930eadec83559ac11e.jpeg)
![423a09836b81f2dea553a2d13d1f6998.png](https://i-blog.csdnimg.cn/blog_migrate/f7c1d2fed77bbe40c8e366a122508b44.jpeg)
我们执行 git chekcout demoBranch3。
![65f071ffea77dc0f7d4eeef9cefe3016.png](https://i-blog.csdnimg.cn/blog_migrate/2d16716ad5e26ad351e209d6cbca9232.jpeg)
![17e7bf2845179ac8c709fd52728720b7.png](https://i-blog.csdnimg.cn/blog_migrate/1dd11ac29fe0b7145defad931f6407ae.jpeg)
此时我们的 HEAD 文件内容变成了 ref: refs/heads/demoBranch3。
查看 refs/heads/ 路径下的 demoBranch3 文件,如下图所示 demoBranch3 文件记录是demoBranch3 分支下最新的 commit。
![000a0c8d89ebbb4190712c3009156bb0.png](https://i-blog.csdnimg.cn/blog_migrate/6aec8920bdd0971c3837df625eb22f58.jpeg)
![1b93d2de495f21da0bfd79345062a9d0.png](https://i-blog.csdnimg.cn/blog_migrate/3d5a7a6bd8159d7d40268004b6daff35.jpeg)
标签引用
标签引用是一个特殊的引用,它不像 HEAD 引用可以通过 git checkout 来更改引用的指针指向。标签引用不会移动它永远会指向一个 Commit 对象。
标签引用的演示请参看 Tag 对象原理演示部分。
远程引用
远程引用是只读的我们不能通过切换到远程引用执行 commit 将提交更新到远程仓库中
我们在本地分支执行 push 操作,Git 都会帮我们记录 push 到远程分支的最新 comit,当执行push 的时候,如果发现远程分支最新 commit 和我们本地仓库记录最后一次 push 的 commit 不同会报 Note about fast-forwards 异常,如下图所示:
![2a1d7caa61e9702c6a8a52f340c9a369.png](https://i-blog.csdnimg.cn/blog_migrate/bf06735d43f002804ca688f7112e52ab.jpeg)
出现 Note about fast-forwards 我们需要执行 git pull 将远程最新的 commit 拉取下来 然后再执行 git push 操作 或者 直接执行 git puhs -f。这里强调一下不建议执行 git puhs -f 操作因为会强制将本地的历史记录覆盖到远程仓库的历史记录。
通过查看 .git 目录理解远程引用
在 .git/refs/remotes/origin 文件夹中 HEAD 文件内容表示内容表示远程分支处于哪个分支。
![6b1b9e23f8891b8d338563f627de680a.png](https://i-blog.csdnimg.cn/blog_migrate/119f8f7fbf74a53951ca7fb1345cd885.jpeg)
在 .git/refs/remotes/origin 文件夹中 master 文件内容,表示最后一次 push 到远程仓库的commit,如下图所示:
![f2bb1907cc00fecece0c6ac0a3337e79.png](https://i-blog.csdnimg.cn/blog_migrate/a876e4eb73ef128368988188a0258e02.jpeg)
![a07fdb57ad84dc964862ff4fc1b00971.png](https://i-blog.csdnimg.cn/blog_migrate/de30784953b8406d41a7b8d440b87a78.jpeg)
在 .git/refs/remotes/origin 文件夹中 demoBranch1 文件表示,最后一次 push 到 demoBranch1 分支提交的 commitId。
![08586d56ba5b1a280f3e81a2d5885684.png](https://i-blog.csdnimg.cn/blog_migrate/1e05e7818205730e8a771e840bff536b.jpeg)
原文链接:https://blog.csdn.net/ljk126wy/article/details/101064186