美文网首页
golang实现gitlab commit注释校验hook

golang实现gitlab commit注释校验hook

作者: ricktian_e963 | 来源:发表于2018-08-15 15:17 被阅读0次

    最近和项目成员约定了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提示信息,不阻塞提交。等实施了一段时间后,把打印修改为阻塞,强制执行约定。

    希望对大家有用。

    相关文章

      网友评论

          本文标题:golang实现gitlab commit注释校验hook

          本文链接:https://www.haomeiwen.com/subject/vqrwbftx.html