git项目中object清理,.git目录透明化以及基础的git管理基础

一:背景描述

问题起源于生产环境上,每次升级上线采用的是全量的安装包替换的方式升级,然后每次打的新的安装包体积越来越大,导致负责升级的运维同事下载分发安装包的时间过长,因此研发侧分析到是git本身自带的微型的文件管理系统数据过于臃肿导致,故有了此文,目的在于对git项目的文件,分支管理做到简单的透析并试图清理object目录,做到项目瘦身。

二:问题分析

为了分析git项目的基本的逻辑,特意搞了一个简单的git项目以求结构清晰简洁,进入到.git目录下,整个目录的结构如下:
在这里插入图片描述
接下来将对上述主要目录或文件做简要解析。

1:branches

这里,branches是一个目录,但是在我经手的项目中,这个目录都是空的,暂时不知用途,本地的git base中甚至不存在这个目录,暂时忽略其作用,评论区有明白的或者后续有时间我研究研究再补充上。

2:config

这个是文件,是可以直接编辑的文件,其中这个新建的项目内容如下(已做匿名化处理):

[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        fetch = +refs/heads/*:refs/remotes/origin/*
        url = http://name@url/test.git     <---------记录了你从remote拉取项目时的身份信息
[branch "master"]
        remote = origin
        merge = refs/heads/master

当然,这个文件还存储着调用git global --name/emain的账号信息,以及每次本地存储的从remote拉取的分支信息。

3:description

这个是用来对当前git仓库的描述的,无实际用途,当前的这个文件内容如下

Unnamed repository; edit this file 'description' to name the repository.

4:HEAD

这个文件记录着当前工作区所处的分支,例如当前的项目所处的是master分支上,如果在该分支上开发提交也会push到remote的该分支上

ref: refs/heads/master

这个文件是很有用的,例如命令:git reset --hard HEAD,表示回退到当前分支的没有任何变更状态(注意会清空当前分支上所有工作区变更)由于git命令不是本文主要内容,这里只是举例。当然使用命令

git branch -l

结果显示的就是这个HEAD分支

[root@leel info]# git branch -l
* master

5:hooks

这是一个目录,见名思意,钩子,这个目录下是一些钩子方法,通俗的讲就是一系列的中间件方法,这些方法都是用shell的脚本,以其中commit-msg.sample为例

test "" = "$(grep '^Signed-off-by: ' "$1" |
         sort | uniq -c | sed -e '/^[   ]*1[    ]/d')" || {
        echo >&2 Duplicate Signed-off-by lines.
        exit 1

6:index

这是一个文件,内容是二进制格式书写的,是暂存区(stage),文件内容是文件名等信息
例如我这里更新一个文件,注意更新前index暂存区的时间如下
在这里插入图片描述
当我变更一个文件,并且git add 将文件添加到暂存区后,这个时间就变更了,这个文件存储着暂存区的内容。
在这里插入图片描述

7:info

这是个目录,其中有一个文件exclude,见词知意,是排除的意思,这个文件中用来排除掉在.gitignore文件中声明的意外的文件,可以免于被忽略提交,实际中没有使用过,可以忽略这个功能。

8:logs

这是一个目录,这个目录存储着commit日志信息,例如执行命令git log,所返回的结果就是这里获得的。这个logs是很重要的,这里着重介绍一下:
由于自己是新建的项目,没有进行过复杂的操作,所以这个目录下的内容较少

├── logs
│   ├── HEAD             <---------记录着所有当前本地仓的能够引起commit日志变更的命令,pull/rebase/reset/commit等命令
│   └── refs            <-----------记录着本地仓库所有分支,commit日志及其关联关系
│       └── heads       <----------所有分支的能引起日志变更的操作和
│           └── master  <----------分支名,查看该分支文件记录内容,得到该分支上的变更

由于上述是自己新克隆下来的项目,很干净,所以信息不多,为此,特意找了一个在用的项目,脱敏后如下:

.
├── HEAD
└── refs
    ├── heads
    │   ├── qa
    │   ├── version_4.4.0
    │   ├── version_4.5.0
    │   ├── version_4.5.0.1
    │   ├── version_4.5.1
    │   └── update_bulk
    ├── remotes       <---------记录了当前机器上所有人在该项目仓库中添加的远程路径(公用的机器上更新各自代码的产物)
    │   ├── xiaoming  <---------该用户名以及该用户的远程分支
    │   │   ├── 2019_master
    │   │   ├── 4.3.0_vip6  <--------远程分支名,可以查询该分支的变更,`cat 分支名`
    │   │   └── yz_vip
    │   ├── leel
    │   │   ├── 4.3.0_add_az
    │   │   ├── 4.3.0_vip6
    │   │   └── yz_vip
    │   ├── leel_ez
    │   │   ├── 4.3.0_add_az
    │   │   ├── 4.3.0_vip6
    │   │   └── yz_vip
    │   └── xiaohong
    │       ├── 2019_master
    │       ├── 4.3.0_add_az
    │       ├── 4.3.0_vip6
    │       └── yz_vip
    └── stash    <---------git stash执行后保存的记录

经过复杂操作的洗礼的logs目录就复杂多了,新增了stash文件以及remotes目录,
这里需要注意的是,如果当前的代码仓只有自己一个人使用,是不会在remotes中出现多个用户的记录,同时,这里stash细心的人就会发现没有按照用户名来做分类,说明stash是一个全局的栈存储,不相信的话可以自行查看该文件,这里就不演示了(脱敏费事),相关的文章具体可以移步我的其他资源https://blog.csdn.net/qq_21583139/article/details/123571869

9:objects

这个目录是此次安装包瘦身的关键,‘万物皆对象’,git也不例外,对每次的commit都会建立一个‘档案’,保证每次的checkout 都能立即切换到其他的分支,每次git reset HEAD^都能够回退到上一次的commit状态,这是因为git整体维系了一个微型的文件管理系统,分支/commit就是以文件的形式存储在这个目录下的。
接着我们继续看objects目录及其下面的文件。

.
├── 7f                                          <----------- 每次commit对象的ID的前两位
│   └── a09fafe8441d1f3b6b9fdd1dd2247e5e1725e3  <------------commit id除去前两位的剩余字符,和前两位拼接就是完整的commit id,存储着二进制格式数据
├── info
└── pack   <------------git 项目自动打包的文件,瘦身的主要对象
    ├── pack-06431b47ca3c88c64b1b8c91e44550942a8584e1.idx   <-------------二进制索引文件
    └── pack-06431b47ca3c88c64b1b8c91e44550942a8584e1.pack  <-------------二进制pack文件

注意,这里的7f是一个目录,也就是说所有经过git处理的commit经过hash后的id,只要前两位是7f,就会被放置在这个对象目录下,而且使用二进制存储也是git的考量的,这是因为 git 出于内存考虑,会使用 zlib 压缩算法 对存储内容都进行了压缩处理,为了能获取可读的内容我们可以通过如下命令,看看这个二进制文件内容到底是什么,

git cat-file -t <key> # 获取文件类型
git cat-file -p <key> # 获取文件内容
git cat-file -s <key> # 获取文件大小

[root@leel 7f]# git cat-file -p 7fa09fafe8441d1f3b6b9fdd1dd2247e5e1725e3
这个是研究生毕业的系统项目,django2.1.7,bootstrap4,jquery

因此通过上边的命令实现了解析,记住这个解析后的内容。
为了和上面二进制内容对比,查看这个commit到底做了哪些改变

git show commit_id
[root@leel 7f]# git show 7fa09fafe8441d1f3b6b9fdd1dd2247e5e1725e3
这个是研究生毕业的系统项目,django2.1.7,bootstrap4,jquery

结果一致,因此,我们就知道了每次我们想要查看每个commit到底做了哪些变更所执行的git show id以及切换某个commit状态时的git checkout commit_id时git是怎么做到的了。
此外,我们知道git中对象的直接引用是通过一个固定的40位的hash来做键的,比如我新建一个文件,并进行如下操作,
在这里插入图片描述
即新增了一个文件,并且git add添加到暂存区后,就会生成一个commit id为18开头的,
我们可以通过如下命令检验

git hash-object test.txt

[root@leel smartlog]# git hash-object test.txt
1844877d0b1be2236e42bbb92434220b4d24b5b0    <------------结果显示确实是这个commit_id

当然,objects目录下的这个新的文件是二进制,需要通过git cat-file -p <key>查看,这里就不赘述。

接下来,就是重头戏pack目录,此时瘦身的主要对象,将在本文第三部分展开。这里简要介绍一下git中的pack对象。
在git中,pack文件可以有效的使用磁盘缓存,并且为常用命令读取最近引用的对象提供访问模式;git会将多个指定的对象打包成一个成为包文件(packfile)的二进制文件,用于节省空间和提高效率。

git 默认的存储方式使用的是松散对象格式。但是当push代码,或者手动调用git gc时,git 会将这些文件打包到 packfile 文件中,packfile 中对于同一个文件只存储一份完整的内容(在最近的提交中,因为 git 默认用户更常查看最近的提交),而之前的提交只需要保存两者之间的差异,真正达到只保存修改的效果。

10:packed-refs

这是一个文件,文件名已经告知我们很多内容了,是packed-references,表示打包-引用。建议了解这个文件前先看11。因为这个文件的内容如下:

# pack-refs with: peeled 
013bee1a32dd6d9684374cae71f5ad8fa725dccd refs/remotes/origin/master  <------分支最新的commit_id remote分支名称
67cf8463e591bdd4d8f37838b69a62b08e6d24c5 refs/remotes/origin/dev

很明显这个文件的打包的路径是refs下的非heads下所有分支命名的,如果remotes下有用户名还会有用户名进行拼接。

11:refs

git目录下最后的结构,这是个目录,存储着引用的信息。细心的话会发现3:logs中也有一个refs,目录结构一致,但是实际上文件存储的内容是不同的。确实是我们在使用branch、tag时大多数都是引用到该目录下,然后再指向具体的objects。属于对用户友好的一种文件寻址方式,可以和objects中的对象存储对比,

项目objectsrefs
交互性计算机友好用户友好
命令对象commitbranch
存储方式hashbranch_name/tag
存储内容每次commit提交内容的二进制数据每个分支最新commit的id

从上表中能够看出端倪,git工具为commit和branch的切换提供了两种文件存储和索引方式,前者存储在objects,后者存储在refs目录下。知道了二者的差别,我们接下来进入refs目录下,详细看看内容。

.
├── heads       <-----------------目录,存储着本地的所有分支,git checkout branch_name的基础,其中回到HEAD,就是显示的当前的branch_name
│   └── master   
├── remotes   <-------------目录,存储远程的所有分支,同样也区分不同用户和3logs一样
│   └── origin
│       └── HEAD  <-----------这里要和3中的logs/refs中的内容区分,这里的分支文件内容不是变更记录,而是commit_id,从而使用git checkout branch_name实现与objects中commit_id的联动
└── tags    <----------通过tag号引用方式

结合以上的分析知道,refs这种用户友好的分支引用方式实际上是一种间接引用方式,本质上还是需要通过objects中的commit_id来切换(因为refs中分支文件中存储的是commit id)这种方式就如同机器语言与高级语言之间的区别。
前面提到了logs下也有一个refs,结构和这个refs相似,为了透明展示,特选择了refs中一个分支文件的数据与logs中的refs中的数据最对比,这里我都选取了master文件进行展示。

路径:logs/refs/heads/master
0000000000000000000000000000000000000000 013bee1a32dd6d9684374cae71f5ad8fa725dccd root <root@leel> 1667271551 +0800    clone: from http://leel@url/hackathon/smartlog.git
路径:refs/heads
013bee1a32dd6d9684374cae71f5ad8fa725dccd

结果呼之欲出,logs侧重变更,refs是引用,侧重寻址,存储的是commit_id。
备注:前面提到refs目录结构和logs/refs一致,所以这个模块中也是有stash文件的,自然也是存储着commit_id,由于篇幅原因,没有列出在refs中。

3:git目录瘦身

通过第二部分的分析,我们知道整个git目录下最占据空间的就是objects目录了,每个commit对象都存储在这个目录下,在项目瘦身中,我直接使用需要瘦身的项目为例,显然直接干掉这个目录是愚蠢的行为,因为会导致git对象丢失,git一系列功能失效报错,甚至后续的对该项目的生产环境打patch也无法实现(直接替换文件的方法除外哈
在瘦身前,pack有如下数量和占据空间大小,

[root@leel .git] ls objects/pack/ | wc -l && du -lh --max-depth=1
78    <--------gc处理前的pack目录下文件数量,29个idx和29个pack
90M     ./objects
7.3M    ./logs
7.3M    ./refs
68K     ./rebase-apply
128K    ./info
4.0K    ./branches
56K     ./hooks
105M    .    <------------gc处理前git目录空间大小

显示文件数78个
先执行git gc操作,关于gc操作读者请自行百度,这里不赘述(这里不探讨是否自动执行git gc问题)。
再次查询pack目录下文件数以及所占空间,

[root@leel .git] ls objects/pack/ | wc -l && du -lh --max-depth=1
2   <---------------pack目录下经过压缩后的数量为2个,一个是idx,一个是pack
57M     ./objects
2.4M    ./logs
324K    ./refs
68K     ./rebase-apply
260K    ./info
4.0K    ./branches
84K     ./hooks
60M     .   <----------------经过gc处理后的git目录空间大小

对比后显而易见,文件数量减少了,磁盘空间也得到了释放,这么一看似乎我们已经实现了效果,在整个项目中,整个git目录也由105M变为了60M,但是根据参考文献5可知,gc后只是对原有的commit做了整合,无效的commit(分支已经删除)暂时不会清除,本文未完待续。

参考文献:
1:https://zhuanlan.zhihu.com/p/471591319
2:http://t.zoukankan.com/linhaostudy-p-8349912.html
3:https://www.ysext.com/articleinfo/1599.html
4:https://blog.csdn.net/lihuanshuai/article/details/37345565
5:https://zhuanlan.zhihu.com/p/492086828

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值