github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/plugins/retitle/retitle.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package retitle implements the retitle plugin 18 package retitle 19 20 import ( 21 "regexp" 22 "strings" 23 24 "github.com/sirupsen/logrus" 25 26 "sigs.k8s.io/prow/pkg/config" 27 "sigs.k8s.io/prow/pkg/github" 28 "sigs.k8s.io/prow/pkg/pluginhelp" 29 "sigs.k8s.io/prow/pkg/plugins" 30 "sigs.k8s.io/prow/pkg/plugins/invalidcommitmsg" 31 "sigs.k8s.io/prow/pkg/plugins/trigger" 32 ) 33 34 const ( 35 // pluginName defines this plugin's registered name. 36 pluginName = "retitle" 37 ) 38 39 var ( 40 retitleRe = regexp.MustCompile(`(?mi)^/retitle\s*(.*)$`) 41 ) 42 43 func init() { 44 plugins.RegisterGenericCommentHandler(pluginName, handleGenericCommentEvent, helpProvider) 45 } 46 47 func helpProvider(config *plugins.Configuration, _ []config.OrgRepo) (*pluginhelp.PluginHelp, error) { 48 var configMsg string 49 if config.Retitle.AllowClosedIssues { 50 configMsg = "The retitle plugin also allows retitling closed/merged issues and PRs." 51 } else { 52 configMsg = "The retitle plugin does not allow retitling closed/merged issues and PRs." 53 } 54 yamlSnippet, err := plugins.CommentMap.GenYaml(&plugins.Configuration{ 55 Retitle: plugins.Retitle{ 56 AllowClosedIssues: true, 57 }, 58 }) 59 if err != nil { 60 logrus.WithError(err).Warnf("cannot generate comments for %s plugin", pluginName) 61 } 62 pluginHelp := &pluginhelp.PluginHelp{ 63 Description: "The retitle plugin allows users to re-title pull requests and issues where GitHub permissions don't allow them to.", 64 Config: map[string]string{ 65 "": configMsg, 66 }, 67 Snippet: yamlSnippet, 68 } 69 pluginHelp.AddCommand(pluginhelp.Command{ 70 Usage: "/retitle <title>", 71 Description: "Edits the pull request or issue title.", 72 Featured: true, 73 WhoCanUse: "Collaborators on the repository.", 74 Examples: []string{"/retitle New Title"}, 75 }) 76 return pluginHelp, nil 77 } 78 79 func handleGenericCommentEvent(pc plugins.Agent, e github.GenericCommentEvent) error { 80 var ( 81 org = e.Repo.Owner.Login 82 repo = e.Repo.Name 83 ) 84 return handleGenericComment(pc.GitHubClient, func(user string) (bool, error) { 85 t := pc.PluginConfig.TriggerFor(org, repo) 86 trustedResponse, err := trigger.TrustedUser(pc.GitHubClient, t.OnlyOrgMembers, t.TrustedApps, t.TrustedOrg, user, org, repo) 87 return trustedResponse.IsTrusted, err 88 }, pc.PluginConfig.Retitle.AllowClosedIssues, pc.Logger, e) 89 } 90 91 type githubClient interface { 92 CreateComment(owner, repo string, number int, comment string) error 93 GetPullRequest(org, repo string, number int) (*github.PullRequest, error) 94 EditPullRequest(org, repo string, number int, pr *github.PullRequest) (*github.PullRequest, error) 95 GetIssue(org, repo string, number int) (*github.Issue, error) 96 EditIssue(org, repo string, number int, issue *github.Issue) (*github.Issue, error) 97 } 98 99 func handleGenericComment(gc githubClient, isTrusted func(string) (bool, error), allowClosedIssues bool, log *logrus.Entry, gce github.GenericCommentEvent) error { 100 // If closed/merged issues and PRs shouldn't be considered, 101 // return early if issue state is not open. 102 if !allowClosedIssues && gce.IssueState != "open" { 103 return nil 104 } 105 106 // Only consider new comments. 107 if gce.Action != github.GenericCommentActionCreated { 108 return nil 109 } 110 111 // Make sure they are requesting a re-title 112 if !retitleRe.MatchString(gce.Body) { 113 return nil 114 } 115 116 var ( 117 org = gce.Repo.Owner.Login 118 repo = gce.Repo.Name 119 number = gce.Number 120 user = gce.User.Login 121 ) 122 123 trusted, err := isTrusted(user) 124 if err != nil { 125 log.WithError(err).Error("Could not check if user was trusted.") 126 return err 127 } 128 if !trusted { 129 return gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(gce.Body, gce.HTMLURL, user, `Re-titling can only be requested by trusted users, like repository collaborators.`)) 130 } 131 132 matches := retitleRe.FindStringSubmatch(gce.Body) 133 if matches == nil { 134 // this shouldn't happen since we checked above 135 return nil 136 } 137 newTitle := strings.TrimSpace(matches[1]) 138 if newTitle == "" { 139 return gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(gce.Body, gce.HTMLURL, user, `Titles may not be empty.`)) 140 } 141 142 if invalidcommitmsg.AtMentionRegex.MatchString(newTitle) || invalidcommitmsg.CloseIssueRegex.MatchString(newTitle) { 143 return gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(gce.Body, gce.HTMLURL, user, `Titles may not contain [keywords](https://help.github.com/articles/closing-issues-using-keywords) which can automatically close issues and at(@) mentions.`)) 144 } 145 146 if gce.IsPR { 147 pr, err := gc.GetPullRequest(org, repo, number) 148 if err != nil { 149 return err 150 } 151 pr.Title = newTitle 152 _, err = gc.EditPullRequest(org, repo, number, pr) 153 return err 154 } 155 issue, err := gc.GetIssue(org, repo, number) 156 if err != nil { 157 return err 158 } 159 issue.Title = newTitle 160 _, err = gc.EditIssue(org, repo, number, issue) 161 return err 162 }