背景

项目组多人协作进行项目开发时,经常遇到如下情况:如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
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

  1. [git hooks] pre-commit 配置

    在开发过程中,通常使用 eslint 来规范团队的代码风格.但是 eslint 只能在开发服务器启动的时候才去检验代码.如果一个人在不启动开发服务器的情况下,修改了代码直接提交到git,那么别人pul ...

  2. Git Hooks、GitLab CI持续集成以及使用Jenkins实现自动化任务

    Git Hooks.GitLab CI持续集成以及使用Jenkins实现自动化任务 前言 在一个共享项目(或者说多人协同开发的项目)的开发过程中,为有效确保团队成员编码风格的统一,确保部署方式的统一, ...

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

    处理浏览器兼容问题实际上不是jQuery的精髓,毕竟让技术员想方设法取弥补浏览器的过错从而使得代码乱七八糟不是个好事.一些特殊情况的处理,完全实在浪费浏览器的性能:突兀的兼容解决使得的代码看起来既不美 ...

  4. U3D-页游-检测机制-webplayer-调试方法

    前言 页游目前有两个客户端入口: 网页端 (unity webplayer) 游戏微端 (unity standalone) 关于微端的技术,可参考我之前的文章: dotNet开发游戏微端 游戏微端的 ...

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

    原文地址:http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Advanced-Build-Customization ...

  6. 012-基于 git hooks 的前端代码质量控制解决方案

    原文看这里:https://github.com/kuitos/kui...全部文章看这里 https://github.com/kuitos/kui... 国际惯例先说下故事背景 通常情况下,如果我 ...

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

    [Android 修炼手册]Gradle 篇 -- Android Gradle Plugin 主要 Task 分析 预备知识 理解 gradle 的基本开发 了解 gradle task 和 plu ...

  8. Gradle之Android Gradle Plugin 主要流程分析(二)

    [Android 修炼手册]Gradle 篇 -- Android Gradle Plugin 主要流程分析 预备知识 理解 gradle 的基本开发 了解 gradle task 和 plugin ...

  9. Visual Studio 2013 always switches source control plugin to Git and disconnect TFS

      A few days ago, I've been facing a strange behavior with Visual Studio 2013.   No matter what solu ...

随机推荐

  1. Flask蓝图的增删改查

    怎样用flask蓝图来实现增删改查呢?请看下面的内容 这是我们的目录结构 从图中可以看出每一个功能都有一个各自的文件夹 首先我们要自己先来创建一个数据,在Flask_data.py中写入如下内容: S ...

  2. ABAP:从例子学习ABAP

    1.插入内表行: *插入内表行: DATA: BEGIN OF man, name(20) TYPE c, high TYPE p DECIMALS 2, weight TYPE p DECIMALS ...

  3. python 函数学习之sys.argv[1]

    一.sys 模块 sys是Python的一个「标准库」,也就是官方出的「模块」,是「System」的简写,封装了一些系统的信息和接口. 官方的文档参考:https://docs.python.org/ ...

  4. pta 编程题16 Saving James Bond - Easy Version

    其它pta数据结构编程题请参见:pta 题目 主要用到了深度优先搜索. #include <iostream> using namespace std; struct Vertex { i ...

  5. bit Byte KB MB GB TB 单位换算

    1TB = 1024G 1G = 1024M 1M = 1024K 1K = 1024 byte 1 byte = 8 bit

  6. ModelName标记的使用

    再项目中出现这样的错误: A model description could not be created. Duplicate model name 'ExamInfoParams' was fou ...

  7. 使用POI 读取 Excel 文件,读取手机号码 变成 1.3471022771E10

    使用POI 读取 Excel 文件,读取手机号码 变成 1.3471022771E10 [问题点数:40分,结帖人xieyongqiu]             不显示删除回复             ...

  8. Java后台调用gcc编译C语言代码

    想做一个能够在线编译代码运行的平台,Java和SQL已经支持了,因为是用Java写的后台,所以Java和SQL挺容易就实现了,做到支持C的时候就卡住了,网上搜了一下这种帖子好像很少. 我采取的办法是就 ...

  9. 关于多行文本 textarea 在ios 真机上padding相对安卓较大问题

    问题: 多行文本组件是带有默认的padding的,然而,小程序的teatarea 在ios和安卓上显示的padding不一样,普遍ios的padding会比安卓的要明显的大.这种情况下我的想法是做兼容 ...

  10. Mybatis 插入一条或批量插入 返回带有自增长主键记录

    首先讲一下,  插入一条记录返回主键的 Mybatis 版本要求低点,而批量插入返回带主键的 需要升级到3.3.1版本,3.3.0之前的都不行, <dependency> <grou ...