和其它版本控制系統同樣,Git 能在特定的重要動做發生時觸發自定義腳本。有兩組這樣的鉤子:客戶端鉤子和服務器鉤子。客戶端鉤子由諸如提交和合並這樣的操做所調用,而服務器端鉤子做用於諸如接收被推送的提交這樣的聯網操做。python
鉤子都被存儲在 Git 目錄下的 hooks 子目錄中。 也即絕大部分項目中的 .git/hooks,默認存在的都是示例,其名字都是以 .sample 結尾,若是你想啓用它們,得先移除這個後綴。把一個正確命名且可執行的文件放入 Git 目錄下的 hooks 子目錄中,便可激活該鉤子腳本。這樣一來,它就能被 Git 調用。android
關於各類詳細的 hook 類型能夠參考官方文檔 《自定義 Git - Git 鉤子》。git
瞭解了這些 hook 鉤子,你就能夠真的隨心所欲了,你能夠用來檢查消息、檢查代碼,能夠用來觸發任意流程,譬如自動規範檢查等等,只能說想象空間巨大無比。服務器
雖然有不少現成的 hook 可用,可是這裏仍是給出一個簡單的例子演示下,這裏實現一個提交 message 格式的簡單檢查,要求提交消息單行且分兩部分,且有必定的字數限制。app
#!/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 便可。命令行
上面簡單介紹和實戰了一個小的 git hook 操做,感興趣你能夠無限想象,和你的 checkstyle 什麼的,各類檢查什麼的結合起來均可以,反正師傅領進門,修行靠本身,需求靠團隊。版本控制
參考連接: