golang实现gitlab commit注释校验hook

ricktian_e963 · · 1548 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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

希望对大家有用。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:ricktian_e963

查看原文:golang实现gitlab commit注释校验hook

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1548 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传