golang实现gitlab commit注释校验hook
source link: https://studygolang.com/articles/14237?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
最近和项目成员约定了git commit规则,但是约定归约定,要保证大家都执行,还是需要程序来做些校验工作。
大致的约定如下:
comment 格式:<start|do|end>:#69 fix something bug
其中的start为在redmine版本管理中指定的关键字,具体参见redmine的”配置“ -> "版本库" -> "在提交信息中引用和解决问题" 中的配置。
废话不多说,直接上代码:
package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" "os" "os/exec" "regexp" "strconv" "strings" ) type COMMIT_TYPEstring const ( OK COMMIT_TYPE ="ok" //issue 完成 START COMMIT_TYPE ="start" //issue 开始 DOING COMMIT_TYPE ="doing" //issue 进行中 ) // 是否开启严格模式,严格模式下将校验所有的提交信息格式(多 commit 下) var commitMsgReg = regexp.MustCompile(COMMIT_MESSAGE_PATTERN) var USER2EMAIL =map[string]string{ "developer1's name":"developer1's email", } var WHITE_LIST = []string{ //"", } const ( ISSUE_STATUS_NEW ="1" ISSUE_STATUS_DOING ="2" ISSUE_STATUS_END ="3" ISSUE_STATUS_FEEDBACK ="4" ISSUE_STATUS_CLOSE ="5" ISSUE_STATUS_REJECTED ="6" ) var ISSUE_STATUS_STR =map[string]string{ "1":"NEW", "2":"DOING", "3":"RESOLVED", "4":"FEEDBACK", "5":"CLOSED", "6":"REJECTED", } const ( COMMENT_PREFIX_BEGIN ="begin:" COMMENT_PREFIX_END ="end:" COMMENT_PREFIX_DO ="do:" ) func main() { input, _ := ioutil.ReadAll(os.Stdin) //write2Log(string(input)) param := strings.Fields(string(input)) // allow branch/tag delete if param[0] =="0000000000000000000000000000000000000000" || param[1] =="0000000000000000000000000000000000000000" { os.Exit(0) } //write2Log(fmt.Sprintf("%v\n", param)) //commitMsg := getCommitMsg(param[0], param[1]) commitDetails := getCommitDetail(param[0], param[1]) checkMsgFormat(commitDetails) } //提交的详细信息 type CommitDetailstruct { messagestring commitEmailstring hashstring } //获取提交的详细信息 func getCommitDetail(oldCommitID, commitIDstring) (details []*CommitDetail) { details =make([]*CommitDetail, 0, 10) getCommitMsgCmd := exec.Command("git", "log", oldCommitID+".."+commitID, "--pretty=format:%s::%ce::%H") getCommitMsgCmd.Stdin = os.Stdin getCommitMsgCmd.Stderr = os.Stderr b, err := getCommitMsgCmd.Output() if err != nil { write2Log(MSG_TYPE_ERROR, fmt.Sprintf("cmd %v execute error : %v", getCommitMsgCmd, err)) //checkFailed() return } write2Log(MSG_TYPE_INFO, fmt.Sprintf("%v", getCommitMsgCmd.Args)) //write2Log(string(b)) //先按照"\n"来分割,因为可能会存在多个commit同时push的情况 commits := strings.Split(string(b), "\n") if len(commits) <=0 { write2Log(MSG_TYPE_ERROR, fmt.Sprintf("get commits failed from %s !", string(b))) //checkFailed() return } for _, commit :=range commits { infos := strings.Split(commit, "::") //write2Log(fmt.Sprintf("len(infos) : %d", len(infos))) if len(infos) !=3 { write2Log(MSG_TYPE_ERROR, "get commit info failed !") //checkFailed() return } details = append(details, &CommitDetail{ message: infos[0], commitEmail: infos[1], hash: infos[2], }) } return } //校验注释格式是否正确 func checkMsgFormat(details []*CommitDetail) { for _, d :=range details { //查找"#" pos0 := strings.Index(d.message, "#") if pos0 == -1 { write2Log(MSG_TYPE_ERROR, d.hash+" '#' no found in comment") //checkSucceed() continue } //获取前缀 prefix := d.message[:pos0] if len(prefix) <=0 { write2Log(MSG_TYPE_ERROR, d.hash+"WARNING: no any prefix , pls check as follow : begin|end|do:# .") //checkSucceed() continue } //查找空格 pos1 := strings.Index(d.message[pos0+1:], " ") if pos1 == -1 { write2Log(MSG_TYPE_ERROR, d.hash+"WARNING: no any blankspace , pls check as follow : begin|end|do:# .") continue //checkFailed() } //write2Log(fmt.Sprintf("pos0 %d, pos1 %d of %v", pos0, pos1, d)) //是否是正确的issue序列号 issueId, err := strconv.ParseUint(d.message[pos0+1:pos0+1+pos1], 0, 64) if err != nil { write2LogErr(err) //checkFailed() continue } reqStr := fmt.Sprintf("http://<你的gitlab服务器>/issues/%d.json?key=<你的api key>", issueId) resp, err := http.Get(reqStr) if err != nil { write2LogErr(err) //checkFailed() continue } if resp.StatusCode !=200 { write2Log(MSG_TYPE_ERROR, d.hash+fmt.Sprintf("get issue info from redmine failed ! resp.StatusCode %d, please check if redmine service is valid !", resp.StatusCode)) //checkFailed() continue } contents, err := ioutil.ReadAll(resp.Body) resp.Body.Close() items :=make(map[string]interface{}, 0) err = json.Unmarshal(contents, &items) if err != nil { write2LogErr(err) continue //checkFailed() } //write2Log(d.hash + fmt.Sprintf("issue detail : %v\n", items)) //issue当前责任人是否是提交人 if issue, ok := items[ISSUE]; !ok { write2Log(MSG_TYPE_ERROR, d.hash+fmt.Sprintf("issue %d no found !", issueId)) //checkFailed() continue }else { if v, ok := issue.(map[string]interface{}); !ok { write2Log(MSG_TYPE_ERROR, d.hash+fmt.Sprintf("issue info error. %v", issue)) //checkFailed() continue }else { //获取责任人 ok, assignedTo := getIssueItemName(d, ISSUE_ASSIGNED_TO, v) if !ok { write2Log(MSG_TYPE_ERROR, d.hash+fmt.Sprintf("get %s failed ! %v", v)) //checkFailed() continue } email, ok := USER2EMAIL[assignedTo] if !ok { write2Log(MSG_TYPE_ERROR, d.hash+fmt.Sprintf("unkown user.name %s !\nkown users : %v", assignedTo, USER2EMAIL)) //checkFailed() continue } //当前问题的责任人不是提交人 if 0 != strings.Compare(email, d.commitEmail) { write2Log(MSG_TYPE_ERROR, d.hash+fmt.Sprintf(" issue#%d's owner is %s but not you.", issueId, assignedTo)) //checkFailed() continue } //问题状态校验 ok, status := getIssueItemId(d, ISSUE_STATUS, v) if !ok { write2Log(MSG_TYPE_ERROR, d.hash+fmt.Sprintf("get %s failed ! %v", ISSUE_STATUS, v)) //checkFailed() continue } if status !=ISSUE_STATUS_NEW && status !=ISSUE_STATUS_DOING && status !=ISSUE_STATUS_FEEDBACK { write2Log(MSG_TYPE_ERROR, d.hash+fmt.Sprintf("error issue #%d status : %s", issueId, ISSUE_STATUS_STR[status])) //checkFailed() continue } //switch prefix { //case COMMENT_PREFIX_BEGIN: // { // if status != ISSUE_STATUS_NEW && // status != ISSUE_STATUS_FEEDBACK { // write2Log(fmt.Sprintf("issue#%d should be new or feedback !", issueId)) // checkFailed() // } // } //case COMMENT_PREFIX_END: // { // if status != ISSUE_STATUS_NEW && // status != ISSUE_STATUS_FEEDBACK && // status != ISSUE_STATUS_DOING { // write2Log(fmt.Sprintf("issue#%d should be new or feedback or doing !", issueId)) // checkFailed() // } // } //case COMMENT_PREFIX_DO: // { // if status != ISSUE_STATUS_DOING { // write2Log(fmt.Sprintf("issue#%d should be doing !", issueId)) // checkFailed() // } // } //default: // { // write2Log(fmt.Sprintf("unkown prefix %s !", prefix)) // checkFailed() // } //} write2Log(MSG_TYPE_INFO, d.hash+" check succeed!") } } } } //属性结构体字段索引 const ( INDEX_ID =iota INDEX_NAME ) func getIssueItemName(d *CommitDetail, namestring, vmap[string]interface{}) (okbool, valuestring) { return getIssueItemStr(d, name, v, INDEX_NAME) } func getIssueItemId(d *CommitDetail, namestring, vmap[string]interface{}) (okbool, valuestring) { return getIssueItemStr(d, name, v, INDEX_ID) } //获取issue子信息 func getIssueItemStr(d *CommitDetail, namestring, vmap[string]interface{}, indexint) (okbool, valuestring) { //获取责任人 var vTmpinterface{} if vTmp, ok = v[name]; !ok { write2Log(MSG_TYPE_ERROR, d.hash+fmt.Sprintf("%s no found!!! from %v", name, v)) checkFailed() }else { valueMap, ok := vTmp.(map[string]interface{}) if !ok { write2Log(MSG_TYPE_ERROR, d.hash+fmt.Sprintf("convert %s map failed. %v", name, vTmp)) checkFailed() } var keystring switch index { case INDEX_ID: key =ID case INDEX_NAME: key =NAME default: write2Log(MSG_TYPE_ERROR, d.hash+fmt.Sprintf("unkown index %v", index)) checkFailed() } valueTmp, ok := valueMap[key] if !ok { write2Log(MSG_TYPE_ERROR, d.hash+fmt.Sprintf("get %s.%s failed ! %v", name, key, vTmp)) checkFailed() } value = fmt.Sprintf("%v", valueTmp) } return } const ( ISSUE ="issue" ID ="id" NAME ="name" ISSUE_STATUS ="status" ISSUE_ASSIGNED_TO ="assigned_to" ISSUE_SUBJECT ="subject" ) func checkFailed() { os.Exit(1) } func checkSucceed() { os.Exit(0) } type MSG_TYPEint const ( MSG_TYPE_ERROR MSG_TYPE =iota MSG_TYPE_WARNING MSG_TYPE_INFO ) func write2LogErr(errerror) { write2Log(MSG_TYPE_ERROR, fmt.Sprintf("%v", err)) } func write2Log(tMSG_TYPE, sstring) { var msg_prefixstring switch t { case MSG_TYPE_ERROR: msg_prefix ="ERROR" case MSG_TYPE_WARNING: msg_prefix ="WARNING" default: msg_prefix ="INFO" } fmt.Fprintln(os.Stderr, msg_prefix+": "+s) }
如上代码将gitlab url地址和api key替换成自己的就可以直接使用。
为了方便项目组成员过度,在校验不通过的时候,暂时只返回ERROR提示信息,不阻塞提交。等实施了一段时间后,把打印修改为阻塞,强制执行约定。
希望对大家有用。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK