diff --git a/README.md b/README.md index 5d18adbd..0c61a3b2 100755 --- a/README.md +++ b/README.md @@ -532,6 +532,7 @@ Flags: -P, --project strings The name, including owner of a GitLab project in the format "ownerName/repoName". -R, --repo strings The name, including owner of a GitHub repository in the format "ownerName/repoName". -r, --reviewers strings The username of the reviewers to be added on the pull request. + -a, --assignees strings The username of the assignees to be added on the pull request. --skip-pr Skip pull request and directly push to the branch. -T, --token string The GitHub/GitLab personal access token. Can also be set using the GITHUB_TOKEN/GITLAB_TOKEN/GITEA_TOKEN/BITBUCKET_SERVER_TOKEN environment variable. -U, --user strings The name of a user. All repositories owned by that user will be used. diff --git a/cmd/cmd-run.go b/cmd/cmd-run.go index 46c88933..e0e10d8e 100755 --- a/cmd/cmd-run.go +++ b/cmd/cmd-run.go @@ -45,6 +45,7 @@ func RunCmd() *cobra.Command { cmd.Flags().BoolP("dry-run", "d", false, "Run without pushing changes or creating pull requests.") cmd.Flags().StringP("author-name", "", "", "Name of the committer. If not set, the global git config setting will be used.") cmd.Flags().StringP("author-email", "", "", "Email of the committer. If not set, the global git config setting will be used.") + cmd.Flags().StringSliceP("assignees", "a", nil, "The username of the assignees to be added on the pull request.") configureGit(cmd) configurePlatform(cmd) configureRunPlatform(cmd, true) @@ -74,6 +75,7 @@ func run(cmd *cobra.Command, args []string) error { authorName, _ := flag.GetString("author-name") authorEmail, _ := flag.GetString("author-email") strOutput, _ := flag.GetString("output") + assignees, _ := flag.GetStringSlice("assignees") if concurrent < 1 { return errors.New("concurrent runs can't be less than one") @@ -168,6 +170,7 @@ func run(cmd *cobra.Command, args []string) error { SkipPullRequest: skipPullRequest, CommitAuthor: commitAuthor, BaseBranch: baseBranchName, + Assignees: assignees, Concurrent: concurrent, diff --git a/internal/git/pullrequest.go b/internal/git/pullrequest.go index c8b88c20..9ca6e2d5 100755 --- a/internal/git/pullrequest.go +++ b/internal/git/pullrequest.go @@ -13,6 +13,7 @@ type NewPullRequest struct { Base string Reviewers []string // The username of all reviewers + Assignees []string } // PullRequestStatus is the status of a pull request, including statuses of the last commit diff --git a/internal/multigitter/run.go b/internal/multigitter/run.go index 71904b3b..cbcc47d0 100755 --- a/internal/multigitter/run.go +++ b/internal/multigitter/run.go @@ -50,6 +50,7 @@ type Runner struct { DryRun bool CommitAuthor *git.CommitAuthor BaseBranch string // The base branch of the PR, use default branch if not set + Assignees []string Concurrent int SkipPullRequest bool // If set, the script will run directly on the base-branch without creating any PR @@ -278,6 +279,7 @@ func (r *Runner) runSingleRepo(ctx context.Context, repo git.Repository) (git.Pu Head: r.FeatureBranch, Base: baseBranch, Reviewers: getReviewers(r.Reviewers, r.MaxReviewers), + Assignees: r.Assignees, }) if err != nil { return nil, err diff --git a/internal/scm/bitbucketserver/bitbucket_server.go b/internal/scm/bitbucketserver/bitbucket_server.go index fc253138..76aed57f 100644 --- a/internal/scm/bitbucketserver/bitbucket_server.go +++ b/internal/scm/bitbucketserver/bitbucket_server.go @@ -246,26 +246,21 @@ func (b *BitbucketServer) CreatePullRequest(ctx context.Context, repo git.Reposi client := newClient(ctx, b.config) - var usersWithMetadata []bitbucketv1.UserWithMetadata - for _, reviewer := range newPR.Reviewers { - response, err := client.DefaultApi.GetUser(reviewer) - if err != nil { - return nil, err - } - - var userWithLinks bitbucketv1.UserWithLinks - err = mapstructure.Decode(response.Values, &userWithLinks) - if err != nil { - return nil, err - } + reviewers, err := b.getUsersWithLinks(newPR.Reviewers, client) + if err != nil { + return nil, err + } - usersWithMetadata = append(usersWithMetadata, bitbucketv1.UserWithMetadata{User: userWithLinks}) + assignees, err := b.getUsersWithLinks(newPR.Assignees, client) + if err != nil { + return nil, err } response, err := client.DefaultApi.CreatePullRequest(r.project, r.name, bitbucketv1.PullRequest{ Title: newPR.Title, Description: newPR.Body, - Reviewers: usersWithMetadata, + Reviewers: reviewers, + Participants: assignees, FromRef: bitbucketv1.PullRequestRef{ ID: fmt.Sprintf("refs/heads/%s", newPR.Head), Repository: bitbucketv1.Repository{ @@ -297,6 +292,27 @@ func (b *BitbucketServer) CreatePullRequest(ctx context.Context, repo git.Reposi return newPullRequest(pullRequestResp), nil } +func (b *BitbucketServer) getUsersWithLinks(usernames []string, client *bitbucketv1.APIClient) ([]bitbucketv1.UserWithMetadata, error) { + var usersWithMetadata []bitbucketv1.UserWithMetadata + + for _, username := range usernames { + response, err := client.DefaultApi.GetUser(username) + if err != nil { + return nil, err + } + + var userWithLinks bitbucketv1.UserWithLinks + err = mapstructure.Decode(response.Values, &userWithLinks) + if err != nil { + return nil, err + } + + usersWithMetadata = append(usersWithMetadata, bitbucketv1.UserWithMetadata{User: userWithLinks}) + } + + return usersWithMetadata, nil +} + // GetPullRequests Gets the latest pull requests from repositories based on the scm configuration func (b *BitbucketServer) GetPullRequests(ctx context.Context, branchName string) ([]git.PullRequest, error) { client := newClient(ctx, b.config) diff --git a/internal/scm/gitea/gitea.go b/internal/scm/gitea/gitea.go index ffd933fb..fd832d74 100644 --- a/internal/scm/gitea/gitea.go +++ b/internal/scm/gitea/gitea.go @@ -211,6 +211,7 @@ func (g *Gitea) CreatePullRequest(ctx context.Context, repo git.Repository, prRe Base: newPR.Base, Title: newPR.Title, Body: newPR.Body, + Assignees: newPR.Assignees, }) if err != nil { return nil, errors.Wrap(err, "could not create pull request") diff --git a/internal/scm/github/github.go b/internal/scm/github/github.go index 9b2e382f..f21df0e1 100755 --- a/internal/scm/github/github.go +++ b/internal/scm/github/github.go @@ -246,6 +246,10 @@ func (g Github) CreatePullRequest(ctx context.Context, repo git.Repository, prRe return nil, err } + if err := g.addAssignees(ctx, r, newPR, pr); err != nil { + return nil, err + } + return convertPullRequest(pr), nil } @@ -274,6 +278,14 @@ func (g Github) addReviewers(ctx context.Context, repo repository, newPR git.New return err } +func (g Github) addAssignees(ctx context.Context, repo repository, newPR git.NewPullRequest, createdPR *github.PullRequest) error { + if len(newPR.Assignees) == 0 { + return nil + } + _, _, err := g.ghClient.Issues.AddAssignees(ctx, repo.ownerName, repo.name, createdPR.GetNumber(), newPR.Assignees) + return err +} + // GetPullRequests gets all pull requests of with a specific branch func (g Github) GetPullRequests(ctx context.Context, branchName string) ([]git.PullRequest, error) { // TODO: If this is implemented with the GitHub v4 graphql api, it would be much faster diff --git a/internal/scm/gitlab/gitlab.go b/internal/scm/gitlab/gitlab.go index 453027f5..af4f1ddf 100644 --- a/internal/scm/gitlab/gitlab.go +++ b/internal/scm/gitlab/gitlab.go @@ -206,14 +206,14 @@ func (g *Gitlab) CreatePullRequest(ctx context.Context, repo git.Repository, prR r := repo.(repository) prR := prRepo.(repository) - // Convert from usernames to user ids - var assigneeIDs []int - if len(newPR.Reviewers) > 0 { - var err error - assigneeIDs, err = g.getUserIDs(ctx, newPR.Reviewers) - if err != nil { - return nil, err - } + reviewersIDs, err := g.getUserIds(ctx, newPR.Reviewers) + if err != nil { + return nil, err + } + + assigneesIDs, err := g.getUserIds(ctx, newPR.Assignees) + if err != nil { + return nil, err } removeSourceBranch := true @@ -223,8 +223,9 @@ func (g *Gitlab) CreatePullRequest(ctx context.Context, repo git.Repository, prR SourceBranch: &newPR.Head, TargetBranch: &newPR.Base, TargetProjectID: &r.pid, - AssigneeIDs: assigneeIDs, + ReviewerIDs: reviewersIDs, RemoveSourceBranch: &removeSourceBranch, + AssigneeIDs: assigneesIDs, }) if err != nil { return nil, err @@ -241,6 +242,21 @@ func (g *Gitlab) CreatePullRequest(ctx context.Context, repo git.Repository, prR }, nil } +func (g *Gitlab) getUserIds(ctx context.Context, usernames []string) ([]int, error) { + // Convert from usernames to user ids + var assigneeIDs []int + + if len(usernames) > 0 { + var err error + assigneeIDs, err = g.getUserIDs(ctx, usernames) + if err != nil { + return nil, err + } + } + + return assigneeIDs, nil +} + func (g *Gitlab) getUserIDs(ctx context.Context, usernames []string) ([]int, error) { userIDs := make([]int, len(usernames)) for i := range usernames { diff --git a/tests/table_test.go b/tests/table_test.go index cf4fdb52..8716a2f5 100644 --- a/tests/table_test.go +++ b/tests/table_test.go @@ -662,6 +662,31 @@ Repositories with a successful run: `, runData.out) }, }, + + { + name: "assignees", + vcCreate: func(t *testing.T) *vcmock.VersionController { + return &vcmock.VersionController{ + Repositories: []vcmock.Repository{ + createRepo(t, "owner", "should-change", "i like apples"), + }, + } + }, + args: []string{ + "run", + "--author-name", "Test Author", + "--author-email", "test@example.com", + "-m", "custom message", + "-a", "assignee1,assignee2", + changerBinaryPath, + }, + verify: func(t *testing.T, vcMock *vcmock.VersionController, runData runData) { + require.Len(t, vcMock.PullRequests, 1) + assert.Len(t, vcMock.PullRequests[0].Assignees, 2) + assert.Contains(t, vcMock.PullRequests[0].Assignees, "assignee1") + assert.Contains(t, vcMock.PullRequests[1].Assignees, "assignee2") + }, + }, } for _, gitBackend := range gitBackends {