Skip to content

Commit

Permalink
Implement support for requesting reviewers (#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
zackproser authored Nov 6, 2022
1 parent ed97a6d commit f68178c
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 14 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,8 @@ echo "gruntwork-io/terragrunt gruntwork-io/terratest" | git-xargs \
| `--max-pr-retries` | The number of seconds to wait between opening serial pull requests. If you are being rate limited, continue to increase this value until rate limiting eases. Default: `3` seconds. | Integer | No |
| `--seconds-to-wait-when-rate-limited` | The number of seconds to pause once git-xargs has detected it has been rate limited. Note that this buffer is in addition to the value of --seconds-between-prs. If you are regularly being rate limited, increase this value until rate limiting eases. Default: `60` seconds. | Integer | No |
| `--no-skip-ci` | By default, git-xargs will prepend \"[skip ci]\" to its commit messages to prevent large git-xargs jobs from creating expensive CI jobs excessively. If you pass the `--no-skip-ci` flag, then git-xargs will not prepend \"[skip ci]\". Default: false, meaning that \"[skip ci]\" will be prepended to commit messages. | Bool | No |

| `--reviewers` | An optional slice of GitHub usernames, separated by commas, to request reviews from after a pull request is successfully opened. Default: empty slice, meaning that no reviewers will be requested. | String | No |
| `--team-reviewers` | An optional slice of GitHub team names, separated by commas, to request reviews from after a pull request is successfully opened. Default: empty slice, meaning that no team reviewers will be requested. IMPORTANT: Please read and understand [the GitHub restrictions](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review) on this functionality before using it! Only certain GitHub organizations / payment plans support this functionality. | String | No |
## Best practices, tips and tricks

### Write your script to run against a single repo
Expand Down
1 change: 1 addition & 0 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
type githubPullRequestService interface {
Create(ctx context.Context, owner string, name string, pr *github.NewPullRequest) (*github.PullRequest, *github.Response, error)
List(ctx context.Context, owner string, repo string, opts *github.PullRequestListOptions) ([]*github.PullRequest, *github.Response, error)
RequestReviewers(ctx context.Context, owner, repo string, number int, reviewers github.ReviewersRequest) (*github.PullRequest, *github.Response, error)
}

// The go-github package satisfies this Repositories service's interface in production
Expand Down
2 changes: 2 additions & 0 deletions cmd/git-xargs.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ func parseGitXargsConfig(c *cli.Context) (*config.GitXargsConfig, error) {
config.CommitMessage = c.String("commit-message")
config.PullRequestTitle = c.String("pull-request-title")
config.PullRequestDescription = c.String("pull-request-description")
config.Reviewers = c.StringSlice("reviewers")
config.TeamReviewers = c.StringSlice("team-reviewers")
config.ReposFile = c.String("repos")
config.GithubOrg = c.String("github-org")
config.RepoSlice = c.StringSlice("repo")
Expand Down
10 changes: 10 additions & 0 deletions common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const (
BaseBranchFlagName = "base-branch-name"
PullRequestTitleFlagName = "pull-request-title"
PullRequestDescriptionFlagName = "pull-request-description"
PullRequestReviewersFlagName = "reviewers"
PullRequestTeamReviewersFlagName = "team-reviewers"
SecondsToWaitBetweenPrsFlagName = "seconds-between-prs"
DefaultCommitMessage = "git-xargs programmatic commit"
DefaultPullRequestTitle = "git-xargs programmatic pull request"
Expand Down Expand Up @@ -80,6 +82,14 @@ var (
Usage: "The description to add to pull requests opened by git-xargs",
Value: DefaultPullRequestDescription,
}
GenericPullRequestReviewersFlag = cli.StringSliceFlag{
Name: PullRequestReviewersFlagName,
Usage: "A list of GitHub usernames to request reviews from",
}
GenericPullRequestTeamReviewersFlag = cli.StringSliceFlag{
Name: PullRequestTeamReviewersFlagName,
Usage: "A list of GitHub team names to request reviews from",
}
GenericSecondsToWaitFlag = cli.IntFlag{
Name: SecondsToWaitBetweenPrsFlagName,
Usage: "The number of seconds to sleep between pull requests in order to respect GitHub API rate limits. Increase this number if you are being rate limited regularly. Defaults to 12 seconds.",
Expand Down
8 changes: 8 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type GitXargsConfig struct {
CommitMessage string
PullRequestTitle string
PullRequestDescription string
Reviewers []string
TeamReviewers []string
ReposFile string
GithubOrg string
RepoSlice []string
Expand Down Expand Up @@ -53,6 +55,8 @@ func NewGitXargsConfig() *GitXargsConfig {
CommitMessage: common.DefaultCommitMessage,
PullRequestTitle: common.DefaultPullRequestTitle,
PullRequestDescription: common.DefaultPullRequestDescription,
Reviewers: []string{},
TeamReviewers: []string{},
ReposFile: "",
GithubOrg: "",
RepoSlice: []string{},
Expand Down Expand Up @@ -81,3 +85,7 @@ func NewGitXargsTestConfig() *GitXargsConfig {

return config
}

func (c *GitXargsConfig) HasReviewers() bool {
return len(c.Reviewers) > 0 || len(c.TeamReviewers) > 0
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ go 1.14
require (
github.com/go-git/go-git/v5 v5.3.0
github.com/golang/protobuf v1.4.3 // indirect
github.com/google/go-github v17.0.0+incompatible // indirect
github.com/google/go-github/v43 v43.0.0
github.com/google/go-github/v48 v48.0.0 // indirect
github.com/gruntwork-io/go-commons v0.8.2
github.com/pterm/pterm v0.12.42
github.com/sirupsen/logrus v1.7.0
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,16 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.0.0-20200110202235-f4fb41bf00a3/go.mod h1:2wIuQute9+hhWqvL3vEI7YB0EKluF4WcPzI1eAliazk=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg=
github.com/google/go-github/v43 v43.0.0 h1:y+GL7LIsAIF2NZlJ46ZoC/D1W1ivZasT0lnWHMYPZ+U=
github.com/google/go-github/v43 v43.0.0/go.mod h1:ZkTvvmCXBvsfPpTHXnH/d2hP9Y0cTbvN9kr5xqyXOIc=
github.com/google/go-github/v48 v48.0.0 h1:9H5fWVXFK6ZsRriyPbjtnFAkJnoj0WKFtTYfpCRrTm8=
github.com/google/go-github/v48 v48.0.0/go.mod h1:dDlehKBDo850ZPvCTK0sEqTCVWcrGl2LcDiajkYi89Y=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ func setupApp() *cli.App {
common.GenericCommitMessageFlag,
common.GenericPullRequestTitleFlag,
common.GenericPullRequestDescriptionFlag,
common.GenericPullRequestReviewersFlag,
common.GenericPullRequestTeamReviewersFlag,
common.GenericSecondsToWaitFlag,
common.GenericMaxPullRequestRetriesFlag,
common.GenericSecondsToWaitWhenRateLimitedFlag,
Expand Down
33 changes: 20 additions & 13 deletions mocks/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,45 @@ import (
// Mock *github.Repository slice that is returned from the mock Repositories service in test
var ownerName = "gruntwork-io"

var repoName1 = "terragrunt"
var repoName2 = "terratest"
var repoName3 = "fetch"
var repoName4 = "terraform-kubernetes-helm"
var (
repoName1 = "terragrunt"
repoName2 = "terratest"
repoName3 = "fetch"
repoName4 = "terraform-kubernetes-helm"
)

var repoURL1 = "https://github.com/gruntwork-io/terragrunt"
var repoURL2 = "https://github.com/gruntwork-io/terratest"
var repoURL3 = "https://github.com/gruntwork-io/fetch"
var repoURL4 = "https://github.com/gruntwork-io/terraform-kubernetes-helm"
var (
repoURL1 = "https://github.com/gruntwork-io/terragrunt"
repoURL2 = "https://github.com/gruntwork-io/terratest"
repoURL3 = "https://github.com/gruntwork-io/fetch"
repoURL4 = "https://github.com/gruntwork-io/terraform-kubernetes-helm"
)

var archivedFlag = true

var MockGithubRepositories = []*github.Repository{
&github.Repository{
{
Owner: &github.User{
Login: &ownerName,
},
Name: &repoName1,
HTMLURL: &repoURL1,
},
&github.Repository{
{
Owner: &github.User{
Login: &ownerName,
},
Name: &repoName2,
HTMLURL: &repoURL2,
},
&github.Repository{
{
Owner: &github.User{
Login: &ownerName,
},
Name: &repoName3,
HTMLURL: &repoURL3,
},
&github.Repository{
{
Owner: &github.User{
Login: &ownerName,
},
Expand All @@ -69,6 +73,10 @@ func (m mockGithubPullRequestService) List(ctx context.Context, owner string, re
return []*github.PullRequest{m.PullRequest}, m.Response, nil
}

func (m mockGithubPullRequestService) RequestReviewers(ctx context.Context, owner, repo string, number int, reviewers github.ReviewersRequest) (*github.PullRequest, *github.Response, error) {
return m.PullRequest, m.Response, nil
}

// This mocks the Repositories service in go-github that is used in production to call the associated GitHub endpoint
type mockGithubRepositoriesService struct {
Repository *github.Repository
Expand Down Expand Up @@ -98,7 +106,6 @@ func ConfigureMockGithubClient() auth.GithubClient {
Repository: MockGithubRepositories[0],
Repositories: MockGithubRepositories,
Response: &github.Response{

Response: &http.Response{
StatusCode: 200,
},
Expand Down
15 changes: 15 additions & 0 deletions repository/repo-operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,21 @@ func openPullRequest(config *config.GitXargsConfig, pr types.OpenPrRequest) erro
"Pull Request URL": githubPR.GetHTMLURL(),
}).Debug("Successfully opened pull request")

reviewersRequest := github.ReviewersRequest{
NodeID: githubPR.NodeID,
Reviewers: config.Reviewers,
TeamReviewers: config.TeamReviewers,
}

// If the user supplied reviewer information on the pull request, initiate a separate request to ask for reviews
if config.HasReviewers() {
_, _, reviewRequestErr := config.GithubClient.PullRequests.RequestReviewers(context.Background(), *pr.Repo.GetOwner().Login, pr.Repo.GetName(), githubPR.GetNumber(), reviewersRequest)
if reviewRequestErr != nil {
config.Stats.TrackSingle(stats.RequestReviewersErr, pr.Repo)
}

}

if config.Draft {
config.Stats.TrackDraftPullRequest(pr.Repo.GetName(), githubPR.GetHTMLURL())
} else {
Expand Down
3 changes: 3 additions & 0 deletions stats/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ const (
PRFailedDueToRateLimitsErr types.Event = "pr-failed-due-to-rate-limits"
// PRFailedAfterMaximumRetriesErr denotes a repo whose pull requests all failed to be created via GitHub following the maximum number of retries
PRFailedAfterMaximumRetriesErr types.Event = "pr-failed-after-maximum-retries"
// RequestReviewersErr denotes a repo whose follow up request to add reviewers to the opened pull request failed
RequestReviewersErr types.Event = "request-reviewers-error"
)

var allEvents = []types.AnnotatedEvent{
Expand Down Expand Up @@ -109,6 +111,7 @@ var allEvents = []types.AnnotatedEvent{
{Event: BaseBranchTargetInvalidErr, Description: "Repos that did not have the branch specified by --base-branch-name"},
{Event: PRFailedDueToRateLimitsErr, Description: "Repos whose initial Pull Request failed to be created due to GitHub rate limits"},
{Event: PRFailedAfterMaximumRetriesErr, Description: "Repos whose Pull Request failed to be created after the maximum number of retries"},
{Event: RequestReviewersErr, Description: "Repos whose request to add reviewers to the opened pull request failed"},
}

// RunStats will be a stats-tracker class that keeps score of which repos were touched, which were considered for update, which had branches made, PRs made, which were missing workflows or contexts, or had out of date workflows syntax values, etc
Expand Down

0 comments on commit f68178c

Please sign in to comment.