项目组多人协做进行项目开发时,常常遇到以下状况:如Git Commit
信息混乱,又如提交者信息用了本身非公司的私人邮箱等等。所以,有必要在Git
操做过程当中的适当时间点上,进行必要的如统一规范、安全检测等常规性的例行检测。java
面对此类需求,Git
为咱们提供了Git Hooks
机制。在每一个项目根目录下,都存在一个隐藏的.git
目录,目录中除了Git
自己的项目代码版本控制之外,还带有一个名为hooks
的目录,默认状况下,内置了经常使用的一些Git Hooks
事件检测模板,并以.sample
结尾,其内部对应的是shell
脚本。实际使用时,须要将.sample
结尾去掉,且对应的脚本能够是其余类型,如你们用的比较多的python
等。python
顾名思义,Git Hooks
称之为Git 钩子
,意指在进行Git
操做时,会对应触发相应的钩子
,相似于写代码时在特定时机用到的回调。这样,就能够在钩子
中进行一些逻辑判断,如实现你们常见的Git Commit Message
规范等,以及其余相对比较复杂的逻辑处理等。android
多人协做的项目开发,即使已经实现了Git Hooks
,但因为此目录并不是属于Git
版本管理,所以也不能直接达到项目组成员公共使用并直接维护的目的。git
那么,是否能够有一种机制,能够间接的将其归入到Git
项目版本管理的范畴,从而能够全组通用,且能直接维护?shell
答案是能够的。api
对于Android
项目开发,经过利用自定义的Gradle Plugin
插件,能够达到这一目的。安全
项目中应用自定义的Gradle Plugin
,并在Gradle Plugin
中处理好对应的Git Hooks
文件的逻辑。后续须要维护时,也只须要修改对应的Gradle Plugin
便可。bash
下面主要经过实例展现具体的完整过程,以达到以下两个目的:
1,统一规范Git Commit
时的message
格式,在不符合规范要求的状况下commit
失败;
2,统一规范Git Commit
提交者的邮箱,只能使用公司的邮箱,具体经过检测邮箱后缀实现。app
具体过程以下:
1,新建对应的Git
工程,包含默认的app
示例应用模块。
2,新建模块,命名为buildSrc
,此模块主要是真正的实现自定的插件。此模块名称不可修改(由于此独立项目构建时,会将buildSrc
命名的模块自动加入到构建过程,这样,app
模块中只须要直接apply plugin
对应的插件名称便可)。
3,自定义插件,实现主体逻辑。
buildSrc
模块主要目录结果以下:ide
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
,让咱们实现了本来不属于项目版本管理范畴的逻辑整合和同步,从而能够实现整个项目组通用性的规范和易维护及扩展性的方案,不失为一种有效策略。