通过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 ‘\n‘
                    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 (‘==================================================================================\n‘)
    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 (‘==================================================================================\n‘)
    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
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

原文地址:https://www.cnblogs.com/lwbqqyumidi/p/10827960.html

时间: 2024-10-07 03:44:58

通过Gradle Plugin实现Git Hooks检测机制的相关文章

Android Gradle Plugin指南(六)——高级构建定制

原文地址:http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Advanced-Build-Customization 7. Advanced Build Customization(高级构建定制) 7.1 Build options(构建选项) 7.1.1 Java Compilation options(Java编译选项) android { compileOptions { sourceCompatibili

jQuery-1.9.1源码分析系列(七) 钩子(hooks)机制及浏览器兼容

处理浏览器兼容问题实际上不是jQuery的精髓,毕竟让技术员想方设法取弥补浏览器的过错从而使得代码乱七八糟不是个好事.一些特殊情况的处理,完全实在浪费浏览器的性能:突兀的兼容解决使得的代码看起来既不美观也也不能对前端技术有任何提升.但是不管怎么说,只要不同的浏览器存在,就有可能出现兼容性问题,我们还必须去解决.比较好的是jQuery提供了一些比较优雅的浏览器兼容方案. 在处理浏览器兼容问题的时候最没有技术含量的方式是if…else..分支判断.jQuery中用到很多处理兼容的方法:多用于普通兼容

Android 源码系列之&lt;十三&gt;从源码的角度深入理解LeakCanary的内存泄露检测机制(中)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52958563 在上篇文章Android 源码系列之<十二>从源码的角度深入理解LeakCanary的内存泄露检测机制(上)中主要介绍了Java内存分配相关的知识以及在Android开发中可能遇见的各种内存泄露情况并给出了相对应的解决方案,如果你还没有看过上篇文章,建议点击这里阅读一下,这篇文章我将要向大家介绍如何在我们的应用中使用square开源的LeakCanary库来检测应用中出

Git工程开发实践(二)——Git内部实现机制

Git工程开发实践(二)--Git内部实现机制 一.Git仓库内部实现简介 Git本质上是一个内容寻址(content-addressable)的文件系统,根据文件内容的SHA-1哈希值来定位文件.Git核心部分是一个简单的键值对数据库(key-value data store).向Git数据库插入任意类型的内容,会返回一个键值,通过返回的键值可以在任意时刻再次检索(retrieve)插入的内容.通过底层命令hash-object可以将任意数据保存到.git目录并返回相应的键值.Git包含一套面

Gradle之Android Gradle Plugin 主要 Task 分析(三)

[Android 修炼手册]Gradle 篇 -- Android Gradle Plugin 主要 Task 分析 预备知识 理解 gradle 的基本开发 了解 gradle task 和 plugin 使用及开发 了解 android gradle plugin 的使用 看完本文可以达到什么程度 了解 android gradle plugin 中各个 task 作用 了解 android gradle plugin 中主要 task 的实现 阅读前准备工作 1.项目添加 android

TCP 连接与TCP keep alive 保活检测机制

生产环境中一台2核4G的linux服务器TCP连接数时常保持在5-7w间徘徊,查看日志每秒的请求数也就100-200,怎么会产生这么大的TCP连接数.检查了下客户端上行的HTTP协议,Connection 头字段是Keep-Alive,并且客户端在请求完之后没有立即关闭连接.而服务端的设计也是根据客户端来的,客户端上行如果Connection:Keep-Alive,服务端是不会主动关闭连接的.在客户端与服务端交互比较频繁的时候,这样的设计还是比较合理的,可以减少TCP的重复握手.显然如果只交互一

gradle wrapper, gradle ,gradle plugin 之间的关系

gradle 是 Android studio 中很重要应用. gradle wrapper 管理 gradle 版本的工具,如果没有,它会从提定网址下载相应版本 gradle. gradle 一般在 c:\ 个人用户\gradle 下面. gradle 用Groovy 语法表示,类似于java. Gradle 各个版本之间兼容性不好, 目前最高版本3.3 gradle plugin 是gradle 基础上写的插件,最重要的两个插件, apply plugin: 'com.android.app

Android Gradle Plugin指南(五)——Build Variants(构建变种版本)

原文地址:http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 6. Build Variants(构建变种版本) 新构建系统的一个目标就是允许为同一个应用创建不同的版本. 这里有两个主要的使用情景: 1.同一个应用的不同版本.例如一个免费的版本和一个收费的专业版本. 2.同一个应用需要打包成不同的apk以发布Google Play Store.查看http://developer.an

Android 源码系列之&lt;十四&gt;从源码的角度深入理解LeakCanary的内存泄露检测机制(下)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52958567 在上边文章Android 源码系列之<十三>从源码的角度深入理解LeakCanary的内存泄露检测机制(中)由于篇幅原因仅仅向小伙伴们讲述了在Android开发中如何使用LeakCanary来检测应用中出现的内存泄露,并简单的介绍了LeakCanary的相关配置信息.根据上篇文章的介绍我们知道LeakCanary为了不给APP进程造成影响所以新开启了一个进程,在新开启的