0%

使用 Shell 脚本禁止 Git 提交大图

最近需要实现一个需求,让团队所有人在提交代码之前,能够自动检查提交中图片文件的大小,如果超过限制则需要提示并不能提交成功。这个需求听起来比较适合用 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 之类的工具非常厉害,一两句就可以完成比较复杂的功能。