最近需要实现一个需求,让团队所有人在提交代码之前,能够自动检查提交中图片文件的大小,如果超过限制则需要提示并不能提交成功。这个需求听起来比较适合用 Git Hooks
(中文叫钩子)来做,不过使用钩子的话需要每个人都在本地设置一次,有更新的话也比较麻烦。我们平时提交到 Gerrit
是使用的仓库中的一个脚本,所以采用了在 shell
脚本中实现这个功能。
获取修改文件列表 要实现这个功能首先就需要找到所有未入库的提交。可以使用 git cherry -v
命令获得。
1 2 3 4 $ git cherry -v + e861d61861a8ce0307cdb2f985a42492a70dcee2 ADD: test1 + a70592c6e47d4d314efdd53a430b433cee4f3cb8 ADD: test2 + df5a0bfa22ab009ee7d84feb196f7e01bd464295 MOD: del file
那如何从中获得所有修改的文件列表呢?可以使用 git diff
命令,这里可以对比相对当前 HEAD 的前 N 个提交的文件修改记录,前面为 D 的表示删除,我们不需要关注。利用上面得到的提交个数,我们就可以得到未入库的所有提交的文件修改列表。
1 2 3 4 5 6 $ git diff --name-status HEAD~3 HEAD M app/src/main/java/.../abc.kt M app/src/main/java/.../def.kt A app/src/main/res/drawable-xxhdpi/xyz.png A app/src/main/res/drawable-xxhdpi/wallpaper01.jpg D app/src/main/res/drawable/aaa.png
这里获取修改的提交数目有个坑,直接用 wc
输出行数的话,输出结果前有空格,导致后面用的时候出错,最后是用 sed
处理了下:
1 2 3 4 5 6 7 HEAD~ 3 fatal: ambiguous argument '3': unknown revision or path not in the working tree. $ git cherry -v | wc -l 3 $ git cherry -v | wc -l | sed 's/ //g' 3
处理文件列表 怎么从上面列表中获取到非删除的图片文件列表呢, grep
大法好,使用 -v
参数即可输出不包含匹配文本的所有行。再使用后缀过滤,即可找出所有添加或者修改的图片文件。
过滤出符合调节的列表后,还需要提取出第二行文件路径,这个时候就需要祭出 awk
神器了,直接提取出第二列。
1 2 3 $ git diff --name-status HEAD~3 HEAD | grep -v ^D | grep -E ".jpg|.png" | awk '{print $2}' app/src/main/res/drawable-xxhdpi/xyz.png app/src/main/res/drawable-xxhdpi/wallpaper01.jpg
接着就是拿文件路径去获取大小了,这里使用 stat
命令,直接执行完会输出一大行信息,使用 awk
输出文件大小的列即可。
1 2 3 4 $ stat -F app/src/main/res/drawable-xxhdpi/wallpaper01.jpg -rw-r--r-- 1 DNMY438 staff 552080 Jul 26 18:00:05 2020 app/src/main/res/drawable-xxhdpi/wallpaper01.jpg $ stat -F app/src/main/res/drawable-xxhdpi/wallpaper01.jpg | awk '{print $5}' 552080
得到大小之后还需要做下判断,很棒的是 awk
里面支持直接写逻辑,所以可以再里面直接判断:
1 2 $ stat -F app/src/main/res/drawable-xxhdpi/wallpaper01.jpg | awk '{if($5 > 100000) {print $10, "size:", $5, "is oversize"}}' app/src/main/res/drawable-xxhdpi/wallpaper01.jpg size: 552080 is oversize
到这里都很顺利,那把两个语句组合到一起是不是就可以呢?直接组合的还就需要把第二个 awk 语句嵌套到第一个 awk 中,试了不可以,其实 cut
命令也可以用来输出列,尝试了下,没有成功,另外这样嵌套写看起来很不直观,逻辑都堆到一起去了,所以最后采用的是先把第一个部分输出的文件列表保存下来,再另外在循环里做判断。
1 2 3 4 5 6 7 $ file_list=`git diff --name-status HEAD~3 HEAD | grep -v ^D | grep -E ".jpg|.png" | awk '{print $2, ","}' ` $ echo $file_list app/src/main/res/drawable-xxhdpi/xyz.png , app/src/main/res/drawable-xxhdpi/wallpaper01.jpg , $ array=(${file_list//,/ } ); for var in ${array[@]} ; do echo $var ; done app/src/main/res/drawable-xxhdpi/xyz.png app/src/main/res/drawable-xxhdpi/wallpaper01.jpg
这里在输出到时候加了逗号做分隔符,方便后面分割成数组。然后把判断文件大小的逻辑添加进去,就完成了:
1 2 $ array=(${file_list//,/ } ); for var in ${array[@]} ; do stat -F $var | awk '{if($5 > 100000) {print $10, "size:", $5, "is oversize"}}' ; done app/src/main/res/drawable-xxhdpi/wallpaper01.jpg size: 552080 is oversize
这里发现有图片文件大小超过限制后,只打印了结果,但是在循环完之后外面需要知道结果,有超过限制的情况就需要退出脚本。刚开始是在外面初始化了一个变量,想在里面赋值,但是 awk 里面不能直接对外面的变量赋值,读取的话可以加 -v
参数,但是赋值试了几种方法没成功,最后就是把整个循环的输出给拿到,然后判断是不是空,不是空的话那肯定是有文件超过限制大小了。
完整脚本 最后完整的脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 file_size_check_enable='True' is_has_oversize='False' if [ $file_size_check_enable = 'True' ]; then echo 'start file size check' commit_count=`git cherry -v | wc -l | sed 's/ //g'` if [ $commit_count -gt 0 ]; then echo total commits:$commit_count file_list=`git diff --name-status HEAD~$commit_count HEAD | grep -v ^D | grep -E ".jpg|.png" | awk '{print $2, ","}'` #echo $file_list array=(${file_list//,/ }); for var in ${array[@]}; do out=`stat -F $var | awk '{if($5 > 100000) {print $10, "size:", $5, "is oversize"}}'`; if [ -n "$out" ]; then echo $out is_has_oversize='True' fi done fi if [ $is_has_oversize != 'False' ]; then exit 0 else echo "no file is oversize" fi else echo 'file size check disabled' fi
一般情况下,更喜欢用 Python 来写脚本,可读性更好。但是不得不说 Shell 下的 awk
, sed
, grep
之类的工具非常厉害,一两句就可以完成比较复杂的功能。