Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
LiYanghang00 committed Dec 13, 2024
1 parent 1babbca commit 3f64366
Show file tree
Hide file tree
Showing 10 changed files with 828 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.git
testdata
*_test.go
59 changes: 59 additions & 0 deletions approve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package main

import (
"fmt"
"regexp"

"github.com/sirupsen/logrus"
)

const approvedLabel = "approved"

var (
regAddApprove = regexp.MustCompile(`(?mi)^/approve\s*$`)
regRemoveApprove = regexp.MustCompile(`(?mi)^/approve cancel\s*$`)
)

func (bot *robot) handleApprove(configmap *repoConfig, comment, commenter, author, org, repo, number string) error {
if regAddApprove.MatchString(comment) {
return bot.AddApprove(commenter, author, org, repo, number, configmap.LgtmCountsRequired)
}

if regRemoveApprove.MatchString(comment) {
return bot.removeApprove(commenter, author, org, repo, number, configmap.LgtmCountsRequired)
}

return nil
}

func (bot *robot) AddApprove(commenter, author, org, repo, number string, lgtmCounts uint) error {
logrus.Infof("AddApprove, commenter: %s, author: %s, org: %s, repo: %s, number: %s", commenter, author, org, repo, number)
if pass, ok := bot.cli.CheckPermission(org, repo, commenter); pass && ok {
if ok := bot.cli.AddPRLabels(org, repo, number, []string{approvedLabel}); !ok {
return fmt.Errorf("failed to add label on pull request")
}
if ok := bot.cli.CreatePRComment(org, repo, number, fmt.Sprintf(commentAddLabel, approvedLabel, commenter)); !ok {
return fmt.Errorf("failed to comment on pull request")
}
} else if !pass {
bot.cli.CreatePRComment(org, repo, number, fmt.Sprintf(commentNoPermissionForLgtmLabel, commenter))
} else {
return fmt.Errorf("failed to add label on pull request")
}
return nil
}

func (bot *robot) removeApprove(commenter, author, org, repo, number string, lgtmCounts uint) error {
logrus.Infof("removeApprove, commenter: %s, author: %s, org: %s, repo: %s, number: %s", commenter, author, org, repo, number)

if pass, ok := bot.cli.CheckPermission(org, repo, commenter); pass && ok {
bot.cli.RemovePRLabels(org, repo, number, []string{approvedLabel})
bot.cli.CreatePRComment(org, repo, number, fmt.Sprintf(commentRemovedLabel, approvedLabel, commenter))
} else if !pass {
bot.cli.CreatePRComment(org, repo, number, fmt.Sprintf(commentNoPermissionForLabel, commenter, "remove", approvedLabel))
} else {
return fmt.Errorf("failed to remove label on pull request")
}

return nil
}
155 changes: 155 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved.
//
// 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 (
"errors"
"fmt"

"github.com/opensourceways/server-common-lib/config"
)

// configuration holds a list of repoConfig configurations.
type configuration struct {
ConfigItems []repoConfig `json:"config_items,omitempty"`
UserMarkFormat string `json:"user_mark_format,omitempty"`
PlaceholderCommenter string `json:"placeholder_commenter"`
}

// Validate to check the configmap data's validation, returns an error if invalid
func (c *configuration) Validate() error {
if c == nil {
return errors.New("configuration is nil")
}

// Validate each repo configuration
items := c.ConfigItems
for i := range items {
if err := items[i].validate(); err != nil {
return err
}
}

return nil
}

// get retrieves a repoConfig for a given organization and repository.
// Returns the repoConfig if found, otherwise returns nil.
func (c *configuration) get(org, repo string) *repoConfig {
if c == nil || len(c.ConfigItems) == 0 {
return nil
}

for i := range c.ConfigItems {
ok, _ := c.ConfigItems[i].RepoFilter.CanApply(org, org+"/"+repo)
if ok {
return &c.ConfigItems[i]
}
}

return nil
}

// repoConfig is a configuration struct for a organization and repository.
// It includes a RepoFilter and a boolean value indicating if an issue can be closed only when its linking PR exists.
type repoConfig struct {
// RepoFilter is used to filter repositories.
config.RepoFilter
// LegalOperator means who can add or remove labels legally
LegalOperator string `json:"legal_operator,omitempty"`

// LgtmCountsRequired specifies the number of lgtm label which will be need for the pr.
// When it is greater than 1, the lgtm label is composed of 'lgtm-login'.
// The default value is 1 which means the lgtm label is itself.
LgtmCountsRequired uint `json:"lgtm_counts_required,omitempty"`

// LabelsForMerge specifies the labels except approved and lgtm relevant labels
// that must be available to merge pr
LabelsForMerge []string `json:"labels_for_merge,omitempty"`

// LabelsNotAllowMerge means that if pull request has these labels, it can not been merged
// even all conditions are met
LabelsNotAllowMerge []string `json:"labels_not_allow_merge,omitempty"`

// MergeMethod is the method to merge PR.
// The default method of merge. Valid options are squash and merge.
MergeMethod string `json:"merge_method,omitempty"`
}

type freezeFile struct {
Owner string `json:"owner" required:"true"`
Repo string `json:"repo" required:"true"`
Branch string `json:"branch" required:"true"`
Path string `json:"path" required:"true"`
}

type branchKeeper struct {
Owner string `json:"owner" required:"true"`
Repo string `json:"repo" required:"true"`
Branch string `json:"branch" required:"true"`
}

func (b branchKeeper) validate() error {
if b.Owner == "" {
return fmt.Errorf("missing owner of branch keeper")
}

if b.Repo == "" {
return fmt.Errorf("missing repo of branch keeper")
}

if b.Branch == "" {
return fmt.Errorf("missing branch of branch keeper")
}

return nil
}

func (f freezeFile) validate() error {
if f.Owner == "" {
return fmt.Errorf("missing owner of freeze file")
}

if f.Repo == "" {
return fmt.Errorf("missing repo of freeze file")
}

if f.Branch == "" {
return fmt.Errorf("missing branch of freeze file")
}

if f.Path == "" {
return fmt.Errorf("missing path of freeze file")
}

return nil
}

// validate to check the repoConfig data's validation, returns an error if invalid
func (c *repoConfig) validate() error {
// If the bot is not configured to monitor any repositories, return an error.
if len(c.Repos) == 0 {
return errors.New("the repositories configuration can not be empty")
}

return c.RepoFilter.Validate()
}

type litePRCommiter struct {
// Email is the one of committer in a commit when a PR is lite
Email string `json:"email" required:"true"`

// Name is the one of committer in a commit when a PR is lite
Name string `json:"name" required:"true"`
}
111 changes: 111 additions & 0 deletions lgtm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package main

import (
"fmt"
"regexp"
"strings"

"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/sets"
)

const (
// the gitee platform limits the maximum length of label to 20.
labelLenLimit = 20
lgtmLabel = "lgtm"

commentAddLGTMBySelf = "***lgtm*** can not be added in your self-own pull request. :astonished:"
commentClearLabelCaseByPRUpdate = `New code changes of pr are detected and remove these labels ***%s***. :flushed: `
commentClearLabelCaseByReopenPR = `When PR is reopened, remove these labels ***%s***. :flushed: `
commentNoPermissionForLgtmLabel = `Thanks for your review, ***%s***, your opinion is very important to us.:wave:
The maintainers will consider your advice carefully.`
commentNoPermissionForLabel = `
***@%s*** has no permission to %s ***%s*** label in this pull request. :astonished:
Please contact to the collaborators in this repository.`
commentAddLabel = `***%s*** was added to this pull request by: ***%s***. :wave:
**NOTE:** If this pull request is not merged while all conditions are met, comment "/check-pr" to try again. :smile: `
commentRemovedLabel = `***%s*** was removed in this pull request by: ***%s***. :flushed: `
)

var (
regAddLgtm = regexp.MustCompile(`(?mi)^/lgtm\s*$`)
regRemoveLgtm = regexp.MustCompile(`(?mi)^/lgtm cancel\s*$`)
)

func (bot *robot) handleLGTM(configmap *repoConfig, comment, commenter, author, org, repo, number string) error {
if regAddLgtm.MatchString(comment) {
return bot.addLGTM(commenter, author, org, repo, number, configmap.LgtmCountsRequired)
}

if regRemoveLgtm.MatchString(comment) {
return bot.removeLGTM(commenter, author, org, repo, number, configmap.LgtmCountsRequired)
}

return nil
}

func (bot *robot) addLGTM(commenter, author, org, repo, number string, lgtmCounts uint) error {
logrus.Infof("addLGTM, commenter: %s, author: %s, org: %s, repo: %s, number: %s", commenter, author, org, repo, number)
if author == commenter {
if ok := bot.cli.CreatePRComment(org, repo, number, commentAddLGTMBySelf); !ok {
return fmt.Errorf("failed to comment on pull request")
}
return nil
}
if pass, ok := bot.cli.CheckPermission(org, repo, commenter); pass && ok {
label := genLGTMLabel(commenter, lgtmCounts)

if ok := bot.cli.AddPRLabels(org, repo, number, []string{label}); !ok {
return fmt.Errorf("failed to add label on pull request")
}
if ok := bot.cli.CreatePRComment(org, repo, number, fmt.Sprintf(commentAddLabel, label, commenter)); !ok {
return fmt.Errorf("failed to comment on pull request")
}
} else {
bot.cli.CreatePRComment(org, repo, number, fmt.Sprintf(commentNoPermissionForLgtmLabel, commenter))
}
return nil

}

func (bot *robot) removeLGTM(commenter, author, org, repo, number string, lgtmCounts uint) error {
logrus.Infof("removeLGTM, commenter: %s, author: %s, org: %s, repo: %s, number: %s", commenter, author, org, repo, number)
if author == commenter {
bot.cli.RemovePRLabels(org, repo, number, getLGTMLabelsOnPR(bot.getPRLabelSet(org, repo, number)))
} else {
if pass, ok := bot.cli.CheckPermission(org, repo, commenter); pass && ok {
label := genLGTMLabel(commenter, lgtmCounts)
bot.cli.RemovePRLabels(org, repo, number, []string{label})
bot.cli.CreatePRComment(org, repo, number, fmt.Sprintf(commentRemovedLabel, label, commenter))
} else {
bot.cli.CreatePRComment(org, repo, number, fmt.Sprintf(commentNoPermissionForLabel, commenter, "remove", lgtmLabel))
}

}
return nil
}

func genLGTMLabel(commenter string, lgtmCount uint) string {
if lgtmCount <= 1 {
return lgtmLabel
}

l := fmt.Sprintf("%s-%s", lgtmLabel, strings.ToLower(commenter))
if len(l) > labelLenLimit {
return l[:labelLenLimit]
}

return l
}

func getLGTMLabelsOnPR(labels sets.Set[string]) []string {
var r []string

for l := range labels {
if strings.HasPrefix(l, lgtmLabel) {
r = append(r, l)
}
}

return r
}
35 changes: 35 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved.
//
// 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 (
"flag"
"github.com/opensourceways/robot-framework-lib/framework"
"os"
)

const component = "robot-universal-review"

func main() {

opt := new(robotOptions)
// Gather the necessary arguments from command line for project startup
cnf, token := opt.gatherOptions(flag.NewFlagSet(os.Args[0], flag.ExitOnError), os.Args[1:]...)
if opt.interrupt {
return
}

bot := newRobot(cnf, token)
framework.StartupServer(framework.NewServer(bot, opt.service), opt.service)
}
Loading

0 comments on commit 3f64366

Please sign in to comment.