-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1babbca
commit 3f64366
Showing
10 changed files
with
828 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.git | ||
testdata | ||
*_test.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.