golang實現gitlab commit注釋校正hook

來源:互聯網
上載者:User

最近和項目成員約定了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 進行中

)

// 是否開啟strict 模式,strict 模式下將校正所有的提交資訊格式(多 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提示資訊,不阻塞提交。等實施了一段時間後,把列印修改為阻塞,強制執行約定。

希望對大家有用。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.