通過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-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
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM