近几天,部署了APP出图自动化测试平台西安Jenkins执行节点,该节点部署应该是能提升APP出图自动化测试的构建效率,但万万没想到,刚部署没多久发现该节点构建频频失败,失败的原因也很直接:GitLab代码仓库中的代码pull到该节点超时(超时时间10min)。杭州节点未出现过该情况,首先怀疑的就是杭州->西安两地间的网络差(GitLab代码仓库在杭州),一顿操作,各种更换网络,结果还是失败到底。同时,发现UMS的代码仓库也是在杭州,执行节点在西安,从未出现过这种情况。杭研<—>西研之间的网络肯定是比不过杭研<—>杭研间的网络,但也不至于UMS运行正常,APP就一直失败。探究影响数据传输原因除了网络之外还有传输数据的大小这个因素,然后有了意外发现,UMS测试套项目大小为60+M,APP测试套项目大小居然600+M,其中.git文件大小就超过了600M。至此,发现了问题所在。那为什么APP测试套会如此之大呢?接下来,我们一层层解密。
一、.git介绍
当你创建一个仓库的时候,使用 git init 指令, git 将会创建一个神奇的目录:.git。这个目录下包含了所有 git 正常工作所需要的信息。说白一点,如果你想从你的项目中删除 git 但是又要保留项目文件,只需要删除 .git 文件夹就可以了。
- 这个文件包含你仓库的设置信息。例如这里会放你远程仓库的 URL,你的 email 地址,你的用户名等等信息。 每次你在控制台使用“git config…”指令时,修改的就是这里;
- 这是一个有意思的特性。Git 提供了一系列的脚本,你可以在 git 每一个有实质意义的阶段让它们自动运行。这些脚本就是 hooks,可以在 commit/rebase/pull…. 的前后运行。脚本的名字表示它什么时候被运行。例如一个有用的预推送 hook 可能会测试关于保持远程仓库一致性的式样原则。
- 你可以把你不想让 git 处理的文件放到 .gitignore 文件里。那么,exclude 文件也有同样的作用,不同的地方是它不会被共享,比如当你不想跟踪你的自定义的 IDE 相关的配置文件时,即使通常情况下 .gitignore 就足够了。
二、commit 的真相
每一次你创建一个文件并跟踪它会发现,git 会对其进行压缩然后以 git 自己的数据结构形式来存储。这个压缩的对象会有一个唯一的名字,即一个哈希值,这个值存放在 object 目录下。
在探索 object 目录前,我们先要问自己 commit 到底是何方神圣。commit 大致可以视为你工作目录的快照,但是它又不仅仅只是一种快照。
实际上,当你提交的时候,为创建你工作目录的快照 git 只做了两件事:
如果这个文件没有改变,git 仅仅只把压缩文件的名字(就是哈希值)放入快照。
如果文件发生了变化,git 会压缩它,然后把压缩后的文件存入 object 目录。最后再把压缩文件的名字(哈希值)放入快照。
一旦快照创建好,其本身也会被压缩并且以一个哈希值命名。那么所有的压缩对象都放在哪里呢?答案是object 目录。
Git 维护着一个微型的文件系统,其中的文件也被称作数据对象。所有的数据对象均存储于项目下面的 .git/objects中。
commit 包含四个部分:
- 工作目录快照的哈希
- 提交的说明信息
- 提交者的信息
- 父提交的哈希值
只要有一次将一个大文件误提交了,那么即使我后面把它删除了,但是,实际上在.git中,这个文件还是存在的,虽然我们可能再也不需要他了,但是他还在那里默默的存在着。。。
Git与大部分版本控制系统的差别是很大的,比如Subversion、CVS、Perforce、Mercurial 等等,使用的是“增量文件系统” (Delta Storage systems), 就是说它们存储每次提交(commit)之间的差异。Git正好与之相反,它会把你的每次提交的文件的全部内容(snapshot)都会记录下来。这会是在使用Git时的一个很重要的理念。
就是说,如果我又一次把一个大文件务提交到git仓库中了,那么,下次提交时,即使你只改动了某个文件的一行内容,Git 也会生成一个全新的对象来存储新的文件内容。
至此,真相浮出水面了,APP出图自动化测试套,换了无数次EZView、EZLive的apk,也就是说.git/objects中的600M空间绝大部分是历史+现存的apk(当前1个apk大约是40+M大小)。接下就是如何删除这些不必要的apk,来给测试套瘦身,远程仓库瘦身。
三、删除.git/objects中的大文件
- 查看哪些历史提交过文件占用空间较大
git rev-list --objects --all | grep "$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -10 | awk '{print$1}')"
- rev-list命令用来列出Git仓库中的提交,我们用它来列出所有提交中涉及的文件名及其ID。 该命令可以指定只显示某个引用(或分支)的上下游的提交。
- –objects:列出该提交涉及的所有文件ID。
- –all:所有分支的提交,相当于指定了位于/refs下的所有引用。
- verify-pack命令用于显示已打包的内容
- 重写commit,删除大文件
使用以下命令,删除历史提交过的大文件:
git filter-branch --force --index-filter 'git rm -rf --cached --ignore-unmatch xxx/xxx/' --prune-empty --tag-name-filter cat -- --all
上面脚本中的xxx/xxx/请换成你第一步查出的大文件名,或者这里直接写一个目录。如写成data/ezlive_a_apk/或data/ezlive_a_apk/EZLive_Android-B2105.1.21.0.apk
- 清理和回收空间
虽然上面我们已经删除了文件, 但是我们的repo里面仍然保留了这些objects, 等待垃圾回收(GC), 所以我们要用命令彻底清除它, 并收回空间,命令如下:
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now
- 推送修改后的repo
以强制覆盖的方式推送你的repo, 命令如下:
git push --force
git remote prune origin
至此,删除了.git/objects中的apk大文件,经过多次、反复努力,APP出图测试套大小成功被减少到了90+M,极大降低了西安app出图测试节点代码拉取的失败率。
四、后记
因app每个apk的大小在40+M,且不排除apk的大小还会在增加。所以后续还是得解决apk传输的问题,比如apk不在上库,而是就是部署apk存储服务?期待此次杭研网络升级?延长代码拉取超时时间?重新优化Jenkins节点部署等等?发现问题,解决问题,我们一直在路上!