git仓库清理 | 您所在的位置:网站首页 › git怎么克隆项目到本地文件 › git仓库清理 |
几个小问题
你的git仓库已经创建多久了? (使用命令 git log --reverse 可以查看 )
你的git仓库有多大? (使用命令 git count-objects -vH 可以查看 )
你在使用git的中过程中会出现"卡"的感觉吗?
前2个小问题,大家可以根据笔者提示查看一下;第三个问题呢,可以主观评价一下;欢迎在评论区一起分享一下你们git库的健康状况.
以上的随便哪一个问题都会影响开发者的工作体验; 故git库体积过大是一个必须得解决的棘手问题; 处理git库过大无从下手的最主要的原因是, 我们平时大量操作的git命令都是git的高级命令, 对于git为什么会这么大都不甚了解; 哪些文件能删,哪些文件不能动也是没有很好的判断; 又因为一个项目的git仓库之于一个项目的重要, 更是不言而喻; 担心经过一系列操作会影响之前的提交记录和后续的开发进展,所以行动起来总是畏首畏尾,施展不开手脚; 针对以上总总疑惑和担心,本篇文章都会给出具体的原理解释和操作步骤; git仓库里面存的什么?从根本上来讲git是一个内容寻址(content-addressable)文件系统, 并在此之上提供了一个版本控制系统的用户界面.
所有的git仓库的根目录下面都有个.git 文件, 它默认是隐藏的.
.git文件夹结构如下:
各文件里面存储的内容如下表介绍: 文件夹类型文件夹里面的内容hooks文件夹目录包含客户端或服务端的钩子脚本;我们最常用的就是pre-commit钩子了;info文件夹包含一个全局性排除文件;logs文件夹保存日志信息; git reflog 展示的内容;objects文件夹目录存储所有数据内容, 这就是实际意义上的 git数据库, 存数据的地方; 并且存了所有的历史记录; refs 文件夹目录存储指向数据(分支、远程仓库和标签等)的提交对象的指针;config 文件包含项目特有的配置选项;description文件用来显示对仓库的描述信息,文件仅供 GitWeb 程序使用,我们无需关心;HEAD文件它文件通常是一个符号引用(symbolic reference),指向目前所在的分支。某些罕见的情况下,HEAD 文件可能会包含一个 git 对象的 SHA-1 值。index文件夹文件保存暂存区信息;FETCH_HEAD文件git fetch; 这将更新git remote 中所有的远程repo 所包含分支的最新commit-id, 将其记录到.git/FETCH_HEAD文件中packed-refs文件对refs打包后(git gc)的存储文件, 与底层命令git pack-refs;以上文件夹中objects是git变大的"罪魁祸首", 我们要清理git仓库,其实就是要清理掉objects中没有太大价值的内容,那么我们先介绍一下神秘的objects文件夹里面究竟存了些什么; objects文件夹里面就是存了一系列的git对象: git对象git对象一共有4种; 他们分别是:数据对象(blob), 树对象(tree), 提交对象(commit), 标签对象(tag); 由于此处的知识点涉及到大量底层命令和实战操作;并且官方文档写的十分的细致和透彻,推荐给位看官直接移步官方中文版书籍进行直接的学习;我这边只做一些重点知识的汇总; 数据对象会把二进制文件进行压缩存储,并输出一个唯一对应的40位hash值作为key进行一一对应; git会把压缩内容存在objects文件夹下,并以对应的key值40位的前两位hash值作为文件夹名,后38位作为文件名;(数据对象就是git清理的重点内容);
git是全量快照存储, 而非增量存储; 数据对象的hash值与文件名无关; 故数据对象无法存储文件信息; 为了解决问题4, git提供了树对象;它可以解决文件名保存的问题, 并且使得项目的多个文件也组织到一起;(树对象就是git清理的第二个重点目标,项目文件过多,每次树对象就会较大); 树对象虽然可以表示我们想要跟踪的快照,但是无法记录快照的信息,操作者信息;并且需要git使用者记住hash值; 为了解决问题6, git提供了提交对象; 提交对象还可以指明它的父提交对象;从而形成一系列的log记录; 我们执行git log查看的就是一条条互相链接的提交对象; 下图是一张简单的数据对象(blob),树对象(tree),提交对象(commit)三者之间的关系:
为了减少hash值对git使用者的负担, git提供了各种各样的引用. 比如: 分支、HEAD引用、标签引用和远程引用; 由于引用对git仓库的体积影响面不是很大, 相关内容还是推荐各位读者直接对官方中文版书籍相关章节进行阅读. 下图是一张最基本的git中4种对象和各引用的关系:
svn等其他的版本控制系统, 都是对文件版本的理念, 是以文件为水平维度, 记录每个文件在每个版本下的delta (改变)值. 而git对文件版本的管理理念却是以每次提交为一次快照, 提交时对所有文件做一次全量快照, 然后存储快照的引用. 如果文件没有修改,git不再重新存储该文件, 而是只保留一个链接指向之前存储的文件. 快照流如下:
我们执行的git操作,几乎都只会往git数据库中添加数据. 我们很难使用命令从git数据库中删除数据,也就是说 git几乎不会执行任何可能导致文件不可恢复的操作. 哪怕是我们在项目中删除某些文件, 对于git都是在新增新存储对象(树对象, 提交对象). --这边是理解难点; 同别的版本控制系统一样, 未提交更新时有可能丢失或弄乱修改的内容. 但是一旦你提交快照到git中,就难以再丢失数据,特别是如果你定期推送到其它git数据仓库的话. 因为git存的是全量的数据对象的快照; 也就是说一个文件(100kb)你只修改了一行, git仓库都会把这份文件存储两份(201kb); git就会这样越来越大,而git却什么事情都不做吗?答案是: 显然不会; git最初向磁盘中存储对象时所使用的格式被称为“松散(loose)”对象格式. 但是,git会时不时地将多个这些对象打包成一个称为“包文件(packfile)”的二进制文件,以节省空间和提高效率. 这个命令就是: git gc“gc” 代表垃圾回收, 这个命令会做以下事情: 收集所有松散对象并将它们放置到包文件中, 将多个包文件合并为一个大的包文件, 移除与任何提交都不相关的陈旧对象. git gc执行后会创建一个包文件和一个索引.
包文件包含了刚才从文件系统中移除的所有对象的内容. 索引文件包含了包文件的偏移信息, 我们通过索引文件就可以快速定位任意一个指定对象.
但是, 就算git提供了完善压缩优化机制也无法修复我们git仓库过大的问题. 因为git clone会下载整个项目的历史, 包括每一个文件的每一个版本(数据对象, 树对象). 如果所有的东西都是源代码那么这很好, 因为git被高度优化来有效地存储这种数据. 然而, 如果某个人在之前向项目添加了一个大小特别大的文件, 即使你将这个文件从项目中移除了, 每次克隆还是都要强制的下载这个大文件. 之所以会产生这个问题, 是因为这个文件在历史中是存在的git对象(数据对象, 树对象), 它会永远在那里(.git objects 文件夹下). 至此我们需要找到两类文件, 它们是我们重点需要处理的文件: 本可以存在cdn上的资源如: 视频, 图片, icon, 等; 文件虽然有工程意义, 需要保留在项目里面, 但我们却没有必要了解他们的commit记录的文件如: 编译后供给cdn服务器编译后的文件, 后端接口生成的类型定义文件(pont工具), 打包生成的镜像文件, 等;至此我们了解到, .git究竟存量哪些内容,他们分别有什么用. 也明确了我们要清理掉哪些东西--它们就是git数据对象和重新整合的树对象和提交对象; 接下来的工作就是找到上述整理的2类文件进行清理了. 那我们到底用什么方法来清理呢? 下面我们来介绍2个工具, 一个是官方提供的filter-branch, 二就是本篇文章的主角BFG; 用filter-branch去做git仓库的清理(纯了解!无需实战!)介绍两个本次清理需要多次用到的git底层命令: verify-pack 可以查看git pack包中存储的数据对象的相关信息; git verify-pack -v .git/objects/pack/[pack包的hash].idx本条命令返回的格式如下: SHA-1 type size size-in-packfile offset-in-packfile或 SHA-1 type size size-in-packfile offset-in-packfile depth base-SHA-1其中第一列是hash值, 第二列是git对象的类型,第三列是原本的大小, 第四列是打包后的大小 .... 我们需要的是前3列的信息, 并且用linux命令以第三列进行排列; 取最大的一些文件进行分析; 2. rev-list 可以根据hash值查看到,对应的文件名信息; git rev-list --objects --all | grep [blob对象hash值]使用步骤如下: 查找占用空间大的文件是哪些. 执行以下命令行: # 使用verify-pack命令查看, pack包里面的最大的10个文件对应的hash值 git verify-pack -v .git/objects/pack/[pack包的hash].idx | sort -k 3 -nr | head -n 10 # 根据rev-list命令来查看, 最大的文件的 文件名是什么 git rev-list --objects --all | grep [blob对象hash值] # 以下为真实操作的demo git verify-pack -v .git/objects/pack/pack-20e0b86c39b603c14f134768b583fb7d052b44d9.idx| sort -k 3 nr | head # 查看hash值为d8ffa048ad87ee8fcb8eada24e3ecc36f7eb2fc3 它对应的文件名是什么 git rev-list --objects --all | grep d8ffa048ad87ee8fcb8eada24e3ecc36f7eb2fc3以上两步在整个清理中我们需要多次用到, 且需要多次查看 hash类别和查找对应的文件名故我们可以写一个名为largelist.sh的shell脚本存放在.git文件下用以快速查找体积较大的对象内容; (清理后需要删除largelist.sh脚本); #!/bin/bash #set -x IFS=$'\n'; # 默认值是 10个 可以一次性展示更多 就修改第15行代码 | head -n 30 或者更多 objects=`git verify-pack -v objects/pack/pack-*.idx | grep -v chain | sort -k3nr | head` echo "All sizes are in kB. The pack column is the size of the object, compressed, inside the pack file." output="size,pack,SHA,location" for y in $objects do # extract the size in bytes size=$((`echo $y | cut -f 5 -d ' '`/1024)) # extract the compressed size in bytes compressedSize=$((`echo $y | cut -f 6 -d ' '`/1024)) # extract the SHA sha=`echo $y | cut -f 1 -d ' '` # find the objects location in the repository tree other=`git rev-list --all --objects | grep $sha` #lineBreak=`echo -e "\n"` output="${output}\n${size},${compressedSize},${other}" done echo -e $output | column -t -s ', '创建完成后需要给 largelist.sh 文件赋予执行权限; 并执行文件 chmod +x largelist.sh ./largelist.sh结果格式如下:
3. 使用git filter-branch 命令 git filter-branch --index-filter \ 'git rm --ignore-unmatch --cached [第1步找到需要清理的文件名]' -- [第2步找到第一次提交的hash值]^.. # 以下为demo值, 删除和名为 w.gif 相关的文件记录, 它首次操作的commit对象的hash值为d90c646 注意 ^ 符号和 .. 符号 git filter-branch --index-filter \ 'git rm --ignore-unmatch --cached w.gif' -- d90c646^..4. 重复第2, 3两步,删掉所有有影响的文件, 然后再重复1,2,3三步, 继续查看是否还有有问题的资源需要清除; 5. 清理相关的历史 log, 和清理相关的git dead的对象和无引用的对象; rm -Rf .git/refs/original rm -Rf .git/logs/ git gc git prune --expire now6. 此刻本地git仓库以及清理完毕. 7. 后续的怎么同步到所有开发本地, 以及如何避免被污染后续再讲 亲测git filter-branch是可以做git仓库清理的; 但是它非常非常的慢! 官方文档也有强调. 这个命令非常耗资源,而且效率不高,容易出错;整个文档 warning占了大半篇幅; 那么还有别的方法了吗? 那就要祭出万众瞩目的 BFG 了! 用BFG去做git仓库的清理(最佳工具)BFG官网 BFG是用Scala语言写的一个工具库, 它做的就是git仓库清理的工作,它的特点就是快, 还有就是好用, 功能非常强大! 那为什么不直接介绍BFG呢? 还是希望大家能够通过比对, 能感受BFG的带来的提效; BFG的使用条件 得有本地的 java 环境; 得下载好bfg的jar包 (官网右上角 download 按钮进行下载); BFG使用的几种方法bfg --delete-files id_{dsa,rsa} my-repo.git 作用: 按照文件名来删除指定的文件bfg --delete-folders folders --no-blob-protection my-repo.git 作用: 删除指定的文件夹 --no-blob-protection 表明需不需要在master分支进行文件的保留bfg --strip-blobs-bigger-than 50M my-repo.git 作用: 删除体积大于多少体积的的文件 这个命令其实是没有实战价值, 因为我们不能仅因为它大就判断它是无价值的;bfg --replace-text passwords.txt my-repo.git 替换指定的文件 BFG的使用步骤git clone --mirror git://example.com/some-big-repo.git 一定要加上--mirror 或--bare; --mirror 和 --bare 都是把git镜像下载到本地, 里面的内容就是 .git文件夹里面的内容, 区别是, mirror下载下来的内容是可以通过 git remote update 来更新的, 而 bare的库就不行, 没法同步;java -jar bfg.jar--strip-blobs-bigger-than 100M some-big-repo.git 这一步就是根据bfg提供的几种方法,去定制化删除需要删除的文件.上述的4个方法都可以多次组合使用; 哪些文件是需要删除的? 下文会讲解到;cd some-big-repo.git && git reflog expire --expire=now --all && git gc --prune=now --aggressive 清理完指定的对象后,需要对git的reflog记录进行清空, 并且进行git仓库的垃圾处理删除无意义的git对象;git push --mirror bfg基本关键步骤就是上面4步, 但是具体在项目实战中还有很多坑, 下文会补上更为详细的保姆级的实战操作记录; Dataphin前端仓库清理的BFG实战操作记录Dataphin是阿里巴巴集团OneData数据治理方法论内部实践的云化输出,一站式提供数据采、建、管、用全生命周期的大数据能力,以助力企业显著提升数据治理水平,构建质量可靠、消费便捷、生产安全经济的企业级数据中台。 1. 根据项目结构分析整理出哪些是需要处理的文件 文件文件夹描述处理原因处理方式src/services由pont生成的接口类型定义,和接口请求对象;1. pont自动生成,无查看修改记录的意义;1. 文件过多,内容过大;保留文件,仅删除commit记录devBuild之前的DLL文件, 用来做本地模块缓存;1. 文件多, 体积大; 2.升级webpack5后也无需此资源了1. 没有阅读查看价值无查看修改记录的意义删除文件和commit记录 2. 利用largelist.sh脚本去查看其它可以被治理的文件由于分支众多, 且历史悠久, 无法单单通过查看当前几个主分支的项目结构就能找到所有的有文件的问题; 所以这个时候还是需要使用git verify-pack 和 git rev-list 这两个git底层命令去查找需要治理的文件; 上文filter-branch章节有详细介绍; 这边我就直接贴一下largelist.sh脚本的内容, 以及执行步骤; 因为BFG需要使用的是git镜像所有我们得把largelist.sh存放到同级目录下, 清理完成后得删除掉largelist.sh脚本; #!/bin/bash #set -x # Shows you the largest objects in your repo's pack file. # Written for osx. # # @see http://stubbisms.wordpress.com/2009/07/10/git-script-to-show-largest-pack-objects-and-trim-your-waist-line/ # @author Antony Stubbs # set the internal field spereator to line break, so that we can iterate easily over the verify-pack output IFS=$'\n'; # list all objects including their size, sort by size, take top 10 # 默认值是 10个 可以一次性展示更多 就修改第15行代码 | head -n 30 或者更多 objects=`git verify-pack -v objects/pack/pack-*.idx | grep -v chain | sort -k3nr | head` echo "All sizes are in kB. The pack column is the size of the object, compressed, inside the pack file." output="size,pack,SHA,location" for y in $objects do # extract the size in bytes size=$((`echo $y | cut -f 5 -d ' '`/1024)) # extract the compressed size in bytes compressedSize=$((`echo $y | cut -f 6 -d ' '`/1024)) # extract the SHA sha=`echo $y | cut -f 1 -d ' '` # find the objects location in the repository tree other=`git rev-list --all --objects | grep $sha` #lineBreak=`echo -e "\n"` output="${output}\n${size},${compressedSize},${other}" done echo -e $output | column -t -s ', '创建完成后需要给 largelist.sh 文件赋予执行权限; 并执行文件 chmod +x largelist.sh ./largelist.sh结果格式如下:
通过查看返回结果, 前置的都是视频文件, 所以我们需要把所有 .mp4 相关的文件都删除掉; 还有一些压缩包, 一些报告文件. 因为,这些可能都是开发者自己的开发分支, 然后提交到远程; 所以这些问题在实际项目中还是比较难避免的; 最后我们删除的文件有: services, devBuild,*.mp4, welcome.gif, welcome2.gif, app.js,app.js.map, report.html, test.tar.gz, editor.main.js.map, editor.main.js, public.tgz, log0000, guiji.fbx ... 3. 处理好后的干净库如何同步到所有参与开发的同学?经过上述所有的操作得到一个干净的git库后, 还需要避免其他有历史库的同学执行push操作, 因为一旦执行push操作就会前功尽弃,导致陈旧的历史库再次污染到新的远程仓库中; 所以我们需要设计一个全流程的操作的步骤, 去避免这样的污染问题; 我这边将提供我们实际的操作每一步的操作,并说明原因和考虑点; 4. 步骤详细版:特殊说明: 因为我们项目的原仓库地址关联了许多阿里内部的平台,所以我采用的是保留原项目地址的方案,这个方案相对复杂一点; 当然读者也可以采用废弃旧库地址,直接重启一个新库的方案来进行迁移, 那么步骤和下面列出来会有稍许不同; 欢迎大家在评论中补充创建新库的具体步骤; 安装 java 环境; 下载bfg-1.14.0.jar; 执行it clone --mirror xxx.git xxx-mirror.git 提前下载好两个镜像 (一个用来操作的:xxx-mirror.git ,一个用来做安全备份:xxx-mirror-copy.git);设置gitlab库为私有库, 保存之前的库成员列表; 删除所有当前库用户的权限, 仅留自己, 确保所有人不能push(gitlab中浏览者也可以进行push); 因为优化的结果, 一旦被之前开发者本地旧仓库一进行push操作就会有污染; 为了防止旧库的push污染操作,在优化前关闭所有的push通道; 选择在周日执行git库的优化, 是一个很好的时间窗口;找其他同学check权限设置是否生效; 在上述的两个镜像仓库执行 git remote update , 进行镜像的更新; 备份库需要删除remot引用,断绝污染: git remote remove origin ; 开始执行 BFG 的命令 java -jar ~/utils/bfg-1.14.0.jar --delete-folders {services,build,devBuild} --no-blob-protection dataphin-mirror.git java -jar ~/utils/bfg-1.14.0.jar --delete-files "*.mp4" --no-blob-protection dataphin-mirror.git java -jar ~/utils/bfg-1.14.0.jar --delete-files {xxxxx.gif,xxxxx.gif} dataphin-mirror.git java -jar ~/utils/bfg-1.14.0.jar --delete-files "{app.js,app.js.map,guiji.fbx,report.html,test.tar.gz, editor.main.js.map, editor.main.js, public.tgz, log0000}" dataphin-mirror.git 支持 *.mp4 这样的语法, 但是得加上 " 引号包裹;cd xxx-mirror.git git reflog expire --expire=now --all && git gc --prune=now --aggressive 清理完指定的对象后,需要对git的reflog记录进行清空, 并且进行git仓库的垃圾处理删除无意义的git对象;删掉largelist.sh脚本; git push --mirror 由于这边git push --mirror 推送的引用 较多, 需要关掉gitlab的邮件检查否则会报错; 报错文案如下:
gitlab仓库中邮箱检测的关闭路径: Settings => Features => Check Email 一定要点击SAVE CHNAGES按钮才会生效! 在git push的时候由于引用较多往往需要集团gitlab服务的同学帮忙远程处理(执行 git gc); 各位同学切换原库的remote到一个备份的bak git仓库上. 并改老库仓库名为 xxx-bak git remote remove origin; git remote add origin [email protected]:xxx-bak.git; cd .. && mv xxx xxx-bak; 防呆处理, 防止万一有人误操作; 当然删除本地的就仓库也行,也能避免污染,但是不推荐这么去做, 因为本地的旧仓库在短期内还是有价值的; 保存旧库在本地, 会让开发者感觉到踏实;确认参与开发的同学第14步的切换操作成功, 并删除本地多余的的老仓库后, 给予开通权限; 参与开发同学克隆新库到本地, git clone [email protected]:xxx.git; 完成! 后续操作就和之前开发的步骤一样了! 此时你得到两个库: 1个是旧库 xxx-bak 它存着你之前的代码; 一个是新库它是优化瘦身后的新库; 老库过段时间可以删除. 优化的后的镜像xxx-mirror.git也可以删除. 备份镜像xxx-mirror-copy.git建议保留一段时间. 5. 本次Dataphin前端优化的效果对比 优化前: 无法clone 项目, 经常报错; 无法git pull项目;什么? 看完文章后还是没有体验到"保姆级"的感受; 那就欢迎多多评论,来沟通, 一起来共同学习git库的清理吧; 推荐视频:Git初版原理与源码解读 |
CopyRight 2018-2019 实验室设备网 版权所有 |