git原理探索
概念
GIT是什么?简单来说它就是管理和维护代码的软件,它是由linux之父Linus大神花了两个星期开发的,最初是为了给linux内核开发社区提供源码管理,其实在GIT之前linux开源社区的代码管理都是通过bitkeeper版本控制系统来管理的(bitkeeper是由BitMover公司开发,它是一家商业公司),直到2005年,BitMover公司停止了跟linux开源社区的合作关系,也就是因为这个原因,git诞生了。
GITHUB、GITLAB、GITEE是什么?个人理解啊,首先它们是一个托管平台(一个共享仓库),其次它们也是一个跟我们本地一样的git版本库,这个怎么理解?Linus设计初衷就是要做一个分布式版本控制系统,并且去中心化,也就是每个安装GIT的电脑都是一个完整的版本库,可以相互推送。那么这样就会存在一个问题,毕竟我们的PC电脑不是服务器,所以GITHUB、GITEE、GITLAB诞生了,当然这些托管平台针对GIT管理只能说是它们一小部分功能,它们主要还是以论坛社交的方式在服务。
GIT工作流程,GIT工作流程三大核心,工作区、暂存区(索引区)、版本库,看图。
上图是我自己画的一张简图,大概反映了GIT工作流程以及涉及到的4种文件状态,有两个地方需要注意一下,图里面的渐变箭头和最下面那个箭头,1. 渐变箭头表示该文件已经添加到了暂存区,但是又被修改了,导致了工作区和暂存区状态不一致,2. 最下面那个箭头表示,文件从unmodified状态又回到了untracked状态,一般情况下不会有这两种状态的变更,极端情况下,比如撤销回退操作,这些操作后面都会说,概念就介绍到这吧。
准备工作
我这边准备的GIT环境比较简单,三个本地仓库文件夹,也就是三个本地文件夹,一个模拟远程仓库,两个模拟本地仓库,看图。
server模拟共享仓库,并且初始化一个Test.Service的仓库,user01、user02模拟两个pc客户端,通过git clone 命令从server共享库里面克隆Test.Service服务,就是这么简单干脆。
GIT目录结构
所有准备工作已完成,仓库也已完成初始化操作,下面通过tree命令看看GIT目录结构及作用。
$ tree .git/ .git/ |-- COMMIT_EDITMSG |-- FETCH_HEAD |-- HEAD |-- config |-- description |-- hooks |-- index |-- info | `-- exclude |-- logs | |-- HEAD | `-- refs | |-- heads | | `-- master | `-- remotes | `-- origin | `-- master |-- objects | |-- 44 | | `-- de11e3e3bbe90467ce478b79e821014c87aecd | |-- 6a | | `-- bc29053633bce78260d5ac85a7f44b63b5d44e | |-- 86 | | `-- b5874bc0612638b63e760d5b47bcf43a529bb1 | |-- info | `-- pack `-- refs |-- heads | `-- master |-- remotes | `-- origin | `-- master `-- tags 18 directories, 15 files
如上图,这是user02GIT仓库的目录结构,在调用tree命令之前,我用user02提交了一个文件,所以有些目录里面也就有了内容,初始化状态这些目录结构里面是没有内容的,hooks文件夹里面本来是有一些sample文件,今天暂时不会涉及到hook内容,所以全部删掉了,接下来我们一起看看这些目录文件的作用。
HEAD:它是一个文件,存储我们当前的工作分支,以及当前分支指向的最新commit提交,我们直接通过cat命令看看里面的内容,看代码,
$ cat .git/HEAD; cat .git/refs/heads/master; git cat-file -p 6abc ref: refs/heads/master 6abc29053633bce78260d5ac85a7f44b63b5d44e tree 44de11e3e3bbe90467ce478b79e821014c87aecd author xxx <Administrator@JDF546R4F5WFBZD> 1648389719 +0800 committer xxx <Administrator@JDF546R4F5WFBZD> 1648389719 +0800 user02 1st
以上代码我是三个命令一起执行了,cat .git/HEAD; 输出了ref: refs/heads/master,表示当前工作分支是master,cat .git/refs/heads/master; 输出了内容为6abc2,这是一串sha1的hash值,表示当前master的最新提交索引在6abc这个文件里面,git cat-file -p 6abc命令查看最新提交内容,这里面涉及到了几个文件对象,稍后再说。
FETCH_HEAD:这个文件在仓库初始化状态是没有的,当我们执行git fetch或者git pull命令之后产生的,它里面存储了远程库所有分支最新提交的sha1索引,并且当前工作分支排在最前面,它有什么作用呢?这里有个小细节,git pull命令实际是两个命令的组合,当我们执行git pull的时候,会先执行git fetch,fetch之后也就产生了这个文件或者说更新了这个文件,并且把远程信息拉取到了本地,此时还没有合并,接着执行git merge合并,其实就是给merge提供对象。
$ cat .git/FETCH_HEAD 6abc29053633bce78260d5ac85a7f44b63b5d44e branch 'master' of file:///h:/git/server/Test.Service/ $ git cat-file -p 6abc tree 44de11e3e3bbe90467ce478b79e821014c87aecd author xxx <Administrator@JDF546R4F5WFBZD> 1648389719 +0800 committer xxx <Administrator@JDF546R4F5WFBZD> 1648389719 +0800 user02 1st
命令一,查看fetch head内容,当前只有一个master分支,所以只有一行输出,如果有多个分支会有多条记录,61bc表示远程master的最新提交,命令二查看远程master最新提交对象,它同样是个索引,它是个tree对象,对象这部分在objects目录的时候再说。
config:配置文件,主要存储的是本地配置信息,最只管的就是,我们在公司提交代码到服务器,显示的作者信息就是来自于这个配置文件,如果本地配置文件我们没有设置信息,如用户邮箱啥的,git会从全局配置信息获取,全局配置信息我就不贴了,命令很简单,$ git config --global -l
$ cat .git/config [core] repositoryformatversion = 0 filemode = false bare = false logallrefupdates = true symlinks = false ignorecase = true [user] name = xxx [remote "origin"] url = file:///h:/git/server/Test.Service/.git fetch = +refs/heads/*:refs/remotes/origin/* [branch "master"] remote = origin merge = refs/heads/master
index:暂存区的索引文件,它里面存储了文件名、目录以及它们的内容索引地址,它关联了工作区和仓库区,这个怎么理解?我现在工作区新添加了一个文件,执行git status,git终端输出文件未跟踪,当我执行git add命令之后再次执行status,它又提示我staged,所有这些都是通过索引区的这个index文件完成,我们通过命令ls-files -s看下它内容
$ git ls-files -s 100644 86b5874bc0612638b63e760d5b47bcf43a529bb1 0 user02.txt $ git cat-file -p 86b5874 user02
这里我只做了一个简单的文件提交,所以里面只有一条数据,user02.txt表示文件名,那串hash表示内容索引,我也通过cat-file把它数出来了。
objects:这个目录表示对象集合,这个里面的对象一般有三种,blob、commit、tree这三种类型,我们前面提到的所有sha1索引指向的内容都在这objects目录里面能找到,我们这里有3个objects,我们通过命令cat-file命令看看,
$ git cat-file -t 44de;git cat-file -t 6abc;git cat-file -t 86b5 tree commit blob
git cat-file -t 查看的是对象类型,接着我们通过cat-file -p查看这三个索引对应的内容
$ git cat-file -p 44de;git cat-file -p 6abc;git cat-file -p 86b5 100644 blob 86b5874bc0612638b63e760d5b47bcf43a529bb1 user02.txt // tree对象的内容 tree 44de11e3e3bbe90467ce478b79e821014c87aecd // commit对象的内容 author yukang.wu <Administrator@JDF546R4F5WFBZD> 1648389719 +0800 committer yukang.wu <Administrator@JDF546R4F5WFBZD> 1648389719 +0800 user02 1st user02 // blob对象的内容
以上内容我简单做了一些注释,这里再补充一下,blob对象产生是在 git add命令之后,这个对象只会记录文件内容(注意如果两份或者以上文件里面的内容完全一样,只会有一个blob对象产生,空文件夹不跟踪),commit对象产生是在 git commit命令之后,这个对象一般会包含一个tree对象以及其他辅助信息,如作者、时间戳、时区、提交说明、父对象(因为我这边是首次提交),tree对象的产生同样是在commit命令之后,tree对象里面主要记录的是文件内容指针,目录结构,文件名,整个设计确实很巧妙。
refs目录:前面有提到过,主要记录的是本地、远程、以及标签的内容索引地址,可以回到上面看一下。
pack: objects对象打包目录,一般情况下,我们第一次从远程拉取文件,我们的所有对象都是压缩打包的,包括index,还有一种情况,我们执行git gc命令,git也会帮我们打包所有objects对象,我们可以通过git verify-pack -v 命令查看里面的内容
|-- objects | |-- info | | |-- commit-graph | | `-- packs | `-- pack | |-- pack-872cbb392373e21735af47cb261f9fe3886ea31c.idx | `-- pack-872cbb392373e21735af47cb261f9fe3886ea31c.pack $ git verify-pack -v .git/objects/pack/pack-872cbb392373e21735af47cb261f9fe3886ea31c.pack c4ca5ee2c3162610f2d218ebf61e22f153875def commit 222 155 12 6abc29053633bce78260d5ac85a7f44b63b5d44e commit 193 136 167 fcd15acf93cad34ac127b658f4e16be63a12e915 blob 3 12 303 0fe633e30c461e9e5e08d545fe06d909243955dd blob 3 12 315 98d03acc98edaf728a186256b5cc25de43447055 blob 4 13 327 86b5874bc0612638b63e760d5b47bcf43a529bb1 blob 7 16 340 a5ecf8da194ea4dbe5b59e9156e45aba1246cb54 tree 36 45 356 64229d6afd42a9d26082572a8d11f9b529a12fa3 tree 103 108 401 44de11e3e3bbe90467ce478b79e821014c87aecd tree 38 49 509 non delta: 9 objects .git/objects/pack/pack-872cbb392373e21735af47cb261f9fe3886ea31c.pack: ok
结尾:
GIT计划分两次写,今天主要是以概念为主,以静态方式解析git背后的逻辑原理,由于时间问题,明天还要上班,可能有些地方写的不够详细,或者理解不对,包含吧,下次以操作为主,当然不是讲命令操作,主要是挑一些工作中碰到的问题,比如回退、撤销、变基、合并等背后的一些逻辑。