diff --git a/README.md b/README.md index 41fcb64..7a2fc95 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,6 @@ gitlab review-bot -## Preconditions -- Gitlab version 13+ - ## Quick start ### Config diff --git a/cmd/app/app.go b/cmd/app/app.go index 90efeb5..b8dd05c 100644 --- a/cmd/app/app.go +++ b/cmd/app/app.go @@ -1,18 +1,17 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package app import ( @@ -35,8 +34,21 @@ func NewServerCommand() *cobra.Command { Short: "review bot", Long: `Review Bot.`, SilenceUsage: true, - RunE: run, + RunE: func(_ *cobra.Command, _ []string) error { + cfg := global.Environ() + if err := server.ParseConfigWithEnv(cfgFile, cfg, global.EnvPrefix); err != nil { + return err + } + if err := global.InitCfg(cfg); err != nil { + return err + } + s := server.New(&cfg.Server) + s.Handler = handler.New() + fmt.Println("Listen on", s.Addr) + return s.Run(context.Background()) + }, } + cfgFilePath := os.Getenv(global.EnvPrefix + "_CONFIG") if cfgFilePath == "" { cfgFilePath = "config/config.yaml" @@ -44,18 +56,3 @@ func NewServerCommand() *cobra.Command { cmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", cfgFilePath, "config file (default is $HOME/config.yaml)") return cmd } - -func run(cmd *cobra.Command, args []string) error { - ctx := context.Background() - cfg, err := global.ParseConfig(cfgFile) - if err != nil { - return err - } - if err := global.InitCfg(cfg); err != nil { - return err - } - s := server.New(&cfg.Server) - s.Handler = handler.New() - fmt.Println("Listen on", s.Addr) - return s.Run(ctx) -} diff --git a/cmd/main.go b/cmd/main.go index 73c50a5..fe87f93 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,18 +1,17 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package main import ( diff --git a/global/config.go b/global/config.go index 19e4efa..b878c95 100644 --- a/global/config.go +++ b/global/config.go @@ -1,29 +1,21 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package global import ( - "fmt" - "os" - "strings" - - "github.com/mitchellh/go-homedir" - "github.com/mitchellh/mapstructure" "github.com/pkgms/go/server" - "github.com/spf13/viper" "github.com/zc2638/review-bot/pkg/scm" ) @@ -48,32 +40,3 @@ func Environ() *Config { cfg.SCM.Type = "gitlab" return cfg } - -func ParseConfig(cfgPath string) (*Config, error) { - if cfgPath != "" { - viper.SetConfigFile(cfgPath) - } else { - home, err := homedir.Dir() - if err != nil { - return nil, err - } - viper.AddConfigPath(home) - viper.SetConfigName("config.yaml") - } - viper.SetEnvPrefix(EnvPrefix) - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viper.AutomaticEnv() - cfg := Environ() - if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(*os.PathError); ok { - fmt.Println("Warning: not find config file, use default.") - return cfg, nil - } - return nil, err - } - fmt.Println("Using config file:", viper.ConfigFileUsed()) - err := viper.Unmarshal(cfg, func(dc *mapstructure.DecoderConfig) { - dc.TagName = "json" - }) - return cfg, err -} diff --git a/global/init.go b/global/init.go index a1331d7..b75fe8a 100644 --- a/global/init.go +++ b/global/init.go @@ -1,18 +1,17 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package global import ( diff --git a/go.mod b/go.mod index c2380e4..099e323 100644 --- a/go.mod +++ b/go.mod @@ -5,21 +5,19 @@ go 1.16 require ( github.com/go-chi/chi v1.5.1 github.com/go-chi/cors v1.1.1 - github.com/mitchellh/go-homedir v1.1.0 - github.com/mitchellh/mapstructure v1.1.2 github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pkg/errors v0.8.1 - github.com/pkgms/go v0.0.0-20210922085647-58df1ef17e4f + github.com/pkgms/go v0.0.0-20220316065414-13c40cbbea1a github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.1.1 - github.com/spf13/viper v1.7.1 github.com/xanzy/go-gitlab v0.54.3 github.com/zc2638/swag v1.1.4 - golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect ) require ( + github.com/99nil/go v0.0.0-20210924013233-ebd290da12d6 github.com/golang-jwt/jwt/v4 v4.3.0 github.com/golang/protobuf v1.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect diff --git a/go.sum b/go.sum index f1b4d88..39a2d96 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/99nil/go v0.0.0-20210924013233-ebd290da12d6 h1:tU1UpYa5ao+4ht21gk02VCP4EDzupUffWya3xIH/euQ= +github.com/99nil/go v0.0.0-20210924013233-ebd290da12d6/go.mod h1:J/4Ei0TEJTTxM8z9WbenTh6pIElunP1px7yV+61dgP4= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -246,8 +248,8 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkgms/go v0.0.0-20201028070800-899b81726496/go.mod h1:dGRY2Ndm0PkdyhUEsQlaJiI4NVkKSOS5he7UgaULvkE= -github.com/pkgms/go v0.0.0-20210922085647-58df1ef17e4f h1:29MXvUdS/eCRsh08mv+Dy6BDx1YirHdZ3WMyBGitQOQ= -github.com/pkgms/go v0.0.0-20210922085647-58df1ef17e4f/go.mod h1:LbHvgTtlRyyaOV6ax3tx/YB0DZdW8o+EVUeLYZhJlPY= +github.com/pkgms/go v0.0.0-20220316065414-13c40cbbea1a h1:ecwbopPjyxXrRDizIqaaH5S9lxu5YunO56Z+794qa/w= +github.com/pkgms/go v0.0.0-20220316065414-13c40cbbea1a/go.mod h1:LbHvgTtlRyyaOV6ax3tx/YB0DZdW8o+EVUeLYZhJlPY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -411,8 +413,9 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/handler/api.go b/handler/api.go index 1bb58cc..c6b8863 100644 --- a/handler/api.go +++ b/handler/api.go @@ -18,6 +18,8 @@ package handler import ( "net/http" + "github.com/zc2638/review-bot/global" + "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/go-chi/cors" @@ -41,7 +43,7 @@ func New() http.Handler { apiDoc.AddEndpointFunc( home.Register, ) - mux.Post("/webhook", webhook.HandlerEvent()) + mux.Post("/webhook", webhook.HandlerEvent(&global.Cfg().SCM, global.SCM())) apiDoc.RegisterMuxWithData(mux, false) return mux diff --git a/handler/home/doc.go b/handler/home/doc.go index e662db3..a3c3c51 100644 --- a/handler/home/doc.go +++ b/handler/home/doc.go @@ -1,18 +1,17 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package home import ( @@ -22,8 +21,6 @@ import ( "github.com/zc2638/swag/swagger" ) -const tag = "home" - func Register(doc *swagger.API) { doc.AddEndpoint( endpoint.New( diff --git a/handler/home/home.go b/handler/home/home.go index 89a6932..1bf0603 100644 --- a/handler/home/home.go +++ b/handler/home/home.go @@ -1,6 +1,17 @@ -/** - * Created by zc on 2021/1/15. - */ +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package home import ( diff --git a/handler/webhook/event/comment.go b/handler/webhook/event/comment.go new file mode 100644 index 0000000..df5e1c8 --- /dev/null +++ b/handler/webhook/event/comment.go @@ -0,0 +1,120 @@ +// Copyright © 2022 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package event + +import ( + "github.com/sirupsen/logrus" + "github.com/xanzy/go-gitlab" + + "github.com/zc2638/review-bot/pkg/scm" + "github.com/zc2638/review-bot/pkg/util" +) + +func NewComment(si scm.Interface, pid string, ref string, prID int) (*Comment, error) { + pr, err := si.GetPullRequest(pid, prID) + if err != nil { + return nil, err + } + cfg, err := si.GetReviewConfig(pid, ref) + if err != nil { + return nil, err + } + return &Comment{ + si: si, + cfg: cfg, + pr: pr, + pid: pid, + prID: prID, + }, nil +} + +type Comment struct { + si scm.Interface + cfg *scm.ReviewConfig + pr *scm.PullRequest + + pid string + prID int +} + +func (e *Comment) newMerge() *Merge { + return &Merge{ + si: e.si, + cfg: e.cfg, + pr: e.pr, + pid: e.pid, + prID: e.prID, + } +} + +func (e *Comment) Process(event *gitlab.MergeCommentEvent) error { + // 获取评论内容 + note := event.ObjectAttributes.Note + + var addLabels, removeLabels []string + + // 匹配admin标签 + if _, ok := util.InStringSlice(e.cfg.Approvers, event.User.Username); ok { + label := scm.AdminSet.FuzzyLabelWithKey("FORCE-MERGE", note) + if label != nil { + logrus.Infof("Run force merge by %s on PR(%v) in Repo(%s)", event.User.Username, e.prID, e.pid) + return e.newMerge().merge(event.MergeRequest.LastCommit.ID) + } + + label = scm.AdminSet.FuzzyLabelWithKey("APPROVE", note) + if label != nil { + addLabels = append(addLabels, label.Name) + } + } + if _, ok := util.InStringSlice(e.cfg.Reviewers, event.User.Username); ok { + label := scm.AdminSet.FuzzyLabelWithKey("LGTM", note) + if label != nil { + addLabels = append(addLabels, label.Name) + } + } + + adds, removes := dealCommonLabel(e.cfg, event.Project.PathWithNamespace, note) + addLabels = append(addLabels, adds...) + removeLabels = append(removeLabels, removes...) + + if len(addLabels) == 0 && len(removeLabels) == 0 { + return nil + } + + approveLabelName := scm.RemoveSet.LabelByKey("APPROVE").Name + for _, v := range removeLabels { + if v == approveLabelName { + // TODO Don't handle the error for now, continue to execute down. + // PREMIUM version supports this feature. + if err := e.si.MergePullRequestApprove( + event.Project.PathWithNamespace, + event.MergeRequest.IID, + false, + ); err != nil { + logrus.Debugf("Remove approve failed: %v", err) + } + break + } + } + + opt := &scm.UpdatePullRequest{ + Labels: filterLabels(e.pr.Labels, addLabels, removeLabels), + AddLabels: addLabels, + RemoveLabels: removeLabels, + AssigneeID: event.MergeRequest.AssigneeID, + AssigneeIDs: event.MergeRequest.AssigneeIDs, + } + return e.si.UpdatePullRequest(event.Project.PathWithNamespace, event.MergeRequest.IID, opt) +} diff --git a/handler/webhook/event/common.go b/handler/webhook/event/common.go new file mode 100644 index 0000000..89ee2fe --- /dev/null +++ b/handler/webhook/event/common.go @@ -0,0 +1,102 @@ +// Copyright © 2022 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package event + +import ( + "strings" + + "github.com/99nil/go/sets" + + "github.com/sirupsen/logrus" + + "github.com/zc2638/review-bot/global" + "github.com/zc2638/review-bot/pkg/scm" +) + +func dealCommonLabel(config *scm.ReviewConfig, repo string, content string) (adds []string, removes []string) { + // 匹配common标签 + labels := scm.AddSet.FuzzyLabels(content) + for _, v := range labels { + adds = append(adds, v.Name) + } + labels = scm.RemoveSet.FuzzyLabels(content) + for _, v := range labels { + removes = append(removes, v.Name) + } + + // 匹配custom标签 + labels = scm.CustomSet.FuzzyLabels(content) + for _, v := range labels { + adds = append(adds, v.Name) + } + // 匹配移除custom标签 + labels = scm.CustomSet.FuzzyLabelsWithPrefix("remove", content) + for _, v := range labels { + removes = append(removes, v.Name) + } + + if len(config.CustomLabels) == 0 { + return + } + + // 匹配配置内的custom标签 + // 匹配移除配置内的custom标签 + var currentLabels []scm.Label + for _, v := range config.CustomLabels { + removeOrder := strings.TrimPrefix(v.Order, "/") + removeOrder = "/remove-" + removeOrder + if strings.Contains(content, removeOrder) { + removes = append(removes, v.Name) + } + if strings.Contains(content, v.Order) { + adds = append(adds, v.Name) + } + + if !scm.RepoCached().IsExist(repo, v.Name) { + if currentLabels == nil { + var err error + currentLabels, err = global.SCM().ListLabels(repo) + if err != nil { + logrus.Warningf("Sync custom labels failed: %s", err) + return + } + } + + exists := false + for _, vv := range currentLabels { + if vv.Name == v.Name { + exists = true + break + } + } + if !exists { + // label创建失败暂不处理 + if err := global.SCM().CreateLabel(repo, &v); err != nil { + logrus.Warningf("Create label failed: %s", err) + continue + } + } + scm.RepoCached().Add(repo, v.Name) + } + } + return +} + +func filterLabels(exists []string, adds []string, removes []string) []string { + s := sets.NewString(exists...) + s.Add(adds...) + s.Remove(removes...) + return s.List() +} diff --git a/handler/webhook/event/merge.go b/handler/webhook/event/merge.go new file mode 100644 index 0000000..62e1160 --- /dev/null +++ b/handler/webhook/event/merge.go @@ -0,0 +1,332 @@ +// Copyright © 2022 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package event + +import ( + "fmt" + "strings" + + "github.com/zc2638/review-bot/pkg/util" + + "github.com/sirupsen/logrus" + "github.com/xanzy/go-gitlab" + "golang.org/x/sync/errgroup" + + "github.com/zc2638/review-bot/global" + + "github.com/zc2638/review-bot/pkg/scm" +) + +func NewMerge(si scm.Interface, pid string, ref string, prID int, host string) (*Merge, error) { + pr, err := si.GetPullRequest(pid, prID) + if err != nil { + return nil, err + } + cfg, err := si.GetReviewConfig(pid, ref) + if err != nil { + return nil, err + } + return &Merge{ + si: si, + cfg: cfg, + pr: pr, + pid: pid, + prID: prID, + host: host, + }, nil +} + +type Merge struct { + si scm.Interface + cfg *scm.ReviewConfig + pr *scm.PullRequest + + pid string + prID int + host string +} + +func (e *Merge) Process(event *gitlab.MergeEvent) error { + // 处理merge事件 + var err error + switch event.ObjectAttributes.Action { + case "merge", "close", "reopen": + case "open": + err = e.open(event) + case "update": + err = e.update(event) + case "approved": + err = e.approve(event, true) + case "unapproved": + err = e.approve(event, false) + } + return err +} + +func (e *Merge) approve(event *gitlab.MergeEvent, approved bool) error { + if _, ok := util.InStringSlice(e.cfg.Approvers, event.User.Username); !ok { + return fmt.Errorf("this user(%s) does not have the approve permission, the operation is prohibited", event.User.Username) + } + + label := scm.AdminSet.LabelByKey("APPROVE").Name + opt := &scm.UpdatePullRequest{} + if approved { + opt.AddLabels = append(opt.AddLabels, label) + } else { + opt.RemoveLabels = append(opt.RemoveLabels, label) + } + opt.Labels = filterLabels(e.pr.Labels, opt.AddLabels, opt.RemoveLabels) + + e.completeAssignees(event, opt) + return e.si.UpdatePullRequest(e.pid, e.prID, opt) +} + +func (e *Merge) open(event *gitlab.MergeEvent) error { + // 初始化所有需要的label + _ = e.initLabels() + + var eg errgroup.Group + eg.Go(func() error { + // TODO 需要检查pipeline是否存在,所以暂不处理错误 + // 添加review check流程 + return e.si.UpdateBuildStatus( + event.Project.PathWithNamespace, + event.ObjectAttributes.LastCommit.ID, + scm.BuildStateRunning, + ) + }) + + eg.Go(func() error { + // 更新labels + adds, removes := dealCommonLabel(e.cfg, e.pid, event.ObjectAttributes.Description) + if len(adds) == 0 { + return nil + } + + opt := &scm.UpdatePullRequest{ + Labels: filterLabels(e.pr.Labels, adds, removes), + AddLabels: adds, + RemoveLabels: removes, + } + e.completeAssignees(event, opt) + return e.si.UpdatePullRequest(e.pid, e.prID, opt) + }) + + eg.Go(func() error { + // TODO 暂时忽略错误,需要处理,比如重复尝试5次 + // 添加自动评论 + err := e.addAutoComment(event) + if err != nil { + logrus.Warningf("open pull request add auto comment failed: %s", err) + } + return err + }) + return eg.Wait() +} + +func (e *Merge) update(event *gitlab.MergeEvent) error { + // TODO 更新commit自动移除LGTM + + // 当label存在do-not-merge时,禁止合并 + var lgtmExists, approvedExists bool + for _, v := range event.Labels { + if strings.Contains(v.Name, scm.DoNotMerge) { + approvedExists = false + break + } + label := scm.AdminSet.LabelByKey("LGTM") + if label != nil && v.Name == label.Name { + lgtmExists = true + } + label = scm.AdminSet.LabelByKey("APPROVE") + if label != nil && v.Name == label.Name { + approvedExists = true + } + } + + // 尝试添加review check流程,如果存在报错则忽略 + if !lgtmExists || !approvedExists { + _ = global.SCM().UpdateBuildStatus( + event.Project.PathWithNamespace, + event.ObjectAttributes.LastCommit.ID, + scm.BuildStateRunning, + ) + return nil + } + // 当label满足lgtm和approved的时,执行分支合并 + return e.merge(event.ObjectAttributes.LastCommit.ID) +} + +func (e *Merge) merge(lastCommitID string) error { + var title, prefix string + if e.cfg.PRConfig.SquashWithTitle { + title = e.pr.Title + } else { + titleData := strings.Split(e.pr.Description, "") + if len(titleData) > 1 { + titleData = strings.Split(titleData[1], "") + if len(titleData) > 0 { + title = strings.TrimSuffix(titleData[0], "\n") + title = strings.TrimSpace(title) + title = strings.TrimLeft(title, ">") + title = strings.TrimSpace(title) + } + } + } + + for _, v := range e.pr.Labels { + label := scm.CustomSet.Label(v) + if label != nil && strings.TrimSpace(label.Short) != "" { + prefix = label.Short + ":" + break + } + } + + opt := &scm.MergePullRequest{ + MergeWhenPipelineSucceeds: true, + ShouldRemoveSourceBranch: true, + } + if title != "" { + opt.Squash = true + opt.SquashCommitMessage = prefix + title + } + // 完成review check流程 + if err := e.si.UpdateBuildStatus(e.pid, lastCommitID, scm.BuildStateSuccess); err != nil { + logrus.Errorln(err) + } + // 执行合并 + return e.si.MergePullRequest(e.pid, e.prID, opt) +} + +func (e *Merge) initLabels() error { + cache := scm.Cached() + if exists := cache.IsExist(e.pid); exists { + return nil + } + + labels, err := e.si.ListLabels(e.pid) + if err != nil { + return err + } + + var allLabels []scm.Label + allLabels = append(allLabels, scm.AdminSet.Labels()...) + allLabels = append(allLabels, scm.AddSet.Labels()...) + allLabels = append(allLabels, scm.CustomSet.Labels()...) + for _, v := range allLabels { + var exists bool + for _, label := range labels { + if v.Name == label.Name { + exists = true + break + } + } + if !exists { + // label创建失败暂不处理 + _ = e.si.CreateLabel(e.pid, &v) + } + } + + cache.Add(e.pid) + return nil +} + +func (e *Merge) completeAssignees(event *gitlab.MergeEvent, opt *scm.UpdatePullRequest) { + getAssigneeIDs := func(event *gitlab.MergeEvent) []int { + var ids []int + for _, v := range event.Assignees { + ids = append(ids, v.ID) + } + return ids + } + if event.Assignees != nil { + opt.AssigneeIDs = getAssigneeIDs(event) + } + if event.Assignee != nil { + opt.AssigneeID = event.Assignee.ID + } +} + +func (e *Merge) addAutoComment(event *gitlab.MergeEvent) error { + repo := event.Project.PathWithNamespace + id := event.ObjectAttributes.IID + authorID := event.ObjectAttributes.AuthorID + + count := 0 + reviewers := make([]string, 0, 2) + + // 获取reviewers的用户id + members := e.getMembers(e.cfg.Reviewers) + for _, v := range members { + if v.ID == authorID { + // 跳过 请求提交者 进行review + continue + } + reviewers = append(reviewers, "@"+v.Username) + count++ + if count > 1 { // 限制每次请求两位reviewer + break + } + } + + var reviewContent string + if len(reviewers) > 0 { + reviewData := make([]string, 0, 4) + reviewData = append(reviewData, "等待") + reviewData = append(reviewData, reviewers...) + reviewData = append(reviewData, "处理 review 请求") + reviewContent = strings.Join(reviewData, " ") + } + + commandHelpURL := e.host + "/command-help" + commitMsg := "合并后的 commit 信息为 title 内容" + if !e.cfg.PRConfig.SquashWithTitle { + commitMsg = "合并后的 commit 信息为 描述中``之间的内容" + } + + content := `您好 ` + event.User.Username + `,请求创建成功! +` + reviewContent + ` + +请注意,合并时将会压缩所有 commits ,` + commitMsg + `。 +可以在[【此处】](` + commandHelpURL + `)找到 bot 接受的完整指令列表。 + +Reviewers(代码审查人员)可以通过评论` + "`/lgtm`" + `来表示审查通过。 +Approvers(请求审批人员)可以通过评论` + "`/approve`" + `来表示审批通过。 +Approvers(请求审批人员)可以通过评论` + "`/force-merge`" + `来进行强制合并。 +` + return global.SCM().CreatePullRequestComment(repo, id, content) +} + +func (e *Merge) getMembers(names []string) map[string]scm.ProjectMember { + var projectMembers []scm.ProjectMember + members := make(map[string]scm.ProjectMember) + for _, name := range names { + if member, ok := scm.UserCached().Get(name); ok { + members[name] = member + continue + } + if projectMembers == nil { + // TODO 无需处理错误,错误时会返回nil + projectMembers, _ = e.si.ListProjectMembers(e.pid) + for _, v := range projectMembers { + scm.UserCached().Add(v.Username, v) + } + } + if member, ok := scm.UserCached().Get(name); ok { + members[name] = member + } + } + return members +} diff --git a/handler/webhook/webhook.go b/handler/webhook/webhook.go index bc95f2d..65bd739 100644 --- a/handler/webhook/webhook.go +++ b/handler/webhook/webhook.go @@ -1,18 +1,17 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package webhook import ( @@ -20,20 +19,20 @@ import ( "io" "io/ioutil" "net/http" - "strings" + + "github.com/xanzy/go-gitlab" + + "github.com/zc2638/review-bot/handler/webhook/event" "github.com/pkg/errors" "github.com/pkgms/go/ctr" - "github.com/sirupsen/logrus" - "github.com/xanzy/go-gitlab" - "golang.org/x/sync/errgroup" "github.com/zc2638/review-bot/global" "github.com/zc2638/review-bot/pkg/scm" "github.com/zc2638/review-bot/pkg/util" ) -func HandlerEvent() http.HandlerFunc { +func HandlerEvent(cfg *scm.Config, si scm.Interface) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Gitlab-Token") claims, err := util.JwtParse(token, global.JWTSecret) @@ -41,7 +40,7 @@ func HandlerEvent() http.HandlerFunc { ctr.Unauthorized(w, errors.New("Signature Token Invalid")) return } - if claims.Auth.CheckSign(global.Cfg().SCM.Secret) { + if claims.Auth.CheckSign(cfg.Secret) { ctr.Unauthorized(w, errors.New("Signature Invalid")) return } @@ -58,11 +57,28 @@ func HandlerEvent() http.HandlerFunc { ctr.BadRequest(w, err) return } - switch event := webhook.(type) { + switch e := webhook.(type) { case *gitlab.MergeEvent: - err = processMergeEvent(event, r.Host) + var scheme = "http" + if r.URL != nil && r.URL.Scheme != "" { + scheme = r.URL.Scheme + } + host := fmt.Sprintf("%s://%s", scheme, r.Host) + mergeEvent, err := event.NewMerge( + si, e.Project.PathWithNamespace, e.Project.DefaultBranch, e.ObjectAttributes.IID, host) + if err != nil { + ctr.InternalError(w, err) + return + } + err = mergeEvent.Process(e) case *gitlab.MergeCommentEvent: - err = processMergeCommentEvent(event) + commentEvent, err := event.NewComment( + si, e.Project.PathWithNamespace, e.Project.DefaultBranch, e.MergeRequest.IID) + if err != nil { + ctr.InternalError(w, err) + return + } + err = commentEvent.Process(e) } if err != nil { ctr.InternalError(w, err) @@ -71,503 +87,3 @@ func HandlerEvent() http.HandlerFunc { ctr.Success(w) } } - -// 处理merge事件 -func processMergeEvent(event *gitlab.MergeEvent, host string) error { - var err error - switch event.ObjectAttributes.Action { - case "merge", "close", "reopen": - case "open": - err = openEvent(event, host) - case "update": - err = updateEvent(event) - case "approved": - err = approveEvent(event, true) - case "unapproved": - err = approveEvent(event, false) - } - return err -} - -// 处理merge评论事件 -func processMergeCommentEvent(event *gitlab.MergeCommentEvent) error { - // 获取评论内容 - note := event.ObjectAttributes.Note - - // 获取仓库review配置 - config, err := global.SCM().GetReviewConfig( - event.Project.PathWithNamespace, - event.Project.DefaultBranch, - ) - if err != nil { - return err - } - - var addLabels, removeLabels []string - - // 匹配admin标签 - if _, ok := util.InStringSlice(config.Reviewers, event.User.Username); ok { - label := scm.AdminSet.FuzzyLabelWithKey("LGTM", note) - if label != nil { - addLabels = append(addLabels, label.Name) - } - } - if _, ok := util.InStringSlice(config.Approvers, event.User.Username); ok { - label := scm.AdminSet.FuzzyLabelWithKey("APPROVE", note) - if label != nil { - addLabels = append(addLabels, label.Name) - } - label = scm.AdminSet.FuzzyLabelWithKey("FORCE-MERGE", note) - if label != nil { - logrus.Infof("Run force merge by %s on PR(%v) in Repo(%s)", - event.User.Username, event.MergeRequest.IID, event.Project.PathWithNamespace) - return commentMerge(event, config) - } - } - - adds, removes := dealCommonLabel(config, event.Project.PathWithNamespace, note) - addLabels = append(addLabels, adds...) - removeLabels = append(removeLabels, removes...) - - if len(addLabels) == 0 && len(removeLabels) == 0 { - return nil - } - - approveLabelName := scm.RemoveSet.LabelByKey("APPROVE").Name - for _, v := range removeLabels { - if v == approveLabelName { - // TODO Don't handle the error for now, continue to execute down. - // PREMIUM version supports this feature. - _ = global.SCM().MergePullRequestApprove( - event.Project.PathWithNamespace, - event.MergeRequest.IID, - false, - ) - break - } - } - - opt := &scm.UpdatePullRequest{ - AddLabels: addLabels, - RemoveLabels: removeLabels, - AssigneeID: event.MergeRequest.AssigneeID, - AssigneeIDs: event.MergeRequest.AssigneeIDs, - } - return global.SCM().UpdatePullRequest(event.Project.PathWithNamespace, event.MergeRequest.IID, opt) -} - -func dealCommonLabel(config *scm.ReviewConfig, repo string, content string) (adds []string, removes []string) { - // 匹配common标签 - labels := scm.AddSet.FuzzyLabels(content) - for _, v := range labels { - adds = append(adds, v.Name) - } - labels = scm.RemoveSet.FuzzyLabels(content) - for _, v := range labels { - removes = append(removes, v.Name) - } - - // 匹配custom标签 - labels = scm.CustomSet.FuzzyLabels(content) - for _, v := range labels { - adds = append(adds, v.Name) - } - // 匹配移除custom标签 - labels = scm.CustomSet.FuzzyLabelsWithPrefix("remove", content) - for _, v := range labels { - removes = append(removes, v.Name) - } - - if len(config.CustomLabels) == 0 { - return - } - - // 匹配配置内的custom标签 - // 匹配移除配置内的custom标签 - var currentLabels []scm.Label - for _, v := range config.CustomLabels { - removeOrder := strings.TrimPrefix(v.Order, "/") - removeOrder = "/remove-" + removeOrder - if strings.Contains(content, removeOrder) { - removes = append(removes, v.Name) - } - if strings.Contains(content, v.Order) { - adds = append(adds, v.Name) - } - - if !scm.RepoCached().IsExist(repo, v.Name) { - if currentLabels == nil { - var err error - currentLabels, err = global.SCM().ListLabels(repo) - if err != nil { - logrus.Warningf("Sync custom labels failed: %s", err) - return - } - } - - exists := false - for _, vv := range currentLabels { - if vv.Name == v.Name { - exists = true - break - } - } - if !exists { - // label创建失败暂不处理 - if err := global.SCM().CreateLabel(repo, &v); err != nil { - logrus.Warningf("Create label failed: %s", err) - continue - } - } - scm.RepoCached().Add(repo, v.Name) - } - } - return -} - -func getMembers(pid string, names []string) map[string]scm.ProjectMember { - var projectMembers []scm.ProjectMember - members := make(map[string]scm.ProjectMember) - for _, name := range names { - if member, ok := scm.UserCached().Get(name); ok { - members[name] = member - continue - } - if projectMembers == nil { - projectMembers, _ = global.SCM().ListProjectMembers(pid) - for _, v := range projectMembers { - scm.UserCached().Add(v.Username, v) - } - } - if member, ok := scm.UserCached().Get(name); ok { - members[name] = member - } - } - return members -} - -func addAutoComment(config *scm.ReviewConfig, event *gitlab.MergeEvent, host string) error { - repo := event.Project.PathWithNamespace - id := event.ObjectAttributes.IID - authorID := event.ObjectAttributes.AuthorID - - count := 0 - reviewers := make([]string, 0, 2) - - // 获取reviewers的用户id - members := getMembers(repo, config.Reviewers) - for _, v := range members { - if v.ID == authorID { // 跳过请求提交者自己进行review - continue - } - reviewers = append(reviewers, "@"+v.Username) - count++ - if count > 1 { // 限制每次请求两位reviewer - break - } - } - - var reviewContent string - if len(reviewers) > 0 { - reviewData := make([]string, 0, 4) - reviewData = append(reviewData, "等待") - reviewData = append(reviewData, reviewers...) - reviewData = append(reviewData, "处理 review 请求") - reviewContent = strings.Join(reviewData, " ") - } - - commandHelpURL := "http://" + host + "/command-help" - commitMsg := "合并后的 commit 信息为 title 内容" - if !config.PRConfig.SquashWithTitle { - commitMsg = "合并后的 commit 信息为 描述中``之间的内容" - } - - content := `恭喜您,请求创建成功! -` + reviewContent + ` - -请注意,合并时将会压缩所有 commits ,` + commitMsg + `。 -可以在[【此处】](` + commandHelpURL + `)找到 bot 接受的完整指令列表。 - -Reviewers(代码审查人员)可以通过评论` + "`/lgtm`" + `来表示审查通过。 -Approvers(请求审批人员)可以通过评论` + "`/approve`" + `来表示审批通过。 -Approvers(请求审批人员)可以通过评论` + "`/force-merge`" + `来进行强制合并。 -` - return global.SCM().CreatePullRequestComment(repo, id, content) -} - -// 当pull request创建时,添加标签 -func openEvent(event *gitlab.MergeEvent, host string) error { - // 获取仓库review配置 - config, err := global.SCM().GetReviewConfig( - event.Project.PathWithNamespace, - event.Project.DefaultBranch, // 获取默认分支的配置 - ) - if err != nil { - return err - } - - // 初始化所有需要的label - _ = initLabels(event) - - var eg errgroup.Group - eg.Go(func() error { - // TODO 需要检查pipeline是否存在,所以暂不处理错误 - // 添加review check流程 - err := global.SCM().UpdateBuildStatus( - event.Project.PathWithNamespace, - event.ObjectAttributes.LastCommit.ID, - scm.BuildStateRunning, - ) - return err - }) - - eg.Go(func() error { - // 更新labels - adds, removes := dealCommonLabel(config, event.Project.PathWithNamespace, event.ObjectAttributes.Description) - if len(adds) == 0 { - return nil - } - - opt := &scm.UpdatePullRequest{ - AddLabels: adds, - RemoveLabels: removes, - } - completeAssignees(event, opt) - return global.SCM().UpdatePullRequest(event.Project.PathWithNamespace, event.ObjectAttributes.IID, opt) - }) - - eg.Go(func() error { - // TODO 暂时忽略错误,需要处理,重复尝试5次 - // 添加自动评论 - err = addAutoComment(config, event, host) - if err != nil { - logrus.Warningf("open pull request add auto comment failed: %s", err) - } - return err - }) - return eg.Wait() -} - -func updateEvent(event *gitlab.MergeEvent) error { - // TODO 更新commit自动移除LGTM - - // 获取仓库review配置 - config, err := global.SCM().GetReviewConfig( - event.Project.PathWithNamespace, - event.Project.DefaultBranch, // 获取默认分支的配置 - ) - if err != nil { - return err - } - - // 当label存在do-not-merge时,禁止合并 - var lgtmExists, approvedExists bool - for _, v := range event.Labels { - if strings.Contains(v.Name, scm.DoNotMerge) { - approvedExists = false - break - } - label := scm.AdminSet.LabelByKey("LGTM") - if label != nil && v.Name == label.Name { - lgtmExists = true - } - label = scm.AdminSet.LabelByKey("APPROVE") - if label != nil && v.Name == label.Name { - approvedExists = true - } - } - - // 尝试添加review check流程,如果存在报错则忽略 - if !lgtmExists || !approvedExists { - _ = global.SCM().UpdateBuildStatus( - event.Project.PathWithNamespace, - event.ObjectAttributes.LastCommit.ID, - scm.BuildStateRunning, - ) - return nil - } - - // 当label满足lgtm和approved的时,执行分支合并 - var title, prefix string - if config.PRConfig.SquashWithTitle { - title = event.ObjectAttributes.Title - } else { - desc := event.ObjectAttributes.Description - titleData := strings.Split(desc, "") - if len(titleData) > 1 { - titleData = strings.Split(titleData[1], "") - if len(titleData) > 0 { - title = strings.TrimSuffix(titleData[0], "\n") - title = strings.TrimSpace(title) - title = strings.TrimLeft(title, ">") - title = strings.TrimSpace(title) - } - } - } - - for _, v := range event.Labels { - label := scm.CustomSet.Label(v.Name) - if label != nil && strings.TrimSpace(label.Short) != "" { - prefix = label.Short + ":" - break - } - } - - opt := &scm.MergePullRequest{MergeWhenPipelineSucceeds: true} - if event.ObjectAttributes.MergeParams != nil { - opt.ShouldRemoveSourceBranch = event.ObjectAttributes.MergeParams.ForceRemoveSourceBranch - } - if title != "" { - opt.Squash = true - opt.SquashCommitMessage = prefix + title - } - - // 完成review check流程 - if err := global.SCM().UpdateBuildStatus( - event.Project.PathWithNamespace, - event.ObjectAttributes.LastCommit.ID, - scm.BuildStateSuccess, - ); err != nil { - return err - } - - // 执行合并 - return global.SCM().MergePullRequest(event.Project.PathWithNamespace, event.ObjectAttributes.IID, opt) -} - -func approveEvent(event *gitlab.MergeEvent, approved bool) error { - // 获取仓库review配置 - config, err := global.SCM().GetReviewConfig( - event.Project.PathWithNamespace, - event.Project.DefaultBranch, // 获取默认分支的配置 - ) - if err != nil { - return err - } - if _, ok := util.InStringSlice(config.Approvers, event.User.Username); !ok { - return fmt.Errorf("this user(%s) does not have the approve permission, the operation is prohibited", event.User.Username) - } - - label := scm.AdminSet.LabelByKey("APPROVE").Name - opt := &scm.UpdatePullRequest{} - if approved { - opt.AddLabels = append(opt.AddLabels, label) - } else { - opt.RemoveLabels = append(opt.RemoveLabels, label) - } - - completeAssignees(event, opt) - return global.SCM().UpdatePullRequest(event.Project.PathWithNamespace, event.ObjectAttributes.IID, opt) -} - -// 初始化所有label -func initLabels(event *gitlab.MergeEvent) error { - cache := scm.Cached() - if exists := cache.IsExist(event.Project.PathWithNamespace); exists { - return nil - } - - labels, err := global.SCM().ListLabels(event.Project.PathWithNamespace) - if err != nil { - return err - } - - var allLabels []scm.Label - allLabels = append(allLabels, scm.AdminSet.Labels()...) - allLabels = append(allLabels, scm.AddSet.Labels()...) - allLabels = append(allLabels, scm.CustomSet.Labels()...) - for _, v := range allLabels { - var exists bool - for _, label := range labels { - if v.Name == label.Name { - exists = true - break - } - } - if !exists { - // label创建失败暂不处理 - _ = global.SCM().CreateLabel(event.Project.PathWithNamespace, &v) - } - } - - cache.Add(event.Project.PathWithNamespace) - return nil -} - -// 通过评论合并PR -func commentMerge(event *gitlab.MergeCommentEvent, config *scm.ReviewConfig) error { - // 获取pr详细信息 - pr, err := global.SCM().GetPullRequest( - event.Project.PathWithNamespace, - event.MergeRequest.IID, - ) - if err != nil { - return err - } - - var title, prefix string - if config.PRConfig.SquashWithTitle { - title = pr.Title - } else { - titleData := strings.Split(pr.Description, "") - if len(titleData) > 1 { - titleData = strings.Split(titleData[1], "") - if len(titleData) > 0 { - title = strings.TrimSuffix(titleData[0], "\n") - title = strings.TrimSpace(title) - title = strings.TrimLeft(title, ">") - title = strings.TrimSpace(title) - } - } - } - - for _, v := range pr.Labels { - label := scm.CustomSet.Label(v) - if label != nil && strings.TrimSpace(label.Short) != "" { - prefix = label.Short + ":" - break - } - } - - opt := &scm.MergePullRequest{ - MergeWhenPipelineSucceeds: true, - } - if event.MergeRequest.MergeParams != nil { - opt.ShouldRemoveSourceBranch = pr.ForceRemoveSourceBranch - } - if title != "" { - opt.Squash = true - opt.SquashCommitMessage = prefix + title - } - // 完成review check流程 - if err := global.SCM().UpdateBuildStatus( - event.Project.PathWithNamespace, - event.MergeRequest.LastCommit.ID, - scm.BuildStateSuccess, - ); err != nil { - logrus.Errorln(err) - } - - // 执行合并 - return global.SCM().MergePullRequest( - event.Project.PathWithNamespace, - event.MergeRequest.IID, - opt, - ) -} - -func completeAssignees(event *gitlab.MergeEvent, opt *scm.UpdatePullRequest) { - getAssigneeIDs := func(event *gitlab.MergeEvent) []int { - var ids []int - for _, v := range event.Assignees { - ids = append(ids, v.ID) - } - return ids - } - if event.Assignees != nil { - opt.AssigneeIDs = getAssigneeIDs(event) - } - if event.Assignee != nil { - opt.AssigneeID = event.Assignee.ID - } -} diff --git a/pkg/scm/cache.go b/pkg/scm/cache.go index b35f61b..25ac5e4 100644 --- a/pkg/scm/cache.go +++ b/pkg/scm/cache.go @@ -1,18 +1,17 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package scm var cache = Cache{} diff --git a/pkg/scm/gitlab.go b/pkg/scm/gitlab.go index ffb7ea1..6de78e1 100644 --- a/pkg/scm/gitlab.go +++ b/pkg/scm/gitlab.go @@ -1,18 +1,17 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package scm import ( @@ -113,6 +112,7 @@ func (s *gitlabClient) GetPullRequest(pid string, prID int) (*PullRequest, error func (s *gitlabClient) UpdatePullRequest(pid string, prID int, data *UpdatePullRequest) error { opt := &gitlab.UpdateMergeRequestOptions{ + Labels: (*gitlab.Labels)(&data.Labels), AddLabels: (*gitlab.Labels)(&data.AddLabels), RemoveLabels: (*gitlab.Labels)(&data.RemoveLabels), } @@ -131,8 +131,7 @@ func (s *gitlabClient) UpdatePullRequest(pid string, prID int, data *UpdatePullR if len(data.AssigneeIDs) > 0 { opt.AssigneeIDs = &data.AssigneeIDs } - logrus.Debugf("UpdatePullRequest before options: %+v", opt) - logrus.Debugf("UpdateMergeRequest after options: %+v", opt) + logrus.Debugf("UpdateMergeRequest options: %+v", opt) _, _, err := s.client.MergeRequests.UpdateMergeRequest(pid, prID, opt) return err } diff --git a/pkg/scm/label.go b/pkg/scm/label.go index b004222..b6fc49d 100644 --- a/pkg/scm/label.go +++ b/pkg/scm/label.go @@ -1,18 +1,17 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package scm import "strings" diff --git a/pkg/scm/scm.go b/pkg/scm/scm.go index 0bef520..2d3ef52 100644 --- a/pkg/scm/scm.go +++ b/pkg/scm/scm.go @@ -1,18 +1,17 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package scm type Config struct { diff --git a/pkg/scm/types.go b/pkg/scm/types.go index 8e6c443..230a3c5 100644 --- a/pkg/scm/types.go +++ b/pkg/scm/types.go @@ -1,18 +1,17 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package scm import "time" diff --git a/pkg/util/jwt.go b/pkg/util/jwt.go index 88b3502..aafc5be 100644 --- a/pkg/util/jwt.go +++ b/pkg/util/jwt.go @@ -1,18 +1,17 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package util import ( diff --git a/pkg/util/util.go b/pkg/util/util.go index 2a9b66e..b4deb76 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1,18 +1,17 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package util func InStringSlice(ss []string, str string) (index int, exists bool) { diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go index c485f79..b4fa4f0 100644 --- a/pkg/util/util_test.go +++ b/pkg/util/util_test.go @@ -1,18 +1,17 @@ -/* -Copyright © 2021 zc2638 . +// Copyright © 2021 zc2638 . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ package util import (