Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"command.go",
"contributors.go",
"merge.go",
"review.go",
],
importpath = "go-common/app/tool/saga/service/command",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/tool/saga/conf:go_default_library",
"//app/tool/saga/dao:go_default_library",
"//app/tool/saga/model:go_default_library",
"//app/tool/saga/service/gitlab:go_default_library",
"//app/tool/saga/service/notification:go_default_library",
"//library/log:go_default_library",
"//vendor/github.com/xanzy/go-gitlab:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = [
"command_test.go",
"contributors_test.go",
"review_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/tool/saga/conf:go_default_library",
"//app/tool/saga/dao:go_default_library",
"//app/tool/saga/model:go_default_library",
"//app/tool/saga/service/gitlab:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,113 @@
package command
import (
"context"
"fmt"
"runtime/debug"
"time"
"go-common/app/tool/saga/conf"
"go-common/app/tool/saga/dao"
"go-common/app/tool/saga/model"
"go-common/app/tool/saga/service/gitlab"
"go-common/library/log"
)
// Command Command def.
type Command struct {
dao *dao.Dao
gitlab *gitlab.Gitlab
cmds map[string]cmdFunc
}
type cmdFunc func(ctx context.Context, event *model.HookComment, repo *model.Repo) (err error)
// New ...
func New(dao *dao.Dao, gitlab *gitlab.Gitlab) (c *Command) {
c = &Command{
dao: dao,
gitlab: gitlab,
cmds: make(map[string]cmdFunc),
}
return
}
// Exec ...
func (c *Command) Exec(ctx context.Context, cmd string, event *model.HookComment, repo *model.Repo) (err error) {
var (
f cmdFunc
ok bool
projID = int(event.MergeRequest.SourceProjectID)
mrIID = int(event.MergeRequest.IID)
)
if f, ok = c.cmds[cmd]; !ok {
return
}
if err = f(ctx, event, repo); err != nil {
c.gitlab.CreateMRNote(projID, mrIID, fmt.Sprintf("<pre>SAGA 异常:%+v %s</pre>", err, debug.Stack()))
return
}
return
}
func (c *Command) register(cmd string, f cmdFunc) {
c.cmds[cmd] = f
}
// Registers ...
func (c *Command) Registers() {
c.register(model.SagaCommandPlusOne, c.runPlusOne)
c.register(model.SagaCommandMerge, c.runTryMerge)
c.register(model.SagaCommandMerge1, c.runTryMerge)
c.register(model.SagaCommandPlusOne1, c.runPlusOne)
}
// ListenTask ...
func (c *Command) ListenTask() {
var (
ctx = context.TODO()
err error
t *time.Timer
ok bool
taskInfos []*model.TaskInfo
)
defer func() {
if x := recover(); x != nil {
log.Error("ListenTask: %+v %s", x, debug.Stack())
}
}()
t = time.NewTimer(time.Duration(conf.Conf.Property.TaskInterval))
for range t.C {
if _, taskInfos, err = c.dao.MergeTasks(ctx, model.TaskStatusWaiting); err != nil {
log.Error("request MergeTasks: %+v", err)
continue
}
for _, taskInfo := range taskInfos {
if ok, err = c.dao.TryLock(ctx, fmt.Sprintf(model.SagaRepoLockKey, int(taskInfo.Event.ProjectID)), model.SagaLockValue, int(taskInfo.Repo.Config.LockTimeout)); err != nil {
log.Error("TryLock ProjectID: %d, MRIID: %d, err: %+v", taskInfo.Event.ProjectID, taskInfo.Event.MergeRequest.IID, err)
continue
}
if !ok {
log.Info("TryLock ok: %t, ProjectID: %d, MRIID: %d", ok, taskInfo.Event.ProjectID, taskInfo.Event.MergeRequest.IID)
continue
}
log.Info("request MRIID: %d, MergeTasks:%+v", taskInfo.Event.MergeRequest.IID, taskInfo)
go func(taskInfo *model.TaskInfo) {
var (
projID = int(taskInfo.Event.MergeRequest.SourceProjectID)
mrIID = int(taskInfo.Event.MergeRequest.IID)
branch = taskInfo.Event.MergeRequest.SourceBranch
)
if err = c.execMergeTask(taskInfo); err != nil {
c.gitlab.UpdateMRNote(projID, mrIID, taskInfo.NoteID, fmt.Sprintf("<pre>SAGA 异常:%+v</pre>", err))
if err = c.resetMergeStatus(projID, mrIID, branch, false); err != nil {
log.Error("resetMergeStatus error: %+v", err)
}
log.Error("execMergeTask: %+v", err)
}
}(taskInfo)
}
t.Reset(time.Duration(conf.Conf.Property.TaskInterval))
}
}

View File

@@ -0,0 +1,209 @@
package command
import (
"context"
"encoding/json"
"flag"
"path/filepath"
"testing"
"go-common/app/tool/saga/conf"
"go-common/app/tool/saga/dao"
"go-common/app/tool/saga/model"
"go-common/app/tool/saga/service/gitlab"
"github.com/smartystreets/goconvey/convey"
)
var (
c *Command
gitlabHookCommentTest = []byte(`{
"object_kind":"note",
"event_type":"note",
"user":{
"name":"changhengyuan",
"username":"changhengyuan",
"avatar_url":"https://www.gravatar.com/avatar/d3218d34473c6fb4d18a770f14e59a89?s=80\u0026d=identicon"
},
"project_id":35,
"project":{
"id":35,
"name":"test-saga",
"description":"",
"web_url":"http://gitlab.bilibili.co/changhengyuan/test-saga",
"avatar_url":null,
"git_ssh_url":"git@gitlab.bilibili.co:changhengyuan/test-saga.git",
"git_http_url":"http://gitlab.bilibili.co/changhengyuan/test-saga.git",
"namespace":"changhengyuan",
"visibility_level":20,
"path_with_namespace":"changhengyuan/test-saga",
"default_branch":"master",
"ci_config_path":null,
"homepage":"http://gitlab.bilibili.co/changhengyuan/test-saga",
"url":"git@gitlab.bilibili.co:changhengyuan/test-saga.git",
"ssh_url":"git@gitlab.bilibili.co:changhengyuan/test-saga.git",
"http_url":"http://gitlab.bilibili.co/changhengyuan/test-saga.git"},
"object_attributes":{
"id":3040,
"note":"test",
"noteable_type":"MergeRequest",
"author_id":15,
"created_at":"2018-09-26 06:55:13 UTC",
"updated_at":"2018-09-26 06:55:13 UTC",
"project_id":35,
"attachment":null,
"line_code":null,
"commit_id":"",
"noteable_id":390,
"system":false,
"st_diff":null,
"updated_by_id":null,
"type":null,
"position":null,
"original_position":null,
"resolved_at":null,
"resolved_by_id":null,
"discussion_id":"450c34e4c0f9e925bdc6a24c2ae4920d7a394ebc",
"change_position":null,
"resolved_by_push":null,
"url":"http://gitlab.bilibili.co/changhengyuan/test-saga/merge_requests/52#note_3040"
},
"repository":{
"name":"test-saga",
"url":"git@gitlab.bilibili.co:changhengyuan/test-saga.git",
"description":"",
"homepage":"http://gitlab.bilibili.co/changhengyuan/test-saga"
},
"merge_request":{
"assignee_id":null,
"author_id":15,
"created_at":"2018-09-26 06:41:55 UTC",
"description":"",
"head_pipeline_id":4510,
"id":390,
"iid":52,
"last_edited_at":null,
"last_edited_by_id":null,
"merge_commit_sha":null,
"merge_error":null,
"merge_params":{
"force_remove_source_branch":"0"
},
"merge_status":"cannot_be_merged",
"merge_user_id":null,
"merge_when_pipeline_succeeds":false,
"milestone_id":null,
"source_branch":"test-branch",
"source_project_id":35,
"state":"opened",
"target_branch":"master",
"target_project_id":35,
"time_estimate":0,
"title":"Test branch",
"updated_at":"2018-09-26 06:54:33 UTC",
"updated_by_id":null,
"url":"http://gitlab.bilibili.co/changhengyuan/test-saga/merge_requests/52",
"source":{
"id":35,
"name":"test-saga",
"description":"",
"web_url":"http://gitlab.bilibili.co/changhengyuan/test-saga",
"avatar_url":null,
"git_ssh_url":"git@gitlab.bilibili.co:changhengyuan/test-saga.git",
"git_http_url":"http://gitlab.bilibili.co/changhengyuan/test-saga.git",
"namespace":"changhengyuan",
"visibility_level":20,
"path_with_namespace":"changhengyuan/test-saga",
"default_branch":"master",
"ci_config_path":null,
"homepage":"http://gitlab.bilibili.co/changhengyuan/test-saga",
"url":"git@gitlab.bilibili.co:changhengyuan/test-saga.git",
"ssh_url":"git@gitlab.bilibili.co:changhengyuan/test-saga.git",
"http_url":"http://gitlab.bilibili.co/changhengyuan/test-saga.git"
},
"target":{
"id":35,
"name":"test-saga",
"description":"",
"web_url":"http://gitlab.bilibili.co/changhengyuan/test-saga",
"avatar_url":null,
"git_ssh_url":"git@gitlab.bilibili.co:changhengyuan/test-saga.git",
"git_http_url":"http://gitlab.bilibili.co/changhengyuan/test-saga.git",
"namespace":"changhengyuan",
"visibility_level":20,
"path_with_namespace":"changhengyuan/test-saga",
"default_branch":"master",
"ci_config_path":null,
"homepage":"http://gitlab.bilibili.co/changhengyuan/test-saga",
"url":"git@gitlab.bilibili.co:changhengyuan/test-saga.git",
"ssh_url":"git@gitlab.bilibili.co:changhengyuan/test-saga.git",
"http_url":"http://gitlab.bilibili.co/changhengyuan/test-saga.git"
},
"last_commit":{
"id":"51e9c3ba2ceac496dbaf55f0db564ab6a15e20eb",
"message":"add CONTRIBUTORS.md\n",
"timestamp":"2018-09-17T18:02:13+08:00",
"url":"http://gitlab.bilibili.co/changhengyuan/test-saga/commit/51e9c3ba2ceac496dbaf55f0db564ab6a15e20eb",
"author":{
"name":"哔哩哔哩",
"email":"bilibili@bilibilideMac-mini.local"
}
},
"work_in_progress":false,
"total_time_spent":0,
"human_total_time_spent":null,
"human_time_estimate":null}}`)
)
func init() {
dir, _ := filepath.Abs("../../cmd/saga-test.toml")
flag.Set("conf", dir)
conf.Init()
c = New(&dao.Dao{}, &gitlab.Gitlab{})
}
func TestCommandNew(t *testing.T) {
convey.Convey("New", t, func(ctx convey.C) {
ctx.Convey("When everything goes positive", func(ctx convey.C) {
ctx.Convey("Then c should not be nil.", func(ctx convey.C) {
ctx.So(c, convey.ShouldNotBeNil)
})
})
})
}
func TestCommandExec(t *testing.T) {
convey.Convey("Exec", t, func(ctx convey.C) {
var (
ct = context.Background()
cmd = "+1"
event = &model.HookComment{}
repo = &model.Repo{}
c = &Command{}
)
_ = json.Unmarshal(gitlabHookCommentTest, event)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := c.Exec(ct, cmd, event, repo)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestCommandRegister(t *testing.T) {
convey.Convey("register", t, func(ctx convey.C) {
var (
cmd = "test_cmd"
f cmdFunc
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
c.register(cmd, f)
ctx.Convey("No return values", func(ctx convey.C) {
cmd, ok := c.cmds["test_cmd"]
ctx.So(ok, convey.ShouldEqual, true)
ctx.So(cmd, convey.ShouldEqual, f)
})
})
})
}

View File

@@ -0,0 +1,470 @@
package command
import (
"context"
"fmt"
"path/filepath"
"strings"
"go-common/app/tool/saga/model"
"go-common/app/tool/saga/service/notification"
"go-common/library/log"
)
type contributor struct {
Owner []string
Author []string
Reviewer []string
}
func readContributor(content []byte) (c *contributor) {
var (
lines []string
lineStr string
curSection string
)
c = &contributor{}
lines = strings.Split(string(content), "\n")
for _, lineStr = range lines {
if lineStr == "" {
continue
}
if strings.Contains(strings.ToLower(lineStr), "owner") {
curSection = "owner"
continue
}
if strings.Contains(strings.ToLower(lineStr), "author") {
curSection = "author"
continue
}
if strings.Contains(strings.ToLower(lineStr), "reviewer") {
curSection = "reviewer"
continue
}
switch curSection {
case "owner":
c.Owner = append(c.Owner, strings.TrimSpace(lineStr))
case "author":
c.Author = append(c.Author, strings.TrimSpace(lineStr))
case "reviewer":
c.Reviewer = append(c.Reviewer, strings.TrimSpace(lineStr))
}
}
return
}
// BuildContributor ...
func (c *Command) BuildContributor(repo *model.RepoInfo) (err error) {
var (
host string
token string
files []string
projID int
branch = repo.Branch
)
if host, token, err = c.gitlab.HostToken(); err != nil {
return
}
if files, err = c.dao.RepoFiles(context.TODO(), host, token, repo); err != nil {
return
}
if projID, err = c.gitlab.ProjectID(fmt.Sprintf("git@%s:%s/%s.git", host, repo.Group, repo.Name)); err != nil {
return
}
if err = c.SaveContributor(projID, branch, files, false); err != nil {
return
}
return
}
func hasbranch(branch string, branchs []string) bool {
for _, b := range branchs {
if strings.EqualFold(b, branch) {
return true
}
}
return false
}
// UpdateContributor ...
func (c *Command) UpdateContributor(projID int, mrIID int, sourceBranch string, targetBranch string, authBranches []string) (err error) {
var changeFiles []string
var deleteFiles []string
if !hasbranch(targetBranch, authBranches) {
log.Info("UpdateContributor not authBranches: %d, %s, %+v", projID, targetBranch, authBranches)
return
}
if changeFiles, deleteFiles, err = c.gitlab.MRChanges(projID, mrIID); err != nil {
return
}
//log.Info("UpdateContributor: projID: %d, changeFiles: %+v, deleteFiles: %+v", projID, changeFiles, deleteFiles)
if err = c.SaveContributor(projID, targetBranch, changeFiles, false); err != nil {
return
}
if err = c.SaveContributor(projID, targetBranch, deleteFiles, true); err != nil {
return
}
return
}
// SaveContributor ...
func (c *Command) SaveContributor(projID int, branch string, files []string, clean bool) (err error) {
var (
ctx = context.TODO()
raw []byte
cb *contributor
)
for _, file := range files {
if strings.EqualFold(filepath.Base(file), model.SagaContributorsName) {
path := filepath.Dir(file)
if clean {
//delete redis key
if err = c.dao.DeletePathAuthR(ctx, projID, branch, path); err != nil {
log.Error("delete Auth Redis key error(%+v) project ID:%d, branch:%s, path:%s", err, projID, branch, path)
err = nil
}
//delete hbase key
if err = c.dao.DeletePathAuthH(ctx, projID, branch, path); err != nil {
log.Error("delete Auth hbase key error(%+v) project ID:%d, branch:%s, path:%s", err, projID, branch, path)
err = nil
}
} else {
if raw, err = c.gitlab.RepoRawFile(projID, branch, file); err != nil {
return
}
cb = readContributor(raw)
log.Info("SaveContributor projectID:%d, branch:%s, path:%s, owner:%+v, reviewer:%+v", projID, branch, path, cb.Owner, cb.Reviewer)
//add to redis
authUser := &model.AuthUsers{
Owners: cb.Owner,
Reviewers: cb.Reviewer,
}
if err = c.dao.SetPathAuthR(ctx, projID, branch, path, authUser); err != nil {
log.Error("Set Auth to Redis error(%+v) project ID:%d, path:%s, owner:%+v, reviewer:%+v", err, projID, path, cb.Owner, cb.Reviewer)
err = nil
}
//add to hbase
if err = c.dao.SetPathAuthH(ctx, projID, branch, path, cb.Owner, cb.Reviewer); err != nil {
log.Error("Set Auth to Hbase error(%+v) projectID:%d, branch:%s, path:%s, owner:%+v, reviewer:%+v", err, projID, branch, path, cb.Owner, cb.Reviewer)
err = nil
}
}
}
}
return
}
// checkSuperAuth ...
func checkSuperAuth(username string, reviewedUsers []string, superUsers []string) (ok bool) {
for _, super := range superUsers {
if strings.EqualFold(super, username) {
return true
}
}
for _, r := range reviewedUsers {
for _, s := range superUsers {
if strings.EqualFold(r, s) {
return true
}
}
}
return false
}
// checkAllPathAuth ...
func (c *Command) checkAllPathAuth(taskInfo *model.TaskInfo) (ok bool, err error) {
var (
comment string
files []string
folders map[string]string
owners []string
reviewers []string
requireOwners []string
requireReviewers []string
reviewedUsers []string
requireReviewFolders []*model.RequireReviewFolder
username = taskInfo.Event.User.UserName
projID = int(taskInfo.Event.MergeRequest.SourceProjectID)
mrIID = int(taskInfo.Event.MergeRequest.IID)
sourceBranch = taskInfo.Event.MergeRequest.SourceBranch
targetBranch = taskInfo.Event.MergeRequest.TargetBranch
authBranch = c.GetAuthBranch(targetBranch, taskInfo.Repo.Config.AuthBranches)
url = taskInfo.Event.ObjectAttributes.URL
minReviewer = taskInfo.Repo.Config.MinReviewer
limitAuth = taskInfo.Repo.Config.LimitAuth
minReviewTip bool
isOwner bool
)
log.Info("checkAllPathAuth start ... MRIID: %d", mrIID)
if reviewedUsers, err = c.reviewedUsers(projID, mrIID); err != nil {
return
}
if len(taskInfo.Repo.Config.SuperAuthUsers) > 0 {
log.Info("checkAllPathAuth MRIID: %d, reviewedUsers: %v, SuperAuthUsers: %v", mrIID, reviewedUsers, taskInfo.Repo.Config.SuperAuthUsers)
ok = checkSuperAuth(username, reviewedUsers, taskInfo.Repo.Config.SuperAuthUsers)
if !ok {
comment, err = c.showRequireSuperAuthComment(taskInfo)
go notification.WechatAuthor(c.dao, username, url, sourceBranch, targetBranch, comment)
}
return
}
if files, err = c.gitlab.CompareDiff(projID, targetBranch, sourceBranch); err != nil {
return
}
log.Info("checkAllPathAuth projID:%d, MRIID:%d, reviewedUsers:%+v, files:%+v, targetBranch:%s, sourceBranch:%s", projID, mrIID, reviewedUsers, files, targetBranch, sourceBranch)
// 去重目录,校验目录权限
folders = make(map[string]string)
for _, file := range files {
folder := filepath.Dir(file)
if _, has := folders[folder]; !has {
folders[folder] = folder
authEnough := false
ownerPathReviewed := false
ownerReviewedStat := false
reviewedCount := 0
requireOwners = []string{}
requireReviewers = []string{}
dir := folder
for {
if owners, reviewers, err = c.GetPathAuth(projID, authBranch, dir); err != nil {
return
}
isOwner, ownerPathReviewed = reviewedOwner(owners, reviewedUsers, username)
num := reviewedNum(reviewers, reviewedUsers)
reviewedCount = reviewedCount + num
if isOwner {
log.Info("checkAllPathAuth user is owner, MRIID: %d, user: %s, owners %v, path: %s", mrIID, username, owners, dir)
authEnough = true
break
}
if ownerPathReviewed {
ownerReviewedStat = true
if reviewedCount >= minReviewer {
log.Info("checkAllPathAuth owner reviewed and reach minReviewer, user: %s, owners %+v, path: %s, reviewedUsers: %+v, reviewedCount: %d, minReviewer: %d", username, owners, dir, reviewedUsers, reviewedCount, minReviewer)
authEnough = true
break
}
}
if (len(requireOwners) == 0) && (len(owners) > 0) {
log.Info("checkAllPathAuth required owners %v, path: %s, MRIID: %d", owners, dir, mrIID)
requireOwners = owners
}
if (len(requireReviewers) == 0) && (len(reviewers) > 0) {
log.Info("checkAllPathAuth required reviewers %v, path: %s, MRIID: %d", reviewers, dir, mrIID)
requireReviewers = reviewers
}
if dir == "." {
break
}
if (len(owners) > 0) && limitAuth {
break
}
dir = filepath.Dir(dir)
}
if !authEnough {
requireAuth := &model.RequireReviewFolder{}
requireAuth.Folder = folder
if !ownerReviewedStat {
requireAuth.Owners = requireOwners
}
if reviewedCount < minReviewer {
requireAuth.Reviewers = requireReviewers
minReviewTip = true
}
requireReviewFolders = append(requireReviewFolders, requireAuth)
}
}
}
if len(requireReviewFolders) > 0 {
comment, err = c.showRequireAuthComment(taskInfo, requireReviewFolders, minReviewTip)
go notification.WechatAuthor(c.dao, username, url, sourceBranch, targetBranch, comment)
return
}
ok = true
return
}
// GetAllPathAuth ...
func (c *Command) GetAllPathAuth(projID int, mrIID int, authBranch string) (pathOwners []model.RequireReviewFolder, err error) {
var (
files []string
deleteFiles []string
folders map[string]string
pathOwner []string
pathReviewer []string
)
if files, deleteFiles, err = c.gitlab.MRChanges(projID, mrIID); err != nil {
return
}
files = append(files, deleteFiles...)
// 去重目录,校验目录权限
folders = make(map[string]string)
for _, file := range files {
folder := filepath.Dir(file)
if _, has := folders[folder]; !has {
folders[folder] = folder
dir := folder
for {
if pathOwner, pathReviewer, err = c.GetPathAuth(projID, authBranch, dir); err != nil {
return
}
if len(pathOwner) > 0 {
exist := false
for _, os := range pathOwners {
if os.Folder == dir {
exist = true
break
}
}
if exist {
break
}
requireOwner := model.RequireReviewFolder{}
requireOwner.Folder = dir
requireOwner.Owners = pathOwner
if len(pathReviewer) > 0 {
requireOwner.Reviewers = pathReviewer
}
pathOwners = append(pathOwners, requireOwner)
break
}
if dir == "." {
break
}
dir = filepath.Dir(dir)
}
}
}
return
}
// GetPathAuth ...
func (c *Command) GetPathAuth(projID int, branch string, path string) (owners []string, reviewers []string, err error) {
var (
ctx = context.TODO()
authUser *model.AuthUsers
)
if authUser, err = c.dao.PathAuthR(ctx, projID, branch, path); err != nil || authUser == nil {
if err != nil {
log.Error("GetPathAuthInfo error project ID:%d, branch:%s, path: %s (err: %+v) ", projID, branch, path, err)
}
if owners, reviewers, err = c.dao.PathAuthH(ctx, projID, branch, path); err != nil {
return
}
if len(owners) <= 0 && len(reviewers) <= 0 {
return
}
if authUser == nil {
authUser = new(model.AuthUsers)
}
authUser.Owners = owners
authUser.Reviewers = reviewers
if err1 := c.dao.SetPathAuthR(ctx, projID, branch, path, authUser); err1 != nil {
log.Error("SetPathAuthR error project ID:%d, branch:%s, path: %s (err1: %+v) ", projID, branch, path, err1)
}
return
}
if authUser != nil {
owners = authUser.Owners
reviewers = authUser.Reviewers
}
return
}
// GetAuthBranch ...
func (c *Command) GetAuthBranch(targetBranch string, authBranches []string) (authBranch string) {
for _, r := range authBranches {
if r == targetBranch {
return r
}
}
return authBranches[0]
}
// showRequireAuthComment ...
func (c *Command) showRequireAuthComment(taskInfo *model.TaskInfo, requireReviewFolders []*model.RequireReviewFolder, minReviewTip bool) (comment string, err error) {
var (
username = taskInfo.Event.User.UserName
mrIID = int(taskInfo.Event.MergeRequest.IID)
projID = int(taskInfo.Event.MergeRequest.SourceProjectID)
noteID = taskInfo.NoteID
minReviewer = taskInfo.Repo.Config.MinReviewer
)
if minReviewTip {
comment = fmt.Sprintf("<pre>[%s]尝试合并失败无权限的文件目录如下并且最少需要review人数 [%d] 个,请寻找对应的大佬 +1 后再 +merge</pre>", username, minReviewer)
} else {
comment = fmt.Sprintf("<pre>[%s]尝试合并失败,无权限的文件目录如下,请寻找对应的大佬 +1 后再 +merge</pre>", username)
}
comment += "\n"
for _, reviewFolder := range requireReviewFolders {
comment += fmt.Sprintf("+ %s ", reviewFolder.Folder)
if len(reviewFolder.Owners) > 0 {
comment += "OWNER: "
for _, o := range reviewFolder.Owners {
if o == "all" {
comment += "所有人" + " 或 "
} else {
comment += o + " 或 "
}
}
comment = comment[:len(comment)-len(" 或 ")]
}
if len(reviewFolder.Reviewers) > 0 {
comment += "REVIEWER: "
for _, o := range reviewFolder.Reviewers {
if o == "all" {
comment += "所有人" + " 与 "
} else {
comment += o + " 与 "
}
}
comment = comment[:len(comment)-len(" 与 ")]
//comment += fmt.Sprintf("<pre>; 已review人数 [%d] 个</pre>", minReviewer)
}
comment += "\n\n"
}
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
return
}
// showRequireSuperAuthComment ...
func (c *Command) showRequireSuperAuthComment(taskInfo *model.TaskInfo) (comment string, err error) {
var (
username = taskInfo.Event.User.UserName
mrIID = int(taskInfo.Event.MergeRequest.IID)
projID = int(taskInfo.Event.MergeRequest.SourceProjectID)
noteID = taskInfo.NoteID
)
comment = fmt.Sprintf("<pre>[%s]尝试合并失败,已配置超级权限用户,请寻找其中至少一位大佬 +1 后再 +merge</pre>", username)
comment += "\n"
comment += fmt.Sprintf("+ SUPERMAN: ")
for _, user := range taskInfo.Repo.Config.SuperAuthUsers {
comment += fmt.Sprintf("%s 或 ", user)
}
comment = comment[:len(comment)-len(" 或 ")]
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
return
}

View File

@@ -0,0 +1,38 @@
package command
import (
"fmt"
"io/ioutil"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestCommandReadContributor(t *testing.T) {
convey.Convey("readContributor", t, func(ctx convey.C) {
content, _ := ioutil.ReadFile("../../CONTRIBUTORS.md")
ctx.Convey("When everything goes positive", func(ctx convey.C) {
cc := readContributor(content)
ctx.Convey("Then c should not be nil.", func(ctx convey.C) {
ctx.So(fmt.Sprint(cc.Author), convey.ShouldEqual, `[muyang yubaihai wangweizhen wuwei]`)
ctx.So(fmt.Sprint(cc.Owner), convey.ShouldEqual, `[muyang zhanglin]`)
ctx.So(fmt.Sprint(cc.Reviewer), convey.ShouldEqual, `[muyang]`)
})
})
})
}
func TestCommandHasBranch(t *testing.T) {
convey.Convey("hasbranch", t, func(ctx convey.C) {
var (
branch = "test"
branchs = []string{"master", "test"}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := hasbranch(branch, branchs)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldEqual, true)
})
})
})
}

View File

@@ -0,0 +1,529 @@
package command
import (
"context"
"fmt"
"runtime/debug"
"go-common/app/tool/saga/model"
"go-common/app/tool/saga/service/notification"
"go-common/library/log"
ggitlab "github.com/xanzy/go-gitlab"
)
func (c *Command) runTryMerge(ctx context.Context, event *model.HookComment, repo *model.Repo) (err error) {
var (
ok bool
canMerge bool
projID = int(event.MergeRequest.SourceProjectID)
mrIID = int(event.MergeRequest.IID)
wip = event.MergeRequest.WorkInProgress
noteID int
taskInfo = &model.TaskInfo{
Event: event,
Repo: repo,
}
)
log.Info("runTryMerge start ... MRIID: %d, Repo Config: %+v", mrIID, repo.Config)
if ok, err = c.dao.ExistMRIID(ctx, mrIID); err != nil || ok {
return
}
if noteID, err = c.gitlab.CreateMRNote(projID, mrIID, "<pre>SAGA 开始执行,请大佬稍后......</pre>"); err != nil {
return
}
taskInfo.NoteID = noteID
// 1, check wip
if wip {
c.gitlab.UpdateMRNote(projID, mrIID, noteID, "<pre>警告当前MR处于WIP状态请待开发结束后再merge</pre>")
return
}
// 2, check labels
if ok, err = c.checkLabels(projID, mrIID, noteID, repo); err != nil || !ok {
return
}
// 3, check merge status
if canMerge, err = c.checkMergeStatus(projID, mrIID, noteID); err != nil || !canMerge {
return
}
// 4, check pipeline status
if repo.Config.RelatePipeline {
if repo.Config.DelayMerge {
if ok, _, err = c.checkPipeline(projID, mrIID, noteID, 0, model.QueryProcessing); err != nil || !ok {
return
}
} else {
if ok, _, err = c.checkPipeline(projID, mrIID, noteID, 0, model.QuerySuccess); err != nil || !ok {
return
}
}
}
// 5, check path auth
if ok, err = c.checkAllPathAuth(taskInfo); err != nil || !ok {
return
}
// 6, show current mr queue info
c.showMRQueueInfo(ctx, taskInfo)
if err = c.dao.PushMergeTask(ctx, model.TaskStatusWaiting, taskInfo); err != nil {
return
}
if err = c.dao.AddMRIID(ctx, mrIID, int(repo.Config.LockTimeout)); err != nil {
return
}
log.Info("runTryMerge merge task 已加入 waiting 任务列队中... MRIID: %d", mrIID)
return
}
func (c *Command) execMergeTask(taskInfo *model.TaskInfo) (err error) {
var (
ctx = context.TODO()
projID = int(taskInfo.Event.MergeRequest.SourceProjectID)
mrIID = int(taskInfo.Event.MergeRequest.IID)
sourceBranch = taskInfo.Event.MergeRequest.SourceBranch
pipeline = &ggitlab.Pipeline{}
noteID = taskInfo.NoteID
mergeInfo = &model.MergeInfo{
ProjID: projID,
MRIID: mrIID,
URL: taskInfo.Event.ObjectAttributes.URL,
AuthBranches: taskInfo.Repo.Config.AuthBranches,
SourceBranch: taskInfo.Event.MergeRequest.SourceBranch,
TargetBranch: taskInfo.Event.MergeRequest.TargetBranch,
AuthorID: int(taskInfo.Event.MergeRequest.AuthorID),
UserName: taskInfo.Event.User.UserName,
MinReviewer: taskInfo.Repo.Config.MinReviewer,
LockTimeout: taskInfo.Repo.Config.LockTimeout,
Title: taskInfo.Event.MergeRequest.Title,
Description: taskInfo.Event.MergeRequest.Description,
}
)
mergeInfo.NoteID = noteID
// 从等待任务列队移除
if err = c.dao.DeleteMergeTask(ctx, model.TaskStatusWaiting, taskInfo); err != nil {
return
}
// 加入到正在执行任务列队
if err = c.dao.PushMergeTask(ctx, model.TaskStatusRunning, taskInfo); err != nil {
return
}
if taskInfo.Repo.Config.RelatePipeline {
if taskInfo.Repo.Config.DelayMerge {
if err = c.HookDelayMerge(projID, sourceBranch, mergeInfo); err != nil {
return
}
return
}
if err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, "<pre>SAGA 提示为了保证合进主干后能正常编译正在重跑pipeline等待时间取决于pipeline运行时间请耐心等待</pre>"); err != nil {
return
}
if pipeline, err = c.retryPipeline(taskInfo.Event); err != nil {
return
}
mergeInfo.PipelineID = pipeline.ID
if err = c.dao.SetMergeInfo(ctx, projID, sourceBranch, mergeInfo); err != nil {
return
}
} else {
if err = c.HookMerge(projID, sourceBranch, mergeInfo); err != nil {
return
}
}
return
}
func (c *Command) retryPipeline(event *model.HookComment) (pipeline *ggitlab.Pipeline, err error) {
var (
trigger *ggitlab.PipelineTrigger
triggers []*ggitlab.PipelineTrigger
projID = int(event.MergeRequest.SourceProjectID)
sourceBranch = event.MergeRequest.SourceBranch
)
if triggers, err = c.gitlab.Triggers(projID); err != nil {
return
}
if len(triggers) == 0 {
log.Info("No triggers were found for project %d, try to create it now.", projID)
if trigger, err = c.gitlab.CreateTrigger(projID); err != nil {
return
}
triggers = []*ggitlab.PipelineTrigger{trigger}
}
trigger = triggers[0]
if trigger.Owner == nil || trigger.Owner.ID == 0 {
log.Info("Legacy trigger (without owner), take ownership now.")
if trigger, err = c.gitlab.TakeOwnership(projID, trigger.ID); err != nil {
return
}
}
if pipeline, err = c.gitlab.TriggerPipeline(projID, sourceBranch, trigger.Token); err != nil {
return
}
return
}
// HookPipeline ...
func (c *Command) HookPipeline(projID int, branch string, pipelineID int) (err error) {
var (
ok bool
canMerge bool
mergeInfo *model.MergeInfo
)
defer func() {
if x := recover(); x != nil {
log.Error("HookPipeline: %+v %s", x, debug.Stack())
}
}()
if ok, mergeInfo, err = c.dao.MergeInfo(context.TODO(), projID, branch); err != nil || !ok {
return
}
log.Info("HookPipeline projID: %d, MRIID: %d, branch: %s, pipelineId: %d", projID, mergeInfo.MRIID, branch, mergeInfo.PipelineID)
if pipelineID < mergeInfo.PipelineID {
return
}
defer func() {
if err = c.resetMergeStatus(projID, mergeInfo.MRIID, branch, true); err != nil {
log.Error("resetMergeStatus MRIID: %d, error: %+v", mergeInfo.MRIID, err)
}
}()
// 1, check pipeline id
if ok, _, err = c.checkPipeline(projID, mergeInfo.MRIID, mergeInfo.NoteID, mergeInfo.PipelineID, model.QueryID); err != nil || !ok {
return
}
// 2, check pipeline status
if ok, _, err = c.checkPipeline(projID, mergeInfo.MRIID, mergeInfo.NoteID, 0, model.QuerySuccess); err != nil || !ok {
return
}
// 3, check merge status
if canMerge, err = c.checkMergeStatus(projID, mergeInfo.MRIID, mergeInfo.NoteID); err != nil || !canMerge {
return
}
log.Info("HookPipeline acceptMerge ... MRIID: %d", mergeInfo.MRIID)
if ok, err = c.acceptMerge(mergeInfo); err != nil || !ok {
return
}
return
}
// HookMerge ...
func (c *Command) HookMerge(projID int, branch string, mergeInfo *model.MergeInfo) (err error) {
var (
ok bool
canMerge bool
)
defer func() {
if x := recover(); x != nil {
log.Error("HookMerge: %+v %s", x, debug.Stack())
}
}()
defer func() {
if err = c.resetMergeStatus(projID, mergeInfo.MRIID, branch, true); err != nil {
log.Error("resetMergeStatus MRIID: %d, error: %+v", mergeInfo.MRIID, err)
}
}()
log.Info("HookMerge projID: %d, MRIID: %d, branch: %s", projID, mergeInfo.MRIID, branch)
if canMerge, err = c.checkMergeStatus(projID, mergeInfo.MRIID, mergeInfo.NoteID); err != nil || !canMerge {
return
}
log.Info("HookMerge acceptMerge ... MRIID: %d", mergeInfo.MRIID)
if ok, err = c.acceptMerge(mergeInfo); err != nil || !ok {
return
}
return
}
// HookDelayMerge ...
func (c *Command) HookDelayMerge(projID int, branch string, mergeInfo *model.MergeInfo) (err error) {
var (
ctx = context.TODO()
ok bool
noteID = mergeInfo.NoteID
mrIID = mergeInfo.MRIID
pipelineID int
status string
)
defer func() {
if x := recover(); x != nil {
log.Error("HookDelayMerge: %+v %s", x, debug.Stack())
}
}()
//if ok, pipelineID, err = c.checkPipeline(projID, mrIID, noteID, 0, model.QuerySuccessRmNote); err != nil {
//return
//}
if pipelineID, status, err = c.gitlab.MRPipelineStatus(projID, mrIID); err != nil {
return
}
if status == model.PipelineSuccess || status == model.PipelineSkipped {
ok = true
} else if status != model.PipelineRunning && status != model.PipelinePending {
comment := fmt.Sprintf("<pre>警告pipeline状态异常请确保pipeline状态正常后再执行merge操作</pre>")
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
return
}
log.Info("HookDelayMerge projID: %d, MRIID: %d, branch: %s, pipeline status: %t", projID, mergeInfo.MRIID, branch, ok)
if ok {
if err = c.HookMerge(projID, branch, mergeInfo); err != nil {
return
}
} else {
mergeInfo.PipelineID = pipelineID
if err = c.dao.SetMergeInfo(ctx, projID, branch, mergeInfo); err != nil {
return
}
}
return
}
func (c *Command) acceptMerge(mergeInfo *model.MergeInfo) (ok bool, err error) {
var (
comment string
author string
canMerge bool
state string
authorID = mergeInfo.AuthorID
username = mergeInfo.UserName
projID = mergeInfo.ProjID
mrIID = mergeInfo.MRIID
url = mergeInfo.URL
sourceBranch = mergeInfo.SourceBranch
targetBranch = mergeInfo.TargetBranch
noteID = mergeInfo.NoteID
content = mergeInfo.Title
)
if author, err = c.gitlab.UserName(authorID); err != nil {
return
}
if canMerge, err = c.checkMergeStatus(projID, mrIID, noteID); err != nil {
return
}
if !canMerge {
go notification.WechatAuthor(c.dao, author, url, sourceBranch, targetBranch, comment)
return
}
if len(mergeInfo.Description) > 0 {
content = content + "\n\n" + mergeInfo.Description
}
mergeMSG := fmt.Sprintf("Merge branch [%s] into [%s] by [%s]\n%s", sourceBranch, targetBranch, username, content)
if state, err = c.gitlab.AcceptMR(projID, mrIID, mergeMSG); err != nil || state != model.MRStateMerged {
if err != nil {
comment = fmt.Sprintf("<pre>[%s]尝试合并失败当前状态不允许合并请查看上方merge按钮旁的提示</pre>", username)
} else {
comment = fmt.Sprintf("<pre>[%s]尝试合并失败,请检查当前状态或同步目标分支代码后再试!</pre>", username)
}
go notification.WechatAuthor(c.dao, author, url, sourceBranch, targetBranch, comment)
c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
return
}
ok = true
comment = fmt.Sprintf("<pre>[%s]尝试合并成功!</pre>", username)
go notification.WechatAuthor(c.dao, author, url, sourceBranch, targetBranch, comment)
c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
return
}
func (c *Command) resetMergeStatus(projID int, MRIID int, branch string, taskRunning bool) (err error) {
var (
ctx = context.TODO()
)
log.Info("resetMergeStatus projID: %d, MRIID: %d start", projID, MRIID)
if err = c.dao.UnLock(ctx, fmt.Sprintf(model.SagaRepoLockKey, projID)); err != nil {
log.Error("UnLock error: %+v", err)
}
if err = c.dao.DeleteMergeInfo(ctx, projID, branch); err != nil {
log.Error("DeleteMergeInfo error: %+v", err)
}
if err = c.dao.DeleteMRIID(ctx, MRIID); err != nil {
log.Error("Delete MRIID :%d, error: %+v", MRIID, err)
}
if taskRunning {
if err = c.DeleteRunningTask(projID, MRIID); err != nil {
log.Error("DeleteRunningTask: %+v", err)
}
}
log.Info("resetMergeStatus projID: %d, MRIID: %d end!", projID, MRIID)
return
}
// DeleteRunningTask ...
func (c *Command) DeleteRunningTask(projID int, mrID int) (err error) {
var (
ctx = context.TODO()
taskInfos []*model.TaskInfo
)
if _, taskInfos, err = c.dao.MergeTasks(ctx, model.TaskStatusRunning); err != nil {
return
}
for _, taskInfo := range taskInfos {
pID := int(taskInfo.Event.MergeRequest.SourceProjectID)
mID := int(taskInfo.Event.MergeRequest.IID)
if pID == projID && mID == mrID {
// 从正在运行的任务列队中移除
err = c.dao.DeleteMergeTask(ctx, model.TaskStatusRunning, taskInfo)
return
}
}
return
}
func (c *Command) checkMergeStatus(projID int, mrIID int, noteID int) (canMerge bool, err error) {
var (
wip bool
state string
status string
comment string
)
if wip, state, status, err = c.gitlab.MergeStatus(projID, mrIID); err != nil {
return
}
if wip {
comment = "<pre>SAGA 尝试合并失败当前MR是一项正在进行的工作若已完成请先点击“Resolve WIP status”按钮处理后再+merge</pre>"
} else if state != model.MergeStateOpened {
comment = "<pre>SAGA 尝试合并失败当前MR已经关闭或者已经合并</pre>"
} else if status != model.MergeStatusOk {
comment = "<pre>SAGA 尝试合并失败,请先解决合并冲突!</pre>"
} else {
canMerge = true
}
if len(comment) > 0 {
c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
}
return
}
// checkLabels ios or android need checkout label when release app stage
func (c *Command) checkLabels(projID int, mrIID int, noteID int, repo *model.Repo) (ok bool, err error) {
var (
labels []string
comment = fmt.Sprintf("<pre>警告SAGA 无法执行+merge发版阶段只允许合入指定label的MR</pre>")
)
if len(repo.Config.AllowLabel) <= 0 {
ok = true
return
}
if labels, err = c.gitlab.MergeLabels(projID, mrIID); err != nil {
return
}
log.Info("checkMrLabels MRIID: %d, labels: %+v", mrIID, labels)
for _, label := range labels {
if label == repo.Config.AllowLabel {
ok = true
return
}
}
if err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment); err != nil {
return
}
return
}
func (c *Command) checkPipeline(projID int, mrIID int, noteID int, lastPipelineID int, queryStatus model.QueryStatus) (ok bool, pipelineID int, err error) {
var status string
if pipelineID, status, err = c.gitlab.MRPipelineStatus(projID, mrIID); err != nil {
return
}
log.Info("checkPipeline MRIID: %d, queryStatus: %d, pipeline status: %s", mrIID, queryStatus, status)
// query pipeline id index
if queryStatus == model.QueryID {
if pipelineID > lastPipelineID {
comment := fmt.Sprintf("<pre>警告SAGA 检测到重新提交代码了,+merge中断请重新review代码</pre>")
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
return
}
ok = true
return
}
// query process status
if queryStatus == model.QueryProcessing {
if status == model.PipelineRunning || status == model.PipelinePending {
comment := fmt.Sprintf("<pre>警告pipeline正在运行中暂不能立即merge待pipeline运行通过后会自动执行merge操作</pre>")
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
ok = true
return
} else if status == model.PipelineSuccess || status == model.PipelineSkipped {
ok = true
return
}
comment := fmt.Sprintf("<pre>警告pipeline状态异常请确保pipeline状态正常后再执行merge操作</pre>")
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
return
}
// query success status
if queryStatus == model.QuerySuccess {
if status != model.PipelineSuccess {
comment := fmt.Sprintf("<pre>警告SAGA 无法执行+mergepipeline还未成功请大佬先让pipeline执行通过</pre>")
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
ok = false
return
}
}
ok = true
return
}
// showMRQueueInfo ...
func (c *Command) showMRQueueInfo(ctx context.Context, taskInfo *model.TaskInfo) (err error) {
var (
mrIID = int(taskInfo.Event.MergeRequest.IID)
projID = int(taskInfo.Event.MergeRequest.SourceProjectID)
noteID = taskInfo.NoteID
taskInfos []*model.TaskInfo
comment string
waitNum int
runningNum int
)
if _, taskInfos, err = c.dao.MergeTasks(ctx, model.TaskStatusWaiting); err != nil {
return
}
for _, waitTaskInfo := range taskInfos {
if waitTaskInfo.Event.ProjectID == taskInfo.Event.ProjectID {
waitNum++
}
}
if _, taskInfos, err = c.dao.MergeTasks(ctx, model.TaskStatusRunning); err != nil {
return
}
for _, runningTaskInfo := range taskInfos {
if runningTaskInfo.Event.ProjectID == taskInfo.Event.ProjectID {
runningNum++
}
}
if waitNum > 0 {
comment = fmt.Sprintf("<pre>SAGA 提示:当前还有 [%d] 个 MR 等待合并,请大佬耐心等待!</pre>", waitNum)
c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
} else if runningNum > 0 {
comment = fmt.Sprintf("<pre>SAGA 提示:当前还有 [%d] 个 MR 正在执行,请大佬耐心等待!</pre>", runningNum)
c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
}
return
}

View File

@@ -0,0 +1,91 @@
package command
import (
"context"
"fmt"
"strings"
"go-common/app/tool/saga/model"
"go-common/app/tool/saga/service/notification"
"go-common/library/log"
)
func (c *Command) runPlusOne(ctx context.Context, event *model.HookComment, repo *model.Repo) (err error) {
var (
author string
url = event.ObjectAttributes.URL
commit = event.MergeRequest.LastCommit.ID
reviewer = event.User.UserName
authorID = int(event.MergeRequest.AuthorID)
sourceBranch = event.MergeRequest.SourceBranch
targetBranch = event.MergeRequest.TargetBranch
wip = event.MergeRequest.WorkInProgress
)
log.Info("runPlusOne start ...")
if wip {
c.gitlab.CreateMRNote(event.Project.ID, int(event.MergeRequest.IID), fmt.Sprintf("<pre>警告当前MR处于WIP状态请待开发结束后再review</pre>"))
return
}
if author, err = c.gitlab.UserName(authorID); err != nil {
log.Error("%+v", err)
return
}
log.Info("runPlusOne notification author: %s", author)
if author != "" {
go func() {
notification.MailPlusOne(author, reviewer, url, commit, sourceBranch, targetBranch)
}()
go func() {
notification.WechatPlusOne(c.dao, author, reviewer, url, commit, sourceBranch, targetBranch)
}()
}
return
}
func reviewedOwner(owners []string, reviewedUsers []string, username string) (isowner bool, reviewed bool) {
for _, owner := range owners {
if strings.EqualFold(owner, username) || strings.EqualFold(owner, "all") {
return true, true
}
}
for _, owner := range owners {
for _, user := range reviewedUsers {
if strings.EqualFold(user, owner) {
return false, true
}
}
}
return false, false
}
func (c *Command) reviewedUsers(projID int, mrIID int) (reviewedUsers []string, err error) {
var awardUsers []string
if reviewedUsers, err = c.gitlab.PlusUsernames(projID, mrIID); err != nil {
return
}
if awardUsers, err = c.gitlab.AwardEmojiUsernames(projID, mrIID); err != nil {
return
}
OUTER:
for _, au := range awardUsers {
for _, mu := range reviewedUsers {
if au == mu {
continue OUTER
}
}
reviewedUsers = append(reviewedUsers, au)
}
return
}
func reviewedNum(reviewers []string, reviewedUsers []string) (num int) {
for _, reviewer := range reviewers {
for _, user := range reviewedUsers {
if strings.EqualFold(user, reviewer) || strings.EqualFold(reviewer, "all") {
num++
}
}
}
return
}

View File

@@ -0,0 +1,53 @@
package command
import (
"fmt"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestCommandReviewedOwner(t *testing.T) {
convey.Convey("ownerReviewed", t, func(ctx convey.C) {
var (
owners = []string{"a", "b"}
reviewedUsers = []string{"a"}
username = "d"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
isowner, reviewed := reviewedOwner(owners, reviewedUsers, username)
fmt.Println(isowner, reviewed)
ctx.Convey("Then isowner,reviewed should not be nil.", func(ctx convey.C) {
ctx.So(reviewed, convey.ShouldBeTrue)
ctx.So(isowner, convey.ShouldBeFalse)
})
})
})
}
func TestCommandReviewedNum(t *testing.T) {
convey.Convey("reviewedNum", t, func(ctx convey.C) {
var (
reviewers = []string{"a", "b"}
reviewedUsers = []string{"c"}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num := reviewedNum(reviewers, reviewedUsers)
ctx.Convey("Then num should not be nil.", func(ctx convey.C) {
ctx.So(num, convey.ShouldEqual, 0)
})
})
})
convey.Convey("reviewedNum", t, func(ctx convey.C) {
var (
reviewers = []string{"a", "b"}
reviewedUsers = []string{"a"}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
num := reviewedNum(reviewers, reviewedUsers)
ctx.Convey("Then num should not be nil.", func(ctx convey.C) {
ctx.So(num, convey.ShouldEqual, 1)
})
})
})
}