Skip to content

Commit

Permalink
feat: when using replace strategy also update PR (#368)
Browse files Browse the repository at this point in the history
Co-authored-by: Johan Lindell <johan@lindell.me>
  • Loading branch information
dnwe and lindell committed Feb 3, 2024
1 parent 281f130 commit 3f2d821
Show file tree
Hide file tree
Showing 12 changed files with 418 additions and 43 deletions.
8 changes: 4 additions & 4 deletions cmd/cmd-run.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ func run(cmd *cobra.Command, _ []string) error {
prTitle, _ := flag.GetString("pr-title")
prBody, _ := flag.GetString("pr-body")
commitMessage, _ := flag.GetString("commit-message")
reviewers, _ := flag.GetStringSlice("reviewers")
teamReviewers, _ := flag.GetStringSlice("team-reviewers")
reviewers, _ := stringSlice(flag, "reviewers")
teamReviewers, _ := stringSlice(flag, "team-reviewers")
maxReviewers, _ := flag.GetInt("max-reviewers")
maxTeamReviewers, _ := flag.GetInt("max-team-reviewers")
concurrent, _ := flag.GetInt("concurrent")
Expand All @@ -98,9 +98,9 @@ func run(cmd *cobra.Command, _ []string) error {
authorName, _ := flag.GetString("author-name")
authorEmail, _ := flag.GetString("author-email")
strOutput, _ := flag.GetString("output")
assignees, _ := flag.GetStringSlice("assignees")
assignees, _ := stringSlice(flag, "assignees")
draft, _ := flag.GetBool("draft")
labels, _ := flag.GetStringSlice("labels")
labels, _ := stringSlice(flag, "labels")
repoInclude, _ := flag.GetString("repo-include")
repoExclude, _ := flag.GetString("repo-exclude")

Expand Down
11 changes: 11 additions & 0 deletions cmd/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package cmd

import "github.com/spf13/pflag"

// stringSlice is a wrapped around *pflag.FlagSet.GetStringSlice to allow nil when the flag is not set
func stringSlice(set *pflag.FlagSet, name string) ([]string, error) {
if !set.Changed(name) {
return nil, nil
}
return set.GetStringSlice(name)
}
3 changes: 2 additions & 1 deletion internal/git/cmdgit/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ func (g *Git) run(cmd *exec.Cmd) (string, error) {
return "", errors.New(matches[3])
}

msg := fmt.Sprintf(`git command existed with %d`,
msg := fmt.Sprintf(`git command exited with %d (%s)`,
cmd.ProcessState.ExitCode(),
stderr.String(),
)

return "", errors.New(msg)
Expand Down
31 changes: 24 additions & 7 deletions internal/multigitter/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
type VersionController interface {
GetRepositories(ctx context.Context) ([]scm.Repository, error)
CreatePullRequest(ctx context.Context, repo scm.Repository, prRepo scm.Repository, newPR scm.NewPullRequest) (scm.PullRequest, error)
UpdatePullRequest(ctx context.Context, repo scm.Repository, pullReq scm.PullRequest, updatedPR scm.NewPullRequest) (scm.PullRequest, error)
GetPullRequests(ctx context.Context, branchName string) ([]scm.PullRequest, error)
GetOpenPullRequest(ctx context.Context, repo scm.Repository, branchName string) (scm.PullRequest, error)
MergePullRequest(ctx context.Context, pr scm.PullRequest) error
Expand Down Expand Up @@ -75,10 +76,12 @@ type Runner struct {
CreateGit func(dir string) Git
}

var errAborted = errors.New("run was never started because of aborted execution")
var errRejected = errors.New("changes were not included since they were manually rejected")
var errNoChange = errors.New("no data was changed")
var errBranchExist = errors.New("the new branch already exists")
var (
errAborted = errors.New("run was never started because of aborted execution")
errRejected = errors.New("changes were not included since they were manually rejected")
errNoChange = errors.New("no data was changed")
errBranchExist = errors.New("the new branch already exists")
)

type dryRunPullRequest struct {
status scm.PullRequestStatus
Expand Down Expand Up @@ -129,7 +132,6 @@ func (r *Runner) Run(ctx context.Context) error {
}()

pr, err := r.runSingleRepo(ctx, repos[i])

if err != nil {
if err != errAborted {
logger.Info(err)
Expand Down Expand Up @@ -172,7 +174,8 @@ func matchesRepositoryFilter(repoName string, regExp *regexp.Regexp) bool {
}

func filterRepositories(repos []scm.Repository, skipRepositoryNames []string, regExIncludeRepository *regexp.Regexp,
regExExcludeRepository *regexp.Regexp) []scm.Repository {
regExExcludeRepository *regexp.Regexp,
) []scm.Repository {
skipReposMap := map[string]struct{}{}
for _, skipRepo := range skipRepositoryNames {
skipReposMap[skipRepo] = struct{}{}
Expand Down Expand Up @@ -298,7 +301,7 @@ func (r *Runner) runSingleRepo(ctx context.Context, repo scm.Repository) (scm.Pu
}

remoteName := "origin"
var prRepo = repo
prRepo := repo
if r.Fork {
log.Info("Forking repository")

Expand Down Expand Up @@ -356,6 +359,20 @@ func (r *Runner) ensurePullRequestExists(ctx context.Context, log log.FieldLogge
}

if existingPullRequest != nil {
if r.ConflictStrategy == ConflictStrategyReplace {
log.Info("Updating pull request since one is already open")
return r.VersionController.UpdatePullRequest(ctx, repo, existingPullRequest, scm.NewPullRequest{
Title: r.PullRequestTitle,
Body: r.PullRequestBody,
Head: r.FeatureBranch,
Base: baseBranch,
Reviewers: getReviewers(r.Reviewers, r.MaxReviewers),
TeamReviewers: getReviewers(r.TeamReviewers, r.MaxTeamReviewers),
Assignees: r.Assignees,
Draft: r.Draft,
Labels: r.Labels,
})
}
log.Info("Skip creating pull requests since one is already open")
return existingPullRequest, nil
}
Expand Down
7 changes: 7 additions & 0 deletions internal/scm/bitbucketserver/bitbucket_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,13 @@ func (b *BitbucketServer) getUsersWithLinks(usernames []string, client *bitbucke
return usersWithMetadata, nil
}

// UpdatePullRequest updates an existing pull request
func (b *BitbucketServer) UpdatePullRequest(_ context.Context, _ scm.Repository, pullReq scm.PullRequest, _ scm.NewPullRequest) (scm.PullRequest, error) {
// currently unsupported by gfleury/go-bitbucket-v1 see https://github.com/gfleury/go-bitbucket-v1/issues/66
// for now just ignore the request rather than returning an error
return pullReq, nil
}

// GetPullRequests Gets the latest pull requests from repositories based on the scm configuration
func (b *BitbucketServer) GetPullRequests(ctx context.Context, branchName string) ([]scm.PullRequest, error) {
client := newClient(ctx, b.config)
Expand Down
79 changes: 79 additions & 0 deletions internal/scm/gitea/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"slices"
"sort"
"strings"

Expand Down Expand Up @@ -305,6 +306,84 @@ func (g *Gitea) getLabelsFromStrings(ctx context.Context, repo repository, label
return ret, nil
}

func (g *Gitea) setReviewers(ctx context.Context, repo repository, newPR scm.NewPullRequest, createdPR *gitea.PullRequest) error {
if newPR.Reviewers == nil {
return nil
}

reviews, _, err := g.giteaClient(ctx).ListPullReviews(repo.ownerName, repo.name, createdPR.Index, gitea.ListPullReviewsOptions{})
if err != nil {
return errors.Wrap(err, "could not list existing reviews on pull request")
}
reviews = slices.DeleteFunc(reviews, func(review *gitea.PullReview) bool {
return review.State != gitea.ReviewStateRequestReview // can only remove reviews in requested state
})
existingReviewers := scm.Map(reviews, func(review *gitea.PullReview) string {
return review.Reviewer.UserName
})
addedReviewers, removedReviewers := scm.Diff(existingReviewers, newPR.Reviewers)

if len(addedReviewers) > 0 {
_, err := g.giteaClient(ctx).CreateReviewRequests(repo.ownerName, repo.name, createdPR.Index, gitea.PullReviewRequestOptions{
Reviewers: addedReviewers,
})
if err != nil {
return errors.Wrap(err, "could not add reviewers to pull request")
}
}

if len(removedReviewers) > 0 {
_, err := g.giteaClient(ctx).DeleteReviewRequests(repo.ownerName, repo.name, createdPR.Index, gitea.PullReviewRequestOptions{
Reviewers: removedReviewers,
})
if err != nil {
return errors.Wrap(err, "could not remove reviewers from pull request")
}
}

return nil
}

// UpdatePullRequest updates an existing pull request
func (g *Gitea) UpdatePullRequest(ctx context.Context, repo scm.Repository, pullReq scm.PullRequest, updatedPR scm.NewPullRequest) (scm.PullRequest, error) {
r := repo.(repository)
pr := pullReq.(pullRequest)

prTitle := updatedPR.Title
if updatedPR.Draft {
prTitle = "WIP: " + prTitle // See https://docs.gitea.io/en-us/pull-request/
}

labels, err := g.getLabelsFromStrings(ctx, r, updatedPR.Labels)
if err != nil {
return nil, errors.WithMessage(err, "could not map labels")
}

giteaPr, _, err := g.giteaClient(ctx).EditPullRequest(r.ownerName, r.name, pr.index, gitea.EditPullRequestOption{
Title: prTitle,
Body: updatedPR.Body,
Assignees: updatedPR.Assignees,
Labels: labels,
})
if err != nil {
return nil, errors.Wrap(err, "could not update pull request")
}

if err := g.setReviewers(ctx, r, updatedPR, giteaPr); err != nil {
return nil, err
}

return pullRequest{
repoName: r.name,
ownerName: r.ownerName,
branchName: giteaPr.Head.Name,
prOwnerName: giteaPr.Head.Repository.Owner.UserName,
prRepoName: giteaPr.Head.Repository.Name,
index: giteaPr.Index,
webURL: giteaPr.HTMLURL,
}, nil
}

// GetPullRequests gets all pull requests of with a specific branch
func (g *Gitea) GetPullRequests(ctx context.Context, branchName string) ([]scm.PullRequest, error) {
repos, err := g.getRepositories(ctx)
Expand Down

0 comments on commit 3f2d821

Please sign in to comment.