git 的 hook 操做

1、git hook

和其它版本控制系統同樣,Git 能在特定的重要動做發生時觸發自定義腳本。有兩組這樣的鉤子:客戶端鉤子和服務器鉤子。客戶端鉤子由諸如提交和合並這樣的操做所調用,而服務器端鉤子做用於諸如接收被推送的提交這樣的聯網操做。python

鉤子都被存儲在 Git 目錄下的 hooks 子目錄中。 也即絕大部分項目中的 .git/hooks,默認存在的都是示例,其名字都是以 .sample 結尾,若是你想啓用它們,得先移除這個後綴。把一個正確命名且可執行的文件放入 Git 目錄下的 hooks 子目錄中,便可激活該鉤子腳本。這樣一來,它就能被 Git 調用。android

關於各類詳細的 hook 類型能夠參考官方文檔 《自定義 Git - Git 鉤子》git

瞭解了這些 hook 鉤子,你就能夠真的隨心所欲了,你能夠用來檢查消息、檢查代碼,能夠用來觸發任意流程,譬如自動規範檢查等等,只能說想象空間巨大無比。服務器

2、commit msg 格式自動檢查

雖然有不少現成的 hook 可用,可是這裏仍是給出一個簡單的例子演示下,這裏實現一個提交 message 格式的簡單檢查,要求提交消息單行且分兩部分,且有必定的字數限制。app

2.1 編寫 commit-msg 腳本

#!/usr/bin/env python
# coding=utf-8
#
# commit msg check
import sys
import re
import io

if hasattr(sys.stdout, 'buffer'):
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

TIPS_INFO = '''
不符合commit規範,提交失敗(當前狀態等於沒作剛剛的commit操做)!

commit規範:
類型 詳細消息

規範樣例:
git commit -m "xxxxx xxxxxxxxxxxxx"

!!!!提交失敗!!!!
'''

def check_commit_line1_format(msg):
    regOther = r'\S{5,} (.){10,100}'
    matchObj = re.match(regOther, msg)
    return matchObj

if __name__=="__main__":
    with open(sys.argv[1], 'r') as f:
        for line in f:
            if (check_commit_line1_format(line)):
                sys.exit(0)
            else:
                print(TIPS_INFO)
                sys.exit(1)

既然編寫好了 commit hook 腳本,那新問題就來了,本地 hook 在項目的 .git 目錄下,.git 目錄又不受版本控制,因此在團隊推廣時,你不可能讓你們主動去放這個文件,一方面可能會放錯,另外一方面可能有些人壓根就不放,愛理不理,故而須要將這件事作成自動的,因此選擇在編譯項目時爲拷貝切入點(由於你拽下來的項目必定會編譯的)。對於 android 項目來講,咱們可使用 gradle 編寫一個小任務來作這件事,具體以下:gradle

/**
 * git-hook-copy.gradle 文件
 * 
 * 本地項目 git hook 自動拷貝腳本
 * 用法:
 * apply from: 'git-hook-copy.gradle'
 */

/**
 * 緊急開關
 */
def forbid = false

project.afterEvaluate {
    if (forbid) {
        preBuild.dependsOn 'resetGitHookConfig'
    } else {
        preBuild.dependsOn 'prepareGitHookConfig'
    }
}

task prepareGitHookConfig(type: Copy) {
    from getConfigFile()
    into getGitHookDir()
}

task resetGitHookConfig {
    doFirst {
        File file = getGitHookFile('commit-msg')
        if (file != null) {
            file.delete()
        }
    }
}

def getGitHookFile(fileName) {
    def dirPath = getGitHookDir()
    if (dirPath != null && dirPath.length() > 0) {
        def file = new File(dirPath, fileName)
        if (file.exists()) {
            return file
        }
    }
    return null
}

def getConfigFile() {
    File configFile = new File(project.rootDir, "git-hook/commit-msg")
    if (configFile.exists()) {
        return configFile.absolutePath
    }
    return null
}

def getGitHookDir() {
    File gitHookDir = new File(project.rootDir, ".git/hooks/")
    if (!gitHookDir.exists()) {
        println("Your project can't find .git directory in the ${project.rootDir.absolutePath}," +
                " please ensure it have been tracked by git VCS!")
        return null
    }
    return gitHookDir.absolutePath
}

上面腳本有兩個任務,一個 reset,一個 config。reset 會將 .git hook 目錄下的規則刪掉,等於沒有規則;config 是把項目根目錄下 git-hook 目錄下的 commit-msg hook 腳本複製到 .git hook 目錄下,這裏不用判斷是否已經存在文件,直接覆蓋便可,由於 gradle task 天生支持 UPDATE 機制,並且咱們須要在修改 commit-msg 文件後自動覆蓋,因此不建議判斷 .git hook 下是否已經存在。ui

至此,咱們能夠進行一波操做了,譬如在命令行提交代碼,你會看到以下提示:
lua

修復 log 格式後再進行 commit 便可。命令行

3、總結

上面簡單介紹和實戰了一個小的 git hook 操做,感興趣你能夠無限想象,和你的 checkstyle 什麼的,各類檢查什麼的結合起來均可以,反正師傅領進門,修行靠本身,需求靠團隊。版本控制



參考連接:

相關文章
相關標籤/搜索