通过Gradle Plugin实现Git Hooks检测机制
2019-05-07 20:45
459 查看
背景
项目组多人协作进行项目开发时,经常遇到如下情况:如
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-msg及
git-hooks.conf文件生成,以及对应的脚本逻辑和配置是否符合预期,并实际提交项目代码,分别模拟
commit message和
git config email场景,测试结果是否与预期一致。
结语
本文主要通过demo形式演示基于
Gradle Plugin插件形式实现
Git Hooks检测机制,以达到项目组通用及易维护的实际实现方案,实际主工程使用时,只需要将此独立独立
Git工程中的
buildSrc模块,直接发布到
marven,主工程在
buildscript的
dependencies中配置上对应的插件
classpath即可。其他跟上述示例中的
app应用模块一样,直接应用插件并对应配置即可使用。
通过
Gradle Plugin,让我们实现了原本不属于项目版本管理范畴的逻辑整合和同步,从而可以实现整个项目组通用性的规范和易维护及扩展性的方案,不失为一种有效策略。
作者:HappyCorn
链接:https://juejin.im/post/5cce5df26fb9a031ee3c2355
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章推荐
- 通过Git WebHooks+脚本实现自动更新发布代码之shell脚本
- 通过Git WebHooks+脚本实现自动更新发布代码
- 通过Git WebHooks+脚本实现自动更新发布代码之Shell脚本(二)
- 通过Git WebHooks+脚本实现自动更新发布代码之shell脚本
- 通过Git WebHooks+脚本实现自动更新发布代码之shell脚本
- 通过Git WebHooks+脚本实现自动更新发布代码之shell脚本
- Lua面向对象编程系列-通过Table和Closure来实现类机制
- 通过反射实现Java下的委托机制代码详解
- Cordova2.0(既PhoneGap)中通过Plugin实现Java和JS代码互相调用
- 通过检测png图片透明区实现不规则按钮
- 微信域名检测 腾讯微信域名检测的机制原理以及实现方式
- Android开发之手势检测及通过手势实现翻页功能的方法
- 通过php反射机制实现栏目显示
- 通过maven-weblogic-plugin实现weblogic的自动部署
- Android应用中通过AIDL机制实现进程间的通讯实例
- Android应用中通过AIDL机制实现进程间的通讯实例
- git hooks 实现网站的自动部署
- [cocos2d-x][游戏开发]通过cocos2d-x实现简易飞机大战 08.游戏界面 碰撞检测
- Git 本身不支持对某一特定文件夹或者子目录的权限控制,但是可以通过子模块的形式间接实现
- 通过类静态成员变量 可以实现自动注册机制