項目組多人協做進行項目開發時,常常遇到以下狀況:如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
,讓咱們實現了本來不屬於項目版本管理範疇的邏輯整合和同步,從而能夠實現整個項目組通用性的規範和易維護及擴展性的方案,不失爲一種有效策略。