經過Gradle Plugin實現Git Hooks檢測機制

背景

項目組多人協做進行項目開發時,常常遇到以下狀況:如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-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 來源:掘金 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
相關文章
相關標籤/搜索