做技术一定要知其然知其所以然,意思就是:知道它是这样的,更知道它为什么是这样的。我主要通过4块内容来简单介绍 Git 原理是什么样的。这4块内容如下:
当然 Git 原理不仅仅包含这些,想要更深入了解请查看官方教程 https://git-scm.com/book/zh/v2/。
本文内容是我在 GitChat 分享关于Git 的Chat 《Git实用操作手册》 摘抄一个章节,关于《Git实用操作手册》其他内容请访问 https://gitbook.cn/gitchat/activity/5cb46e9dd877c443a183f9d4。
首先我们先从 Git 存储目录说起,通过 git init 创建一个空的 Git 仓库,具体操作如下图:
创建完成后进入 .git 目录,如下图所示:
通过 git config --local 查看 config 文件的变化
我们通过 git config --local 配置仅对于 gitLearn 项目有效,用户名和邮箱配置如下图所示:
查看 config 文件,会发现该文件新增用户信息配置。
Git 是一个内容寻址文件系统
其核心部分是一个简单的键值对数据库(key-value data store)。 你可以向该数据库插入任意类型的内容,它会返回一个 40 位字符串键,通过该 40 位字符串键可以在任意时刻再次检索(retrieve)该内容。
每次我们进行提交会通过 SHA-1 算法生成一个长度为 40 个字符的校验和(checksum hash)
(也就是我们的 key)然后根据校验和
去获取我们文件的内容。这种通过唯一标识的 key(也可以理解为内容的地址)去获取我们的内容的操作就是内容寻址
。
在 Git 中有四种对象分别为:
了解 Git 的对象需要使用如下命令进行查看:
我们创建一个 first.txt 文件,并将其提交到暂存区中。
进入 .git 文件夹下会发现新增了一个 index 文件。
我们可以通过 git ls-files --stage 查看 index 文件的内容。
进入 objects 目录发现 9c 文件夹名称+文件名称 和 index 文件中的一段字符串内容相同。
我们通过 git cat-file -t 9c59e24b8393179a5d712de4f990178df5734d99 我查看该表示对象类型 如下图所示表示该标识对象类型是 blob。
执行 git commit -m 将 first.txt 文件提交到本地仓库中。
执行 git log 查看我们的提交记录。
如下图所示我们通过 git cat-file -p commitId 查看我们提交的内容。如下图所示:我们最新一次提交包含了一个 59b06 开头的 tree 对象。
在 ./git/objects 目录中可以找到我们对应的文件。
我们通过 git cat-file -p tree对象哈希值,查看该 tree 对象的内容 。如下图所示显示就是我们 git add参生的 blob对象。
在通过 git cat-file -p blob对象哈希值,查看我们 blob对象内容,如下图所示 blob对象 内容就是我们 first.txt 文件的内容。
我们将 first.txt 文件提交到本地仓库 会产生一个 commit 一个 tree 和一个 blob 对象。
首先我们通过 git log 查看最新的提交是 add a.txt 注释的 commit 如下图所示。
通过 git tag -a v1.1 -m ‘add a.txt tag’ 为该 commit 创建一个附注标签。
在我们的 .git/refs/tags/ 目录下会新增 v1.1 文件。
看到这个你肯定想到了这是一个 Git 对象,我们通过 git cat-file -t 查看这个哈希值对象类型。如下图所示它是一个 tag。
然后通过 git cat-file -p 查看它的内容,如下图所示,该tag包含了commit 对象和 标注的信息。
这里的引用我们可以理解成一个书签,你在阅读一本书的时候可以为你读到的部分打上标签。然后你可以通过这个书签快速找到你之前阅读的位置。在 Git 中我们 创建新的 Commit 或者创建分支都会进行一次打标签的操作。我们各个不同 commit 之间的切换或分支的切换其实就是标签的切换。
在 Git 中有三种类型的的引用 分别是:
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。
我们执行一次 commit Head头指针分支 + master分支 就指向的是最新的提交 896ee 上。
通过 git branch b1 创建新的分支名称为b1,如下图所示,我们发现 Head 头指针还是指向 master 。
当执行 git checkout b1 的时候,如下图所示,此时 Head 指针指向分支 b1。
我们在分支 b1 上执行 commit Head 指针指向了在该分支下的最新提交 3c7acb。
当执行 git checkout master 是 Head 指针指向了 master 上最新的 commit 896ee。
当执行 git merge b1 的时候,会将 b1分支最新 commit 合并到 master,我们 Head指针 + master 同时指向最新的 commit 3c7ba 上。
通过上面的动态演示,这回对 Head 应用如何切换 commit 切换分支有了一定的理解。
这里非常建议你自己通过访问
http://onlywei.github.io/explain-git-with-d3/#zen
来亲自体验一下。
接下来我们来通过查看 .git 目录来介绍 Head 指针是如何实现的。进入 .git 目录你会发现有一个 HEAD 文件,它的内容是 ref: refs/heads/master,如下图所示。
refs/heads/master 是具体文件路径,我们查 refs/heads 目录下的 master 文件内容,如下图所示,master 文件内容就是我们最新的 commit。
我们执行 git chekcout demoBranch3。
此时我们的 HEAD 文件内容变成了 ref: refs/heads/demoBranch3。
查看 refs/heads/ 路径下的 demoBranch3 文件,如下图所示 demoBranch3 文件记录是demoBranch3 分支下最新的 commit。
标签引用是一个特殊的引用,它不像 HEAD 引用可以通过 git checkout 来更改引用的指针指向。标签引用不会移动它永远会指向一个 Commit 对象。
标签引用的演示请参看 Tag 对象原理演示部分。
远程引用是只读的我们不能通过切换到远程引用执行 commit 将提交更新到远程仓库中
我们在本地分支执行 push 操作,Git 都会帮我们记录 push 到远程分支的最新 comit,当执行push 的时候,如果发现远程分支最新 commit 和我们本地仓库记录最后一次 push 的 commit 不同会报 Note about fast-forwards 异常,如下图所示:
出现 Note about fast-forwards 我们需要执行 git pull 将远程最新的 commit 拉取下来 然后再执行 git push 操作 或者 直接执行 git puhs -f。这里强调一下不建议执行 git puhs -f 操作因为会强制将本地的历史记录覆盖到远程仓库的历史记录。
在 .git/refs/remotes/origin 文件夹中 HEAD 文件内容表示内容表示远程分支处于哪个分支。
在 .git/refs/remotes/origin 文件夹中 master 文件内容,表示最后一次 push 到远程仓库的commit,如下图所示:
在 .git/refs/remotes/origin 文件夹中 demoBranch1 文件表示,最后一次 push 到 demoBranch1 分支提交的 commitId。
Git 官方教程