From e7405dd5dd1e1b8535ca71461903154d4eca90bc Mon Sep 17 00:00:00 2001 From: sercan tor Date: Sun, 6 Aug 2023 12:40:01 +0100 Subject: [PATCH 1/3] feat: add support for labels --- auth/auth.go | 6 ++++++ cmd/git-xargs.go | 1 + common/common.go | 5 +++++ config/config.go | 6 ++++++ main.go | 1 + repository/repo-operations.go | 9 +++++++++ stats/stats.go | 3 +++ 7 files changed, 31 insertions(+) diff --git a/auth/auth.go b/auth/auth.go index 3332f55..47cbabd 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -24,6 +24,10 @@ type githubRepositoriesService interface { ListByOrg(ctx context.Context, org string, opts *github.RepositoryListByOrgOptions) ([]*github.Repository, *github.Response, error) } +type githubIssuesService interface { + AddLabelsToIssue(ctx context.Context, owner string, repo string, number int, labels []string) ([]*github.Label, *github.Response, error) +} + // GithubClient is the data structure that is common between production code and test code. In production code, // go-github satisfies the PullRequests and Repositories service interfaces, whereas in test the concrete // implementations for these same services are mocks that return a static slice of pointers to GitHub repositories, @@ -32,12 +36,14 @@ type githubRepositoriesService interface { type GithubClient struct { PullRequests githubPullRequestService Repositories githubRepositoriesService + Issues githubIssuesService } func NewClient(client *github.Client) GithubClient { return GithubClient{ PullRequests: client.PullRequests, Repositories: client.Repositories, + Issues: client.Issues, } } diff --git a/cmd/git-xargs.go b/cmd/git-xargs.go index 5f96f99..17f99d7 100644 --- a/cmd/git-xargs.go +++ b/cmd/git-xargs.go @@ -34,6 +34,7 @@ func parseGitXargsConfig(c *cli.Context) (*config.GitXargsConfig, error) { config.PullRequestDescription = c.String("pull-request-description") config.Reviewers = c.StringSlice("reviewers") config.TeamReviewers = c.StringSlice("team-reviewers") + config.PullRequestLabelSlice = c.StringSlice("label") config.ReposFile = c.String("repos") config.GithubOrg = c.String("github-org") config.RepoSlice = c.StringSlice("repo") diff --git a/common/common.go b/common/common.go index de66162..fc7e2d0 100644 --- a/common/common.go +++ b/common/common.go @@ -17,6 +17,7 @@ const ( PullRequestDescriptionFlagName = "pull-request-description" PullRequestReviewersFlagName = "reviewers" PullRequestTeamReviewersFlagName = "team-reviewers" + PullRequestLabelFlagName = "label" SecondsToWaitBetweenPrsFlagName = "seconds-between-prs" DefaultCommitMessage = "git-xargs programmatic commit" DefaultPullRequestTitle = "git-xargs programmatic pull request" @@ -91,6 +92,10 @@ var ( Name: PullRequestTeamReviewersFlagName, Usage: "A list of GitHub team names to request reviews from", } + GenericPullRequestLabelFlag = cli.StringSliceFlag{ + Name: PullRequestLabelFlagName, + Usage: "A single GitHub label to add to the pull request. Can be invoked multiple times with different labels", + } 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.", diff --git a/config/config.go b/config/config.go index 9c0555d..48ecaaf 100644 --- a/config/config.go +++ b/config/config.go @@ -26,6 +26,7 @@ type GitXargsConfig struct { PullRequestDescription string Reviewers []string TeamReviewers []string + PullRequestLabelSlice []string ReposFile string GithubOrg string RepoSlice []string @@ -58,6 +59,7 @@ func NewGitXargsConfig() *GitXargsConfig { PullRequestDescription: common.DefaultPullRequestDescription, Reviewers: []string{}, TeamReviewers: []string{}, + PullRequestLabelSlice: []string{}, ReposFile: "", GithubOrg: "", RepoSlice: []string{}, @@ -91,3 +93,7 @@ func NewGitXargsTestConfig() *GitXargsConfig { func (c *GitXargsConfig) HasReviewers() bool { return len(c.Reviewers) > 0 || len(c.TeamReviewers) > 0 } + +func (c *GitXargsConfig) HasLabels() bool { + return len(c.PullRequestLabelSlice) > 0 +} diff --git a/main.go b/main.go index f25ea6b..010ea56 100644 --- a/main.go +++ b/main.go @@ -73,6 +73,7 @@ func setupApp() *cli.App { common.GenericPullRequestDescriptionFlag, common.GenericPullRequestReviewersFlag, common.GenericPullRequestTeamReviewersFlag, + common.GenericPullRequestLabelFlag, common.GenericSecondsToWaitFlag, common.GenericMaxPullRequestRetriesFlag, common.GenericSecondsToWaitWhenRateLimitedFlag, diff --git a/repository/repo-operations.go b/repository/repo-operations.go index d1f3761..23cac74 100644 --- a/repository/repo-operations.go +++ b/repository/repo-operations.go @@ -594,6 +594,15 @@ func openPullRequest(config *config.GitXargsConfig, pr types.OpenPrRequest) erro } + // if the user supplied label information on the pull request, initiate a separate request to add label(s) + if config.HasLabels() { + _, _, labelRequestErr := config.GithubClient.Issues.AddLabelsToIssue(context.Background(), *pr.Repo.GetOwner().Login, pr.Repo.GetName(), githubPR.GetNumber(), config.PullRequestLabelSlice) + if labelRequestErr != nil { + config.Stats.TrackSingle(stats.AddLabelsToIssueErr, pr.Repo) + } + + } + if config.Draft { config.Stats.TrackDraftPullRequest(pr.Repo.GetName(), githubPR.GetHTMLURL()) } else { diff --git a/stats/stats.go b/stats/stats.go index 777c5a9..eaef929 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -78,6 +78,8 @@ const ( 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" + // AddLabelsToIssueErr denotes a repo whose follow up request to add labels to the opened pull request failed + AddLabelsToIssueErr types.Event = "add-labels-to-issue-error" ) var allEvents = []types.AnnotatedEvent{ @@ -112,6 +114,7 @@ var allEvents = []types.AnnotatedEvent{ {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"}, + {Event: AddLabelsToIssueErr, Description: "Repos whose request to add labels 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 From a8ca0f6bb014cc0901b2ee5b26466e199bb74c4e Mon Sep 17 00:00:00 2001 From: sercan tor Date: Sun, 6 Aug 2023 12:53:02 +0100 Subject: [PATCH 2/3] docs: add the option to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c456a8..86d2989 100644 --- a/README.md +++ b/README.md @@ -436,6 +436,7 @@ echo "gruntwork-io/terragrunt gruntwork-io/terratest" | git-xargs \ | `--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 | +| `--label` | By default, the PR is opened without any labels. Use this flag multiple times to specify multiple labels that should be attached to the PR upon creation (e.g., --label bug --label enhancement). | String | No | | `--keep-cloned-repositories` | By default, git-xargs will delete the repositories it clones to your temporary file directory once it has completed processing that repo, to save space on your machine. If you wish to retain the local repositories, pass this flag. | Bool | No | ## Best practices, tips and tricks From 22aced76fef01a02136371fe44ebfba34b79b9a6 Mon Sep 17 00:00:00 2001 From: sercan tor Date: Sun, 6 Aug 2023 12:57:21 +0100 Subject: [PATCH 3/3] tests: support mock for issues --- mocks/mocks.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/mocks/mocks.go b/mocks/mocks.go index 18ec7b8..f9806ce 100644 --- a/mocks/mocks.go +++ b/mocks/mocks.go @@ -92,6 +92,15 @@ func (m mockGithubRepositoriesService) ListByOrg(ctx context.Context, org string return m.Repositories, m.Response, nil } +type mockGithubIssuesService struct { + Issue *github.Issue + Response *github.Response +} + +func (m mockGithubIssuesService) AddLabelsToIssue(ctx context.Context, owner string, repo string, number int, labels []string) ([]*github.Label, *github.Response, error) { + return []*github.Label{}, m.Response, nil +} + // ConfigureMockGithubClient returns a valid GithubClient configured for testing purposes, complete with the mocked services func ConfigureMockGithubClient() auth.GithubClient { // Call the same NewClient method that is used by the actual CLI to obtain a GitHub client that calls the @@ -126,6 +135,10 @@ func ConfigureMockGithubClient() auth.GithubClient { }, Response: &github.Response{}, } + client.Issues = mockGithubIssuesService{ + Issue: &github.Issue{}, + Response: &github.Response{}, + } return client }