深入理解Git内部原理

1,924 阅读5分钟

前言

当我们执行git init命令,或者clone网上某个代码仓库后,会看到有一个.git目录,git所有版本控制信息就是放在目录里面,下面对这个.git目录进行分析。

.git目录

  • hooks:钩子目录,存放执行指定git命令前或者后触发的脚本,可以看到默认会有几个sample文件,如果要开启某个钩子脚本,就把脚本文件名的后缀simple去掉就可以,这些钩子会在特定的时机被触发执行,比如post-commit在整个提交过程完成后执行,可以用于发送提交通知等,另外也有服务端钩子,在推送前或者后执行,比如post-receive在推送结束后会被执行,可以用于通知打包平台启动打包任务。关于git钩子更详细的信息可以参考:自定义 Git - Git 钩子

  • info:保存git的相关信息

  • logs

    • refs:暂存记录,本地分支记录,远程分支记录

    • HEAD:记录每次的变更操作

  • objects:存放真实数据,以Git对象形式存放

  • refs

    • heads:存放所有本地分支最新的commit哈希值

    • stash:存放stash对应的哈希值

    • tags:存放tags相关的

  • config:配置文件

  • HEAD:当前分支,并不存放SHA1值,类似/refs/heads/master,这个指向的文件里会有最新commit的SHA1值

  • index:二进制文件,暂存区

Git中的几种对象

  • 数据对象

    数据对象就是用于存储真实的文件数据,其创建过程如下:

    1. 计算内容大小,构造header

    2. 将header添加到内容前面,构造数据对象

    3. 使用sha1算法计算Git对象的160位hash码,表示成16进制就是40位

    4. 使用zlib的deflate算法压缩数据对象

    5. 存储到.git/objectes/目录下,文件夹名为hash码的前两位,文件名为hash码的后38位

  • tree对象

    保存目录信息,也就是目录树的非叶子节点的版本控制信息,需要从暂存区中创建。

  • commit对象

    保存某次提交的详细信息,包括时间、提交人等。

  • tag对象

    跟commit对象类似,包含了一个指针,该指针指向一个commit对象,而且永远不会改变。

Git提交过程原理

  1. 添加到暂存区:git add xxx

    • 创建数据对象

    • 更新index文件

  2. 提交:git commit -m "xxxx"

    • 如果存在目录,就创建tree对象

    • 创建commit对象

  3. 将上面创建的对象保存为目录和文件

可以看到,git提交的整个过程,其实就是创建数据对象、tree对象和commit对象的过程,最终这些对象都会保存到objects目录下。

Git数据存储原理

Git默认采用的是松散对象格式,也就是每次保存一份完整数据,比如有个大文件10MB,第一次提交被保存起来了,后面修改了一点东西又提交,这时Git会保存两个10Mb的对象数据,这样看起来就会比较浪费,最好是能够只保存文件的差异部分。

实际上Git就是这样做的,如果存在太多的松散对象,或者执行了push命令,或者git gc命令,Git就会将松散对象进行打包到一个pack文件中,该文件保存文件不同版本之间的差异内容,减少了存储占用,而且是该pack文件是二进制文件,需要采用命令git verify-pack查看。

注意这里pack会将最新版本保存为完整对象,而之前的版本保存差异信息,因为大部分情况下访问的都会是最新版本。

Git传输协议

Git有四种传输协议,包括本地协议、HTTP协议,SSH协议,GIT协议。

  • 本地协议

    远程仓库就存放在硬盘中,这里的硬盘,可以是远程挂载的网络硬盘,远程仓库的地址直接指定为目录路径,也可以加上file前缀。不过共享文件系统配置起来比较麻烦,速度也比较慢。

  • HTTP协议

    采用http/https协议进行传输,这里细分有两种协议:哑协议和智能协议,哑协议是Git 1.6.6版本之前使用的协议,Git 1.6.6引入了智能协议。

    • 哑协议

      1. 获取info/refs文件,确定所有分支的最新commit的SHA1值

      2. 获取HEAD文件,确定当前分支,检出到工作目录

      3. 获取第一个commit对象的数据

      4. 往上追溯,获取所有对象数据

    • 智能协议(大部分使用)

      不管是上传(比如push等)还是下载(比如pull等),都是客户端开一个进程,服务端开一个进程,然后协商需要传输的数据,确认好哪些数据需要传输,进程端口是http或者https端口。

    HTTP协议的优点:1.就是使用方便,只需要一个远程地址即可,2.认证方便,直接输用户名密码,不需要像SSH首次配置那么麻烦。

  • SSH协议

    使用ssh协议做为传输协议,使用时需要配置ssh信息,配置后就不用每次都输入用户名和密码了。

  • Git协议

    与http智能协议原理类似,但它监听的端口是9418,Git协议没有授权机制,也就是不能实现控制一部分人能push,另一部分不能push,因此大部分情况的做法是让Git协议只开放读取,禁止推送。

git flow协作流程

稳定分支

  • develop:开发分支

  • master:发布分支

临时分支

  • feature:特性分支,用于临时开发新功能,从develop拉,最终合入到develop。

  • release:预发布分支,当develop分支开发完本版本需求后,就拉release分支,用于修bug,配置发布信息等,这时develop分支继续开发下一版本的需求。当release分支修改结束后,就合入develop和master。注意合入到master时要打tag做标记,比如master_8.1之类的命名。

  • hotfix:修复分支,用于修复线上紧急bug,从master拉,最终合入到develop和master。注意合入到master时要打tag做标记,比如master-8.1.1之类的命名。