zoukankan      html  css  js  c++  java
  • 通过Gradle Plugin实现Git Hooks检测机制

    背景

    项目组多人协作进行项目开发时,经常遇到如下情况:如Git Commit信息混乱,又如提交者信息用了自己非公司的私人邮箱等等。因此,有必要在Git操作过程中的适当时间点上,进行必要的如统一规范、安全检测等常规性的例行检测。

    面对此类需求,Git为我们提供了Git Hooks机制。在每个项目根目录下,都存在一个隐藏的.git目录,目录中除了Git本身的项目代码版本控制以外,还带有一个名为hooks的目录,默认情况下,内置了常用的一些Git Hooks事件检测模板,并以.sample结尾,其内部对应的是shell脚本。实际使用时,需要将.sample结尾去掉,且对应的脚本可以是其他类型,如大家用的比较多的python等。

    顾名思义,Git Hooks称之为Git 钩子,意指在进行Git操作时,会对应触发相应的钩子,类似于写代码时在特定时机用到的回调。这样,就可以在钩子中进行一些逻辑判断,如实现大家常见的Git Commit Message规范等,以及其他相对比较复杂的逻辑处理等。

    多人协作的项目开发,即便已经实现了Git Hooks,但由于此目录并非属于Git版本管理,因此也不能直接达到项目组成员公共使用并直接维护的目的。

    那么,是否可以有一种机制,可以间接的将其纳入到Git项目版本管理的范畴,从而可以全组通用,且能直接维护?

    答案是可以的。

    对于Android项目开发,通过利用自定义的Gradle Plugin插件,可以达到这一目的。


    实现

    项目中应用自定义的Gradle Plugin,并在Gradle Plugin中处理好对应的Git Hooks文件的逻辑。后续需要维护时,也只需要修改对应的Gradle Plugin即可。

    下面主要通过实例展示具体的完整过程,以达到如下两个目的:
    1,统一规范Git Commit时的message格式,在不符合规范要求的情况下commit失败;
    2,统一规范Git Commit提交者的邮箱,只能使用公司的邮箱,具体通过检测邮箱后缀实现。

    具体过程如下:
    1,新建对应的Git工程,包含默认的app示例应用模块。
    2,新建模块,命名为buildSrc,此模块主要是真正的实现自定的插件。此模块名称不可修改(因为此独立项目构建时,会将buildSrc命名的模块自动加入到构建过程,这样,app模块中只需要直接apply plugin对应的插件名称即可)。
    3,自定义插件,实现主体逻辑。
    buildSrc模块主要目录结果如下:

    buildSrc
    |____libs
    |____build.gradle
    |____src
    | |____main
    | | |____resources
    | | | |____META-INF
    | | | | |____gradle-plugins
    | | | | | |____Git-Hooks-Plugin.properties
    | | | |____commit-msg
    | | |____groovy
    | | | |____com
    | | | | |____corn
    | | | | | |____githooks
    | | | | | | |____GitHooksUtil.groovy
    | | | | | | |____GitHooksExtension.groovy
    | | | | | | |____GitHooksPlugin.groovy
    复制代码

    GitHooksPlugin实现:

    package com.corn.githooks
    
    import org.gradle.api.GradleException
    import org.gradle.api.Plugin
    import org.gradle.api.Project
    
    class GitHooksPlugin implements Plugin<Project> {
    
            @Override
            void apply(Project project) {
                project.extensions.create(GitHooksExtension.NAME, GitHooksExtension, project)
    
    
                project.afterEvaluate {
                    GitHooksExtension gitHooksExtension = project.extensions.getByName(GitHooksExtension.NAME)
    
                    if (!GitHooksUtil.checkInstalledPython(project)) {
                        throw new GradleException("GitHook require python env, please install python first!", e)
                    }
    
                    File gitRootPathFile = GitHooksUtil.getGitHooksPath(project, gitHooksExtension)
                    if (!gitRootPathFile.exists()) {
                        throw new GradleException("Can't found project git root file, please check your gitRootPath config value")
                    }
    
                    GitHooksUtil.saveHookFile(gitRootPathFile.absolutePath, "commit-msg")
    
                    File saveConfigFile = new File(gitRootPathFile.absolutePath + File.separator + "git-hooks.conf")
    
                    saveConfigFile.withWriter('utf-8') { writer ->
                        writer.writeLine '## 程序自动生成,请勿手动改动此文件!!! ##'
                        writer.writeLine '[version]'
                        writer.writeLine "v = ${GitHooksExtension.VERSION}"
                        writer.writeLine '
    '
                        if (gitHooksExtension.commit != null) {
                            writer.writeLine '[commit-msg]'
                            writer.writeLine "cm_regex=${gitHooksExtension.commit.regex}"
                            writer.writeLine "cm_doc_url=${gitHooksExtension.commit.docUrl}"
                            writer.writeLine "cm_email_suffix=${gitHooksExtension.commit.emailSuffix}"
                        }
                    }
                }
        }
    }
    复制代码

    对应的GitHooksExtension扩展为:

    package com.corn.githooks
    
    import org.gradle.api.Project
    
    class GitHooksExtension {
    
        public static final String NAME = "gitHooks"
        public static final String VERSION = "v1.0"
    
        private Project project
    
        String gitRootPath
        Commit commit
    
        GitHooksExtension(Project project) {
            this.project = project
        }
    
        def commit(Closure closure) {
            commit = new Commit()
            project.configure(commit, closure)
        }
    
    
        class Commit {
            // commit规范正则
            String regex = ''
            // commit规范文档url
            String docUrl = ''
            String emailSuffix = ''
    
            void regex(String regex) {
                this.regex = regex
            }
    
            void docUrl(String docUrl) {
                this.docUrl = docUrl
            }
    
            void emailSuffix(String emailSuffix){
                this.emailSuffix = emailSuffix
            }
        }
    }
    复制代码

    GitHooksUtil工具类:

    package com.corn.githooks
    
    import org.gradle.api.GradleException
    import org.gradle.api.Project
    import org.gradle.process.ExecResult
    
    import java.nio.file.Files
    
    class GitHooksUtil {
    
        static File getGitHooksPath(Project project, GitHooksExtension config) {
            File configFile = new File(config.gitRootPath)
            if (configFile.exists()) {
                return new File(configFile.absolutePath + File.separator + ".git" + File.separator + "hooks")
            }
            else {
                return new File(project.rootProject.rootDir.absolutePath + File.separator + ".git" + File.separator + "hooks")
            }
        }
    
        static void saveHookFile(String gitRootPath, String fileName) {
            InputStream is = null
            FileOutputStream fos = null
    
            try {
                is = GitHooksUtil.class.getClassLoader().getResourceAsStream(fileName)
                File file = new File(gitRootPath + File.separator + fileName)
                file.setExecutable(true)
    
                fos = new FileOutputStream(file)
                Files.copy(is, fos)
    
                fos.flush()
            } catch (Exception e) {
                throw new GradleException("Save hook file failed, file: " + gitRootPath + " e:" + e, e)
            } finally {
                closeStream(is)
                closeStream(fos)
            }
        }
    
        static void closeStream(Closeable closeable) {
            if(closeable == null) {
                return
            }
    
            try {
                closeable.close()
            } catch (Exception e) {
                // ignore Exception
            }
        }
    
    
        static boolean checkInstalledPython(Project project) {
            ExecResult result
            try {
                result = project.exec {
                    executable 'python'
                    args '--version'
                }
            } catch (Exception e) {
                e.printStackTrace()
            }
    
            return result != null && result.exitValue == 0
        }
    }
    复制代码

    resources目录中,META-INF.gradle-plugins实现对Gradle Plugin的配置,文件Git-Hooks-Plugin.properties文件名前缀Git-Hooks-Plugin表示插件名,对应的implementation-class指定插件的实际实现类。

    |____resources
    | | | |____META-INF
    | | | | |____gradle-plugins
    | | | | | |____Git-Hooks-Plugin.properties
    
    --------------------------------------------
    implementation-class=com.corn.githooks.GitHooksPlugin
    
    复制代码

    commit-msg文件直接放到resources目录中,通过代码拷贝到指定的Git Hooks目录下。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    import sys
    import re
    import os
    
    if sys.version > '3':
        PY3 = True
        import configparser
    else:
        PY3 = False
        import ConfigParser as configparser
        reload(sys)
        sys.setdefaultencoding('utf8')
    
    argvs = sys.argv
    # print(argvs)
    commit_message_file = open(sys.argv[1])
    commit_message = commit_message_file.read().strip()
    
    CONFIG_FILE = '.git' + os.path.sep + 'hooks' + os.path.sep + 'git-hooks.conf'
    
    config = configparser.ConfigParser()
    config.read(CONFIG_FILE)
    
    if not config.has_section('commit-msg'):
        print('未找到配置文件: ' + CONFIG_FILE)
        sys.exit(1)
    
    cm_regex = str(config.get('commit-msg', 'cm_regex')).strip()
    cm_doc_url = str(config.get('commit-msg', 'cm_doc_url')).strip()
    cm_email_suffix = str(config.get('commit-msg', 'cm_email_suffix')).strip()
    
    ret = os.popen('git config user.email', 'r').read().strip()
    
    if not ret.endswith(cm_email_suffix):
        print ('===============================  Commit Error ====================================')
        print ('==> Commit email格式出错,请将git config中邮箱设置为标准邮箱格式,公司邮箱后缀为:' + cm_email_suffix)
        print ('==================================================================================
    ')
        commit_message_file.close()
        sys.exit(1)
    
    # 匹配规则, Commit 要以如下规则开始
    if not re.match(cm_regex, commit_message):
        print ('===============================  Commit Error ====================================')
        print ('==> Commit 信息写的不规范 请仔细参考 Commit 的编写规范重写!!!')
        print ('==> 匹配规则: ' + cm_regex)
        if cm_doc_url:
            print ('==> Commit 规范文档: ' + cm_doc_url)
        print ('==================================================================================
    ')
        commit_message_file.close()
        sys.exit(1)
    commit_message_file.close()
    复制代码

    至此,buildSrc模块插件部分已经完成。

    4,app应用模块中应用插件,并测试效果。 app应用模块的build.gralde文件应用插件,并进行相应配置。

    app模块build.gralde相应配置:
    ----------------------------------------
    apply plugin: 'com.android.application'
    
    ....
    ....
    
    apply plugin: 'Git-Hooks-Plugin'
    
    gitHooks {
    
        gitRootPath rootProject.rootDir.absolutePath
    
        commit {
            // git commit 强制规范
            regex "^(新增:|特性:|:合并:|Lint:|Sonar:|优化:|Test:|合版:|发版:|Fix:|依赖库:|解决冲突:)"
            // 对应提交规范具体说明文档
            docUrl "http://xxxx"
    
            // git commit 必须使用公司邮箱
            emailSuffix "@corn.com"
        }
    
    }
    
    ....
    ....
    
    复制代码

    应用插件后,来到项目工程的.git/hooks/目录,查看是否有对应的commit-msggit-hooks.conf文件生成,以及对应的脚本逻辑和配置是否符合预期,并实际提交项目代码,分别模拟commit messagegit config email场景,测试结果是否与预期一致。


    结语

    本文主要通过demo形式演示基于Gradle Plugin插件形式实现Git Hooks检测机制,以达到项目组通用及易维护的实际实现方案,实际主工程使用时,只需要将此独立独立Git工程中的buildSrc模块,直接发布到marven,主工程在buildscriptdependencies中配置上对应的插件classpath即可。其他跟上述示例中的app应用模块一样,直接应用插件并对应配置即可使用。

    通过Gradle Plugin,让我们实现了原本不属于项目版本管理范畴的逻辑整合和同步,从而可以实现整个项目组通用性的规范和易维护及扩展性的方案,不失为一种有效策略。


    作者:HappyCorn
    链接:https://juejin.im/post/5cce5df26fb9a031ee3c2355
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    Create data packages for the data import in Dynamics 365 Finance and Operations from Invoice PDFs
    Tutorial: Create an ASP.NET Core Blazor WebAssembly app using Microsoft Dataverse
    Tutorial: Register an app with Azure Active Directory
    WPS JSA 宏编程(JS):7.用户窗体
    OSCP Security Technology
    OSCP Security Technology
    select * 和select 1 以及 select count(*) 和select count(1)的区别
    你只会用 map.put?试试 Java 8 compute ,操作 Map 更轻松!
    SpringBoot+Vue实现第三方QQ登录(二)
    SpringBoot+Vue实现第三方QQ登录(一)
  • 原文地址:https://www.cnblogs.com/lwbqqyumidi/p/10827960.html
Copyright © 2011-2022 走看看