通过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-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
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
通过Gradle Plugin实现Git Hooks检测机制的更多相关文章
- [git hooks] pre-commit 配置
在开发过程中,通常使用 eslint 来规范团队的代码风格.但是 eslint 只能在开发服务器启动的时候才去检验代码.如果一个人在不启动开发服务器的情况下,修改了代码直接提交到git,那么别人pul ...
- Git Hooks、GitLab CI持续集成以及使用Jenkins实现自动化任务
Git Hooks.GitLab CI持续集成以及使用Jenkins实现自动化任务 前言 在一个共享项目(或者说多人协同开发的项目)的开发过程中,为有效确保团队成员编码风格的统一,确保部署方式的统一, ...
- jQuery-1.9.1源码分析系列(七) 钩子(hooks)机制及浏览器兼容
处理浏览器兼容问题实际上不是jQuery的精髓,毕竟让技术员想方设法取弥补浏览器的过错从而使得代码乱七八糟不是个好事.一些特殊情况的处理,完全实在浪费浏览器的性能:突兀的兼容解决使得的代码看起来既不美 ...
- U3D-页游-检测机制-webplayer-调试方法
前言 页游目前有两个客户端入口: 网页端 (unity webplayer) 游戏微端 (unity standalone) 关于微端的技术,可参考我之前的文章: dotNet开发游戏微端 游戏微端的 ...
- Android Gradle Plugin指南(六)——高级构建定制
原文地址:http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Advanced-Build-Customization ...
- 012-基于 git hooks 的前端代码质量控制解决方案
原文看这里:https://github.com/kuitos/kui...全部文章看这里 https://github.com/kuitos/kui... 国际惯例先说下故事背景 通常情况下,如果我 ...
- Gradle之Android Gradle Plugin 主要 Task 分析(三)
[Android 修炼手册]Gradle 篇 -- Android Gradle Plugin 主要 Task 分析 预备知识 理解 gradle 的基本开发 了解 gradle task 和 plu ...
- Gradle之Android Gradle Plugin 主要流程分析(二)
[Android 修炼手册]Gradle 篇 -- Android Gradle Plugin 主要流程分析 预备知识 理解 gradle 的基本开发 了解 gradle task 和 plugin ...
- 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 ...
随机推荐
- .net core +mysqlSugar(最为简单的增删改查)
首先建立.net Core API - empty 这个就不说了 然后创建新的Controller 记得添加路由 [Route("api/Users")] 然后在Nuget Pac ...
- 使用JAVA读写Properties属性文件
使用JAVA读写Properties属性文件 Properties属性文件在JAVA应用程序中是经常可以看得见的,也是特别重要的一类文件.它用来配置应用程序的一些信息,不过这些信息一般都是比较少的数 ...
- Eucalyptus简介
1.Eucalyptus是什么? Eucalyptus n.桉树 Eucalyptus is a Linux-based software architecture that implements ...
- OpenFirewall
1.写一份json文件:将要添加防火墙例外的应用程序和端口写入到json文件中 2.打开防火墙,读取json文件添加例外 /// <summary> /// Firewall.xaml 的 ...
- COGS 1944. 背驮式行走
★ 输入文件:piggyback.in 输出文件:piggyback.out 简单对比时间限制:1 s 内存限制:256 MB [题目描述] Bessie和她妹妹Elsie白天都在牧场 ...
- UVA 12166 Equilibrium Mobile(贪心,反演)
直接贪心.先想想最后平衡的时候,如果知道了总重量,那么每一个结点的重量其实也就确定了. 每个结点在左在右其实都不影响,只和层数有关.现在反过来,如果不修改某个结点,那么就可以计算出总质量,取总质量出现 ...
- POJ-1936 All in All---字符串水题
题目链接: https://vjudge.net/problem/POJ-1936 题目大意: 给两个字符串,判断是s1是不是s2的子序列 思路: 水 #include<iostream> ...
- 【BZOJ3940】[USACO2015 Feb] Censoring (AC自动机的小应用)
点此看题面 大致题意: 给你一个文本串和\(N\)个模式串,要你将每一个模式串从文本串中删去.(此题是[BZOJ3942][Usaco2015 Feb]Censoring的升级版) \(AC\)自动机 ...
- 【洛谷1967】货车运输(最大生成树+倍增LCA)
点此看题面 大致题意: 有\(n\)个城市和\(m\)条道路,每条道路有一个限重.多组询问,每次询问从\(x\)到\(y\)的最大载重为多少. 一个贪心的想法 首先,让我们来贪心一波. 由于要求最大载 ...
- 2018.10.26 NOIP2018模拟赛 解题报告
得分: \(0+10+10=20\)(\(T1\)死于假题面,\(T3\)死于细节... ...) \(P.S.\)由于原题是图片,所以我没有上传题目描述,只有数据. \(T1\):颜料大乱斗(点此看 ...