从规则列表中驱动bash程序(用于git precommit hook)

时间:2017-10-12 21:59:12

标签: git bash

我正在写一个git precommit hook,我相信它是由bash执行的。我需要检查修改后的文件,如果内容与模式匹配,则显示错误消息。

[{
    fileName: "*.html",
    pattern: "\<font\>",
    message: "Don't use font. Use style sheet istead!"
}, 
{
    fileName: "/Admin/*",
    pattern: "string\.Format\(",
    message: "Don't use string.format. Use string interpolation istead!"
},
....
]

我有很多这样的规则。如何在bash中定义这些规则?如果bash不擅长这个,我该如何实现precommit hook?

1 个答案:

答案 0 :(得分:2)

在Git方面:只要您的操作系统可以通过exec样式系统调用运行挂钩,就可以用任何语言编写挂钩。通常,脚本由某些shell运行,通常是/bin/sh,通常不是 bash,但在类Unix系统上,如果脚本的第一行读取#! /bin/bash或例如,#! /usr/bin/env bash,脚本肯定会用bash运行。您可以使用#! /usr/bin/env python使用Python运行脚本,依此类推。

(同样在Git方面,在预提交钩子中,您可以使用git diff --cached --name-status来获取提议提交中与HEAD提交中的文件不同的文件列表,包括精确的方式它们不同。添加明确的--find-renames--no-renames以明确启用或禁用重命名检测,无论用户配置设置如何;或使用git diff-index代替git diff - 其余部分选项与忽略用户指定的配置项相同。)

在shell脚本方面,bash有很多功能,但最简单的可能就是通过代码运行文件名,并使用显式的glob样式模式匹配和操作:

gripe() {
    echo "$@" 1>&2
    GRIPES=$((GRIPES + 1))
}

check_file() {
    case "$1" in
    *.html) if fgrep "<font>" "$2"; then
        gripe "Don't use font. Use style sheet instead!"; fi;;
    */Admin/*) if grep "string\.Format(" "$2"; then gripe ...; fi;;
    esac
}

现在我们进入整个操作中最特殊的部分:要提交的文件的形式在索引中存在,而不一定在工作树中。这就是为什么上面check_file()中的代码有两个参数。一个是文件的名称,因为它将出现在提交中;另一个是临时文件的名称,我们将在检查之前将索引内容提取到此文件中。

如果您愿意假设没有人会播放任何尚未出现在工作树中的内容,那么您可以跳过很多这种复杂性。使用--name-only--diff-filter,我们可以获取A dded或M odified的文件的文件名列表,并查看其工作树版本:

GRIPES=0
git diff-index --cached --diff-filter=AM HEAD |
    while read path; do
        check_file "$path" "$path"
    done

要做好正确的工作,我们需要将每个文件提取到一个临时文件中(例如,可能使用git checkout-index --temp)并扫描临时文件,然后删除临时文件。

当我们全部完成后,shell变量$ GRIPES会计算投诉数量,这可以用来阻止提交。