背景
專案組多人協作進行專案開發時,經常遇到如下情況:如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
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。