Android Studio项目gradle+Git Hooks 实现提交时对提交日志和代码(checkStyle)的检查

Android Studio项目gradle+Git Hooks 实现提交时对提交日志和代码(checkStyle)的检查

主要解决对项目的日志和代码的规范控制,通过在git提交之前,实现对提交日志和代码的checkStye进行检查。

Git hooks 原理

钩子都被存储在 Git 目录下的 hooks 子目录中。 也即绝大部分项目中的 .git/hooks。 当你用 git init 初始化一个新版本库时,Git 默认会在这个目录中放置一些示例脚本。这些脚本除了本身可以被调用外,它们还透露了被触发时所传入的参数。 所有的示例都是 shell 脚本,其中一些还混杂了 Perl 代码,不过,任何正确命名的可执行脚本都可以正常使用 —— 你可以用 Ruby 或 Python,或其它语言编写它们。 这些示例的名字都是以 .sample 结尾,如果你想启用它们,得先移除这个后缀。
把一个正确命名且可执行的文件放入 Git 目录下的 hooks 子目录中,即可激活该钩子脚本。 这样一来,它就能被 Git 调用。

下面分两部分进行介绍,分别是提交日志部分和代码的检测。

提交日志的检查

要求

每次提交,Commit message 都包括三个部分:Header,Body 和 Footer。

<type>(<scope>): <subject>
// 空一行
<body>
// 空一行
<footer>

其中,Header 是必需的,Body 和 Footer 可以省略。
不管是哪一个部分,任何一行都不得超过72个字符(或100个字符)。这是为了避免自动换行影响美观。

Header部分只有一行,包括三个字段:type(必需)、scope(可选)和subject(必需)。

**type**用于说明 commit 的类别,只允许使用下面7个标识。
- feat: 新功能(feature)
- fix: 修补bug
- docs: 文档(documentation)
- style: 格式(不影响代码运行的变动)
- refactor: 重构(即不是新增功能,也不是修改bug的代码变动)
- perf: 性能优化(performance)
- test: 增加测试
- chore: 构建过程或辅助工具的变动
- revert: 还有一种特殊情况,如果当前commit用于撤销以前的commit,则必须以revert:开头,后面跟着被撤Commit的Header。
revert: feat(pencil): add 'graphiteWidth' option

**scope**用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。

**subject**是 commit 目的的简短描述。

Body部分的格式是固定的,必须写成This reverts commit &lt;hash>.,其中的hash是被撤销 commit 的 SHA 标识符。

实现

我们修改commit-msg文件,由于对shell脚本不熟悉,因此通过python来实现,注意修改commit-msg文件顶部的代码

 #!/bin/sh

修改为

 #!/usr/bin/python2.6

否则识别不了python脚本

  • 1、先创建一个跟commit-msg相关的配置文件(commit-msg-config.txt),并将该文件放置在commit-msg的同一目录下。我们将一些正则表达式,提示信息放在里面,便于后期修改维护,
commitMessageRegex=.+(\n\n.){0,2}
commitMessage=代码提交备注需要格式统一,格式为:\nHeader(72个字以内)+空行+Body(72个字以内,可以省略)+空行 +Footer(72个字以内,可以省略);

HeadLengthMessage=Header字数不能超过72文字
HeadFormatMessage=Head的格式为<type>(<scope>): <subject>  type(必需)、scope(可选)和subject(必需)    **type**用于说明commit的类别,只允许使用下面7个标识。\n**scope**用于说明commit影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。\n**subject**是commit目的的简短描述


headTypeRegex=^(feat|fix|docs|style|refactor|perf|test|chore)(\(.*\)){0,1}$|^revert$
headTypeMessage=type的可选值为:  feat: 新功能(feature)  fix: 修补bug\ndocs: 文档(documentation) tyle: 格式(不影响代码运行的变动)    refactor: 重构(即不是新增功能,也不是修改bug的代码变动) perf: 性能优化(performance) test: 增加测试  chore: 构建过程或辅助工具的变动     revert:当前commit用于撤销以前的commit

RevertCommitHeadMessage=你的type是revert,则必须以revert:开头,后面跟着被撤Commit的Header,当前检测到你的被撤Commit的Header格式有误。
RevertMessage=你的type是revert,则必须以revert:开头,后面跟着被撤Commit的Header。如:\n revert: feat(pencil): add 'graphiteWidth' option

BodyLengthMessage=Body不能超过72个文字
FootLengthMessage=Body不能超过72个文字
  • 2、修改commit-msg文件
#!/usr/bin/python2.6
#coding=utf-8
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message.  The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit.  The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".

# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

# This example catches duplicate Signed-off-by lines.

# test "" = "$(grep '^Signed-off-by: ' "$1" |
#    sort | uniq -c | sed -e '/^[   ]*1[    ]/d')" || {
#   echo >&2 Duplicate Signed-off-by lines.
#   exit 1
# }
import re
import sys
import os

map = dict()
configFile = open('.git/hooks/commit-msg-config.txt','r')

for line in open('.git/hooks/commit-msg-config.txt'):  
    line = configFile.readline()  
    value = line.split('=',2)
    if len(value)==2:
        map[value[0]]=value[1]


filePath    = sys.argv[1]
file       = open(filePath)
content    = file.read(os.path.getsize(filePath))
machObject = re.match(map["commitMessageRegex"], content, flags=0)

if not machObject:
    print map["commitMessage"]
    exit(1)


splitArray = content.split('\n\n',3)
if len(splitArray[0].decode('utf-8')) > 73:
    print map["HeadLengthMessage"]
    exit(1)

head = splitArray[0]
splitHead = head.split(':',2)
if len(splitHead) < 2 or len(splitHead)>3:
    print map["HeadFormatMessage"]
    exit(1)


machHead=re.match(map["headTypeRegex"],splitHead[0],flags=0)
if not machHead:
    print (map["headTypeMessage"])
    exit(1)

if splitHead[0]=="revert" and len(splitHead)==3:
    machRevertHead = re.match(headRegex,splitHead[1],flags=0)
    if not machRevertHead:
        print map["RevertCommitHeadMessage"]
        exit(1)
else:
    print map["RevertMessage"]
    exit(1)


if len(splitArray) > 1 and len(splitArray[1].decode('utf-8')) > 73:
    print map["BodyLengthMessage"]
    exit(1)

if len(splitArray) > 2 and len(splitArray[2].decode('utf-8')) > 73:
    print map["FootLengthMessage"]
    exit(1)

file.close()

其中下面这段代码用于读取我们前面创建的配置文件

map = dict()
configFile = open('.git/hooks/commit-msg-config.txt','r')

for line in open('.git/hooks/commit-msg-config.txt'):  
    line = configFile.readline()  
    value = line.split('=',2)
    if len(value)==2:
        map[value[0]]=value[1]

这段代码用于读取我们提交的日志,用于判断其格式是否正确

filePath    = sys.argv[1]
file       = open(filePath)
content    = file.read(os.path.getsize(filePath))

保存之后,但我们再次通过git提交代码时,git hooks就会对日志进行检查,如果不符合格式,就会将错误打印出来,并终止提交操作。记住,commit-msg的后缀名.sample需要将其去掉,如果还是不起作用,可以通过
chmod a+x commit-msg
给予其读写权限。

提交代码的检查

原理

Android的Gradle Api原生就有checkStyle类型的task,我们需要应用checkstyle plugin,并且实现一个这样的task就可以执行检查代码风格,并且生成检查报告。代码如下:

allprojects {
    ...
    ...
    apply plugin: 'checkstyle'

    checkstyle {
        configFile rootProject.file('checkstyle.xml')
        toolVersion '6.19'
        ignoreFailures false
        showViolations true
    }

    task('checkstyle', type: Checkstyle) {
        source 'src/main/java'
        include '**/*.java'
        exclude '**/R.java'
        exclude '**/BuildConfig.java'
        classpath = files()
    }
}

通过hook git commit就可以来执行这个脚本,然后根据检查结果决定是否可以commit

实现

我们首先要做的有以下几件事:

  • 1、编写一份checkstyle的xml文件
  • 2、用checkstyletask的includeexclude将需要的和不需要的java类添加进来。
  • 3、编写git hook文件调用checkstyle的task

贴上checkstyle的xml文件,大家可以根据自己实际项目需要的进行配置

<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
          "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
          "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">

<!--
    Checkstyle configuration that checks the Google coding conventions from Google Java Style
    that can be found at https://google.github.io/styleguide/javaguide.html.
    Checkstyle is very configurable. Be sure to read the documentation at
    http://checkstyle.sf.net (or in your downloaded distribution).
    To completely disable a check, just comment it out or delete it from the file.
    Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
 -->

<module name = "Checker">
    <property name="charset" value="UTF-8"/>

    <property name="severity" value="warning"/>

    <property name="fileExtensions" value="java, properties, xml"/>
    <!-- Checks for whitespace                               -->
    <!-- See http://checkstyle.sf.net/config_whitespace.html -->
        <module name="FileTabCharacter">
            <property name="eachLine" value="true"/>
        </module>

    <module name="TreeWalker">
        <module name="OuterTypeFilename"/>
        <module name="IllegalTokenText">
            <property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
            <property name="format" value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
            <property name="message" value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
        </module>
        <module name="AvoidEscapedUnicodeCharacters">
            <property name="allowEscapesForControlCharacters" value="true"/>
            <property name="allowByTailComment" value="true"/>
            <property name="allowNonPrintableEscapes" value="true"/>
        </module>
        <module name="LineLength">
            <property name="max" value="100"/>
            <property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
        </module>
        <module name="AvoidStarImport"/>
        <module name="OneTopLevelClass"/>
        <module name="NoLineWrap"/>
        <module name="EmptyBlock">
            <property name="option" value="TEXT"/>
            <property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
        </module>
        <module name="NeedBraces"/>
        <module name="LeftCurly">
            <property name="maxLineLength" value="100"/>
        </module>
        <module name="RightCurly">
            <property name="id" value="RightCurlySame"/>
            <property name="tokens" value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_DO"/>
        </module>
        <module name="RightCurly">
            <property name="id" value="RightCurlyAlone"/>
            <property name="option" value="alone"/>
            <property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT, INSTANCE_INIT"/>
        </module>
        <module name="WhitespaceAround">
            <property name="allowEmptyConstructors" value="true"/>
            <property name="allowEmptyMethods" value="true"/>
            <property name="allowEmptyTypes" value="true"/>
            <property name="allowEmptyLoops" value="true"/>
            <message key="ws.notFollowed"
             value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
             <message key="ws.notPreceded"
             value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
        </module>
        <module name="OneStatementPerLine"/>
        <module name="MultipleVariableDeclarations"/>
        <module name="ArrayTypeStyle"/>
        <module name="MissingSwitchDefault"/>
        <module name="FallThrough"/>
        <module name="UpperEll"/>
        <module name="ModifierOrder"/>
        <module name="EmptyLineSeparator">
            <property name="allowNoEmptyLineBetweenFields" value="true"/>
        </module>
        <module name="SeparatorWrap">
            <property name="id" value="SeparatorWrapDot"/>
            <property name="tokens" value="DOT"/>
            <property name="option" value="nl"/>
        </module>
        <module name="SeparatorWrap">
            <property name="id" value="SeparatorWrapComma"/>
            <property name="tokens" value="COMMA"/>
            <property name="option" value="EOL"/>
        </module>
        <module name="PackageName">
            <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
            <message key="name.invalidPattern"
             value="Package name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="TypeName">
            <message key="name.invalidPattern"
             value="Type name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="MemberName">
            <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
            <message key="name.invalidPattern"
             value="Member name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="ParameterName">
            <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
            <message key="name.invalidPattern"
             value="Parameter name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <!-- <module name="CatchParameterName">
            <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
            <message key="name.invalidPattern"
             value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
        </module> -->
        <module name="LocalVariableName">
            <property name="tokens" value="VARIABLE_DEF"/>
            <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
            <message key="name.invalidPattern"
             value="Local variable name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="ClassTypeParameterName">
            <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
            <message key="name.invalidPattern"
             value="Class type name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="MethodTypeParameterName">
            <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
            <message key="name.invalidPattern"
             value="Method type name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="InterfaceTypeParameterName">
            <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
            <message key="name.invalidPattern"
             value="Interface type name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="NoFinalizer"/>
        <module name="GenericWhitespace">
            <message key="ws.followed"
             value="GenericWhitespace ''{0}'' is followed by whitespace."/>
             <message key="ws.preceded"
             value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
             <message key="ws.illegalFollow"
             value="GenericWhitespace ''{0}'' should followed by whitespace."/>
             <message key="ws.notPreceded"
             value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
        </module>
        <module name="Indentation">
            <property name="basicOffset" value="2"/>
            <property name="braceAdjustment" value="0"/>
            <property name="caseIndent" value="2"/>
            <property name="throwsIndent" value="4"/>
            <property name="lineWrappingIndentation" value="4"/>
            <property name="arrayInitIndent" value="2"/>
        </module>
        <module name="AbbreviationAsWordInName">
            <property name="ignoreFinal" value="false"/>
            <property name="allowedAbbreviationLength" value="1"/>
        </module>
        <module name="OverloadMethodsDeclarationOrder"/>
        <module name="VariableDeclarationUsageDistance"/>
        <module name="CustomImportOrder">
            <property name="sortImportsInGroupAlphabetically" value="true"/>
            <property name="separateLineBetweenGroups" value="true"/>
            <property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
        </module>
        <module name="MethodParamPad"/>
        <module name="ParenPad"/>
        <!-- <module name="OperatorWrap">
            <property name="option" value="NL"/>
            <property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/>
        </module> -->
        <module name="AnnotationLocation">
            <property name="id" value="AnnotationLocationMostCases"/>
            <property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
        </module>
        <module name="AnnotationLocation">
            <property name="id" value="AnnotationLocationVariables"/>
            <property name="tokens" value="VARIABLE_DEF"/>
            <property name="allowSamelineMultipleAnnotations" value="true"/>
        </module>
        <module name="NonEmptyAtclauseDescription"/>
        <module name="JavadocTagContinuationIndentation"/>
        <module name="SummaryJavadoc">
            <property name="forbiddenSummaryFragments" value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
        </module>
        <module name="JavadocParagraph"/>
        <module name="AtclauseOrder">
            <property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
            <property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
        </module>
        <module name="JavadocMethod">
            <property name="scope" value="public"/>
            <property name="allowMissingParamTags" value="true"/>
            <property name="allowMissingThrowsTags" value="true"/>
            <property name="allowMissingReturnTag" value="true"/>
            <property name="minLineCount" value="2"/>
            <property name="allowedAnnotations" value="Override, Test"/>
            <property name="allowThrowsTagsForSubclasses" value="true"/>
        </module>
        <module name="MethodName">
            <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
            <message key="name.invalidPattern"
             value="Method name ''{0}'' must match pattern ''{1}''."/>
        </module>
        <module name="SingleLineJavadoc">
            <property name="ignoreInlineTags" value="false"/>
        </module>
        <module name="EmptyCatchBlock">
            <property name="exceptionVariableName" value="expected"/>
        </module>
        <module name="CommentsIndentation"/>
    </module>
</module>

gradle中的exclude文件比较好写,但是include需要我们只对修改的文件进行检查,否则每次全工程检查,会花费大量的时间。

我们知道 git status -s能够得到修改过的文件字符串,因此我们在gradle中调用该指令,获取修改过的文件列表。

 def getChangeFiles() {
    try {
        String changeInfo = 'git status -s'.execute(null, project.rootDir).text.trim()
        return changeInfo == null ? "" : changeInfo
    } catch (Exception e) {
        return ""
    }
}

然后再解析这个字符串,就可以得到修改过的java文件类名集合。实现函数如下:

def filterCommitter(String gitstatusinfo) {
    ArrayList<String> filterList = new ArrayList<String>();
    String[] lines = gitstatusinfo.split("\\n")
    for (String line : lines) {
        if (line.contains(".java")) {
            String[] spliters = line.trim().split(" ");
            for (String str : spliters) {
                if (str.contains(".java")) {
                    filterList.add(str)
                }
            }
        }
    }
    return filterList;
}

这样我们再在gradle里面,将上述的java文件include进去就可以,就可以实现用gradle task只对修改过的java文件做checkstyle了。
下面贴上gradle完整的代码

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
        mavenCentral()
    }
    apply plugin: 'checkstyle'

    checkstyle {
        toolVersion '6.13'
        ignoreFailures false
        showViolations true
    }


}

task clean(type: Delete) {
    delete rootProject.buildDir
}


task checkstyle(type: Checkstyle) {
    source 'app/src/main/java'

    exclude '**/gen/**'
    exclude '**/R.java'
    exclude '**/BuildConfig.java'
    if (project.hasProperty('checkCommit') && project.property("checkCommit")) {
        def ft = filterCommitter(getChangeFiles());
        def includeList = new ArrayList<String>()
        for (int i = 0; i < ft.size(); i++) {
            String spliter = ft.getAt(i)
            String[] spliterlist = spliter.split("/")
            String fileName = spliterlist[spliterlist.length - 1]
            includeList.add("**/" + fileName)
        }
        if (includeList.size() == 0) {
            exclude '**/*.java'
        } else {
            println("includeList=="+includeList)
            include includeList
        }
    } else {
        include '**/*.java'
    }
    configFile rootProject.file('checkstyle.xml')
    classpath = files()
}


def getChangeFiles() {
    try {
        String changeInfo = 'git status -s'.execute(null, project.rootDir).text.trim()
        return changeInfo == null ? "" : changeInfo
    } catch (Exception e) {
        return ""
    }
}

def filterCommitter(String gitstatusinfo) {
    ArrayList<String> filterList = new ArrayList<String>();
    String[] lines = gitstatusinfo.split("\\n")
    for (String line : lines) {
        if (line.contains(".java")) {
            String[] spliters = line.trim().split(" ");
            for (String str : spliters) {
                if (str.contains(".java")) {
                    filterList.add(str)
                }
            }
        }
    }
    return filterList;
}


gradle文件添加了checkstyle的task之后,我们还需要做到git commit时自动检查,这时就需要修改git hook了。
我们去除./git/hook/pre-commit文件的后缀名.sample,并在里面调用gradle task,并以task的输出结果判断是否可以commit。代码如下:

#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments.  The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".

if git rev-parse --verify HEAD >/dev/null 2>&1
then
    against=HEAD
else
    # Initial commit: diff against an empty tree object
    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

SCRIPT_DIR=$(dirname "$0")
SCRIPT_ABS_PATH=`cd "$SCRIPT_DIR"; pwd`
$SCRIPT_ABS_PATH/../../gradlew  -PcheckCommit="true" checkstyle 
if [ $? -eq 0   ]; then
    echo "checkstyle OK"
else
    exit [[ $ERROR_INFO =~ "checkstyle" ]] && exit 1  
fi


# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --bool hooks.allownonascii)

# Redirect output to stderr.
exec 1>&2

# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
    # Note that the use of brackets around a tr range is ok here, (it's
    # even required, for portability to Solaris 10's /usr/bin/tr), since
    # the square bracket bytes happen to fall in the designated range.
    test $(git diff --cached --name-only --diff-filter=A -z $against |
      LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
    cat <<\EOF
Error: Attempt to add a non-ASCII file name.

This can cause problems if you want to work with people on other platforms.

To be portable it is advisable to rename the file.

If you know what you are doing you can disable this check using:

  git config hooks.allownonascii true
EOF
    exit 1
fi

# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --

这样,我们在通过git提交代码时,就能对修改过的代码进行检查,看是否符合我们规定的风格(checkstyle.xml)

最后感谢以下两位给我提供的思路

Git进阶:Hooks 实例把控代码提交时的备注

Android项目git+gradle实现commit时checkstyle检查

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容