- {{ctx.Locale.Tr "repo.issues.dismiss_review_warning"}}
-
-
- diff --git a/modules/structs/pull.go b/modules/structs/pull.go index ab627666c..55831e642 100644 --- a/modules/structs/pull.go +++ b/modules/structs/pull.go @@ -86,7 +86,9 @@ type CreatePullRequestOption struct { Milestone int64 `json:"milestone"` Labels []int64 `json:"labels"` // swagger:strfmt date-time - Deadline *time.Time `json:"due_date"` + Deadline *time.Time `json:"due_date"` + Reviewers []string `json:"reviewers"` + TeamReviewers []string `json:"team_reviewers"` } // EditPullRequestOption options when modify pull request diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 679e64b42..c3639fb72 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1462,7 +1462,7 @@ issues.new.closed_milestone = Closed Milestones issues.new.assignees = Assignees issues.new.clear_assignees = Clear assignees issues.new.no_assignees = No Assignees -issues.new.no_reviewers = No reviewers +issues.new.no_reviewers = No Reviewers issues.new.blocked_user = Cannot create issue because you are blocked by the repository owner. issues.edit.already_changed = Unable to save changes to the issue. It appears the content has already been changed by another user. Please refresh the page and try editing again to avoid overwriting their changes issues.edit.blocked_user = Cannot edit content because you are blocked by the poster or repository owner. diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 34ebcb42d..28d7379f0 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -554,7 +554,19 @@ func CreatePullRequest(ctx *context.APIContext) { } } - if err := pull_service.NewPullRequest(ctx, repo, prIssue, labelIDs, []string{}, pr, assigneeIDs); err != nil { + prOpts := &pull_service.NewPullRequestOptions{ + Repo: repo, + Issue: prIssue, + LabelIDs: labelIDs, + PullRequest: pr, + AssigneeIDs: assigneeIDs, + } + prOpts.Reviewers, prOpts.TeamReviewers = parseReviewersByNames(ctx, form.Reviewers, form.TeamReviewers) + if ctx.Written() { + return + } + + if err := pull_service.NewPullRequest(ctx, prOpts); err != nil { if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) { ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err) } else if errors.Is(err, user_model.ErrBlockedUser) { diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 34bbaf560..def860eee 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -656,6 +656,47 @@ func DeleteReviewRequests(ctx *context.APIContext) { apiReviewRequest(ctx, *opts, false) } +func parseReviewersByNames(ctx *context.APIContext, reviewerNames, teamReviewerNames []string) (reviewers []*user_model.User, teamReviewers []*organization.Team) { + var err error + for _, r := range reviewerNames { + var reviewer *user_model.User + if strings.Contains(r, "@") { + reviewer, err = user_model.GetUserByEmail(ctx, r) + } else { + reviewer, err = user_model.GetUserByName(ctx, r) + } + + if err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.NotFound("UserNotExist", fmt.Sprintf("User '%s' not exist", r)) + return nil, nil + } + ctx.Error(http.StatusInternalServerError, "GetUser", err) + return nil, nil + } + + reviewers = append(reviewers, reviewer) + } + + if ctx.Repo.Repository.Owner.IsOrganization() && len(teamReviewerNames) > 0 { + for _, t := range teamReviewerNames { + var teamReviewer *organization.Team + teamReviewer, err = organization.GetTeam(ctx, ctx.Repo.Owner.ID, t) + if err != nil { + if organization.IsErrTeamNotExist(err) { + ctx.NotFound("TeamNotExist", fmt.Sprintf("Team '%s' not exist", t)) + return nil, nil + } + ctx.Error(http.StatusInternalServerError, "ReviewRequest", err) + return nil, nil + } + + teamReviewers = append(teamReviewers, teamReviewer) + } + } + return reviewers, teamReviewers +} + func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions, isAdd bool) { pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) if err != nil { @@ -672,42 +713,15 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions return } - reviewers := make([]*user_model.User, 0, len(opts.Reviewers)) - permDoer, err := access_model.GetUserRepoPermission(ctx, pr.Issue.Repo, ctx.Doer) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) return } - for _, r := range opts.Reviewers { - var reviewer *user_model.User - if strings.Contains(r, "@") { - reviewer, err = user_model.GetUserByEmail(ctx, r) - } else { - reviewer, err = user_model.GetUserByName(ctx, r) - } - - if err != nil { - if user_model.IsErrUserNotExist(err) { - ctx.NotFound("UserNotExist", fmt.Sprintf("User '%s' not exist", r)) - return - } - ctx.Error(http.StatusInternalServerError, "GetUser", err) - return - } - - err = issue_service.IsValidReviewRequest(ctx, reviewer, ctx.Doer, isAdd, pr.Issue, &permDoer) - if err != nil { - if issues_model.IsErrNotValidReviewRequest(err) { - ctx.Error(http.StatusUnprocessableEntity, "NotValidReviewRequest", err) - return - } - ctx.Error(http.StatusInternalServerError, "IsValidReviewRequest", err) - return - } - - reviewers = append(reviewers, reviewer) + reviewers, teamReviewers := parseReviewersByNames(ctx, opts.Reviewers, opts.TeamReviewers) + if ctx.Written() { + return } var reviews []*issues_model.Review @@ -716,12 +730,16 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions } for _, reviewer := range reviewers { - comment, err := issue_service.ReviewRequest(ctx, pr.Issue, ctx.Doer, reviewer, isAdd) + comment, err := issue_service.ReviewRequest(ctx, pr.Issue, ctx.Doer, &permDoer, reviewer, isAdd) if err != nil { if issues_model.IsErrReviewRequestOnClosedPR(err) { ctx.Error(http.StatusForbidden, "", err) return } + if issues_model.IsErrNotValidReviewRequest(err) { + ctx.Error(http.StatusUnprocessableEntity, "", err) + return + } ctx.Error(http.StatusInternalServerError, "ReviewRequest", err) return } @@ -736,35 +754,17 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions } if ctx.Repo.Repository.Owner.IsOrganization() && len(opts.TeamReviewers) > 0 { - teamReviewers := make([]*organization.Team, 0, len(opts.TeamReviewers)) - for _, t := range opts.TeamReviewers { - var teamReviewer *organization.Team - teamReviewer, err = organization.GetTeam(ctx, ctx.Repo.Owner.ID, t) - if err != nil { - if organization.IsErrTeamNotExist(err) { - ctx.NotFound("TeamNotExist", fmt.Sprintf("Team '%s' not exist", t)) - return - } - ctx.Error(http.StatusInternalServerError, "ReviewRequest", err) - return - } - - err = issue_service.IsValidTeamReviewRequest(ctx, teamReviewer, ctx.Doer, isAdd, pr.Issue) - if err != nil { - if issues_model.IsErrNotValidReviewRequest(err) { - ctx.Error(http.StatusUnprocessableEntity, "NotValidReviewRequest", err) - return - } - ctx.Error(http.StatusInternalServerError, "IsValidTeamReviewRequest", err) - return - } - - teamReviewers = append(teamReviewers, teamReviewer) - } - for _, teamReviewer := range teamReviewers { comment, err := issue_service.TeamReviewRequest(ctx, pr.Issue, ctx.Doer, teamReviewer, isAdd) if err != nil { + if issues_model.IsErrReviewRequestOnClosedPR(err) { + ctx.Error(http.StatusForbidden, "", err) + return + } + if issues_model.IsErrNotValidReviewRequest(err) { + ctx.Error(http.StatusUnprocessableEntity, "", err) + return + } ctx.ServerError("TeamReviewRequest", err) return } diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 392797283..3477ba36e 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -792,6 +792,10 @@ func CompareDiff(ctx *context.Context) { if ctx.Written() { return } + RetrieveRepoReviewers(ctx, ctx.Repo.Repository, nil, true) + if ctx.Written() { + return + } } } beforeCommitID := ctx.Data["BeforeCommitID"].(string) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index c4fc53544..7fa8d428d 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -654,34 +654,66 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { // repoReviewerSelection items to bee shown type repoReviewerSelection struct { - IsTeam bool - Team *organization.Team - User *user_model.User - Review *issues_model.Review - CanChange bool - Checked bool - ItemID int64 + IsTeam bool + Team *organization.Team + User *user_model.User + Review *issues_model.Review + CanBeDismissed bool + CanChange bool + Requested bool + ItemID int64 } -// RetrieveRepoReviewers find all reviewers of a repository +type issueSidebarReviewersData struct { + Repository *repo_model.Repository + RepoOwnerName string + RepoLink string + IssueID int64 + CanChooseReviewer bool + OriginalReviews issues_model.ReviewList + TeamReviewers []*repoReviewerSelection + Reviewers []*repoReviewerSelection + CurrentPullReviewers []*repoReviewerSelection +} + +// RetrieveRepoReviewers find all reviewers of a repository. If issue is nil, it means the doer is creating a new PR. func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, issue *issues_model.Issue, canChooseReviewer bool) { - ctx.Data["CanChooseReviewer"] = canChooseReviewer + data := &issueSidebarReviewersData{} + data.RepoLink = ctx.Repo.RepoLink + data.Repository = repo + data.RepoOwnerName = repo.OwnerName + data.CanChooseReviewer = canChooseReviewer - originalAuthorReviews, err := issues_model.GetReviewersFromOriginalAuthorsByIssueID(ctx, issue.ID) - if err != nil { - ctx.ServerError("GetReviewersFromOriginalAuthorsByIssueID", err) - return - } - ctx.Data["OriginalReviews"] = originalAuthorReviews + var posterID int64 + var isClosed bool + var reviews issues_model.ReviewList - reviews, err := issues_model.GetReviewsByIssueID(ctx, issue.ID) - if err != nil { - ctx.ServerError("GetReviewersByIssueID", err) - return - } + if issue == nil { + posterID = ctx.Doer.ID + } else { + posterID = issue.PosterID + if issue.OriginalAuthorID > 0 { + posterID = 0 // for migrated PRs, no poster ID + } - if len(reviews) == 0 && !canChooseReviewer { - return + data.IssueID = issue.ID + isClosed = issue.IsClosed || issue.PullRequest.HasMerged + + originalAuthorReviews, err := issues_model.GetReviewersFromOriginalAuthorsByIssueID(ctx, issue.ID) + if err != nil { + ctx.ServerError("GetReviewersFromOriginalAuthorsByIssueID", err) + return + } + data.OriginalReviews = originalAuthorReviews + + reviews, err = issues_model.GetReviewsByIssueID(ctx, issue.ID) + if err != nil { + ctx.ServerError("GetReviewersByIssueID", err) + return + } + if len(reviews) == 0 && !canChooseReviewer { + return + } } var ( @@ -693,11 +725,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is ) if canChooseReviewer { - posterID := issue.PosterID - if issue.OriginalAuthorID > 0 { - posterID = 0 - } - + var err error reviewers, err = repo_model.GetReviewers(ctx, repo, ctx.Doer.ID, posterID) if err != nil { ctx.ServerError("GetReviewers", err) @@ -723,9 +751,9 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is for _, review := range reviews { tmp := &repoReviewerSelection{ - Checked: review.Type == issues_model.ReviewTypeRequest, - Review: review, - ItemID: review.ReviewerID, + Requested: review.Type == issues_model.ReviewTypeRequest, + Review: review, + ItemID: review.ReviewerID, } if review.ReviewerTeamID > 0 { tmp.IsTeam = true @@ -756,7 +784,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is currentPullReviewers := make([]*repoReviewerSelection, 0, len(pullReviews)) for _, item := range pullReviews { if item.Review.ReviewerID > 0 { - if err = item.Review.LoadReviewer(ctx); err != nil { + if err := item.Review.LoadReviewer(ctx); err != nil { if user_model.IsErrUserNotExist(err) { continue } @@ -765,7 +793,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is } item.User = item.Review.Reviewer } else if item.Review.ReviewerTeamID > 0 { - if err = item.Review.LoadReviewerTeam(ctx); err != nil { + if err := item.Review.LoadReviewerTeam(ctx); err != nil { if organization.IsErrTeamNotExist(err) { continue } @@ -776,10 +804,11 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is } else { continue } - + item.CanBeDismissed = ctx.Repo.Permission.IsAdmin() && !isClosed && + (item.Review.Type == issues_model.ReviewTypeApprove || item.Review.Type == issues_model.ReviewTypeReject) currentPullReviewers = append(currentPullReviewers, item) } - ctx.Data["PullReviewers"] = currentPullReviewers + data.CurrentPullReviewers = currentPullReviewers } if canChooseReviewer && reviewersResult != nil { @@ -807,7 +836,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is }) } - ctx.Data["Reviewers"] = reviewersResult + data.Reviewers = reviewersResult } if canChooseReviewer && teamReviewersResult != nil { @@ -835,8 +864,10 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is }) } - ctx.Data["TeamReviewers"] = teamReviewersResult + data.TeamReviewers = teamReviewersResult } + + ctx.Data["IssueSidebarReviewersData"] = data } // RetrieveRepoMetas find all the meta information of a repository @@ -1117,7 +1148,14 @@ func DeleteIssue(ctx *context.Context) { } // ValidateRepoMetas check and returns repository's meta information -func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull bool) ([]int64, []int64, int64, int64) { +func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull bool) (ret struct { + LabelIDs, AssigneeIDs []int64 + MilestoneID, ProjectID int64 + + Reviewers []*user_model.User + TeamReviewers []*organization.Team +}, +) { var ( repo = ctx.Repo.Repository err error @@ -1125,7 +1163,7 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository, isPull) if ctx.Written() { - return nil, nil, 0, 0 + return ret } var labelIDs []int64 @@ -1134,7 +1172,7 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull if len(form.LabelIDs) > 0 { labelIDs, err = base.StringsToInt64s(strings.Split(form.LabelIDs, ",")) if err != nil { - return nil, nil, 0, 0 + return ret } labelIDMark := make(container.Set[int64]) labelIDMark.AddMultiple(labelIDs...) @@ -1157,11 +1195,11 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID) if err != nil { ctx.ServerError("GetMilestoneByID", err) - return nil, nil, 0, 0 + return ret } if milestone.RepoID != repo.ID { ctx.ServerError("GetMilestoneByID", err) - return nil, nil, 0, 0 + return ret } ctx.Data["Milestone"] = milestone ctx.Data["milestone_id"] = milestoneID @@ -1171,11 +1209,11 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull p, err := project_model.GetProjectByID(ctx, form.ProjectID) if err != nil { ctx.ServerError("GetProjectByID", err) - return nil, nil, 0, 0 + return ret } if p.RepoID != ctx.Repo.Repository.ID && p.OwnerID != ctx.Repo.Repository.OwnerID { ctx.NotFound("", nil) - return nil, nil, 0, 0 + return ret } ctx.Data["Project"] = p @@ -1187,7 +1225,7 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull if len(form.AssigneeIDs) > 0 { assigneeIDs, err = base.StringsToInt64s(strings.Split(form.AssigneeIDs, ",")) if err != nil { - return nil, nil, 0, 0 + return ret } // Check if the passed assignees actually exists and is assignable @@ -1195,18 +1233,18 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull assignee, err := user_model.GetUserByID(ctx, aID) if err != nil { ctx.ServerError("GetUserByID", err) - return nil, nil, 0, 0 + return ret } valid, err := access_model.CanBeAssigned(ctx, assignee, repo, isPull) if err != nil { ctx.ServerError("CanBeAssigned", err) - return nil, nil, 0, 0 + return ret } if !valid { ctx.ServerError("canBeAssigned", repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name}) - return nil, nil, 0, 0 + return ret } } } @@ -1216,7 +1254,39 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull assigneeIDs = append(assigneeIDs, form.AssigneeID) } - return labelIDs, assigneeIDs, milestoneID, form.ProjectID + // Check reviewers + var reviewers []*user_model.User + var teamReviewers []*organization.Team + if isPull && len(form.ReviewerIDs) > 0 { + reviewerIDs, err := base.StringsToInt64s(strings.Split(form.ReviewerIDs, ",")) + if err != nil { + return ret + } + // Check if the passed reviewers (user/team) actually exist + for _, rID := range reviewerIDs { + // negative reviewIDs represent team requests + if rID < 0 { + teamReviewer, err := organization.GetTeamByID(ctx, -rID) + if err != nil { + ctx.ServerError("GetTeamByID", err) + return ret + } + teamReviewers = append(teamReviewers, teamReviewer) + continue + } + + reviewer, err := user_model.GetUserByID(ctx, rID) + if err != nil { + ctx.ServerError("GetUserByID", err) + return ret + } + reviewers = append(reviewers, reviewer) + } + } + + ret.LabelIDs, ret.AssigneeIDs, ret.MilestoneID, ret.ProjectID = labelIDs, assigneeIDs, milestoneID, form.ProjectID + ret.Reviewers, ret.TeamReviewers = reviewers, teamReviewers + return ret } // NewIssuePost response for creating new issue @@ -1234,11 +1304,13 @@ func NewIssuePost(ctx *context.Context) { attachments []string ) - labelIDs, assigneeIDs, milestoneID, projectID := ValidateRepoMetas(ctx, *form, false) + validateRet := ValidateRepoMetas(ctx, *form, false) if ctx.Written() { return } + labelIDs, assigneeIDs, milestoneID, projectID := validateRet.LabelIDs, validateRet.AssigneeIDs, validateRet.MilestoneID, validateRet.ProjectID + if projectID > 0 { if !ctx.Repo.CanRead(unit.TypeProjects) { // User must also be able to see the project. @@ -2479,7 +2551,7 @@ func UpdatePullReviewRequest(ctx *context.Context) { return } - err = issue_service.IsValidTeamReviewRequest(ctx, team, ctx.Doer, action == "attach", issue) + _, err = issue_service.TeamReviewRequest(ctx, issue, ctx.Doer, team, action == "attach") if err != nil { if issues_model.IsErrNotValidReviewRequest(err) { log.Warn( @@ -2490,12 +2562,6 @@ func UpdatePullReviewRequest(ctx *context.Context) { ctx.Status(http.StatusForbidden) return } - ctx.ServerError("IsValidTeamReviewRequest", err) - return - } - - _, err = issue_service.TeamReviewRequest(ctx, issue, ctx.Doer, team, action == "attach") - if err != nil { ctx.ServerError("TeamReviewRequest", err) return } @@ -2517,7 +2583,7 @@ func UpdatePullReviewRequest(ctx *context.Context) { return } - err = issue_service.IsValidReviewRequest(ctx, reviewer, ctx.Doer, action == "attach", issue, nil) + _, err = issue_service.ReviewRequest(ctx, issue, ctx.Doer, &ctx.Repo.Permission, reviewer, action == "attach") if err != nil { if issues_model.IsErrNotValidReviewRequest(err) { log.Warn( @@ -2528,12 +2594,6 @@ func UpdatePullReviewRequest(ctx *context.Context) { ctx.Status(http.StatusForbidden) return } - ctx.ServerError("isValidReviewRequest", err) - return - } - - _, err = issue_service.ReviewRequest(ctx, issue, ctx.Doer, reviewer, action == "attach") - if err != nil { if issues_model.IsErrReviewRequestOnClosedPR(err) { ctx.Status(http.StatusForbidden) return diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index cc554a71f..dd9671efb 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1269,11 +1269,13 @@ func CompareAndPullRequestPost(ctx *context.Context) { return } - labelIDs, assigneeIDs, milestoneID, projectID := ValidateRepoMetas(ctx, *form, true) + validateRet := ValidateRepoMetas(ctx, *form, true) if ctx.Written() { return } + labelIDs, assigneeIDs, milestoneID, projectID := validateRet.LabelIDs, validateRet.AssigneeIDs, validateRet.MilestoneID, validateRet.ProjectID + if setting.Attachment.Enabled { attachments = form.Files } @@ -1318,8 +1320,17 @@ func CompareAndPullRequestPost(ctx *context.Context) { } // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt // instead of 500. - - if err := pull_service.NewPullRequest(ctx, repo, pullIssue, labelIDs, attachments, pullRequest, assigneeIDs); err != nil { + prOpts := &pull_service.NewPullRequestOptions{ + Repo: repo, + Issue: pullIssue, + LabelIDs: labelIDs, + AttachmentUUIDs: attachments, + PullRequest: pullRequest, + AssigneeIDs: assigneeIDs, + Reviewers: validateRet.Reviewers, + TeamReviewers: validateRet.TeamReviewers, + } + if err := pull_service.NewPullRequest(ctx, prOpts); err != nil { switch { case repo_model.IsErrUserDoesNotHaveAccessToRepo(err): ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error()) diff --git a/services/agit/agit.go b/services/agit/agit.go index 82aa2791a..83b12dfcd 100644 --- a/services/agit/agit.go +++ b/services/agit/agit.go @@ -137,8 +137,12 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git. Type: issues_model.PullRequestGitea, Flow: issues_model.PullRequestFlowAGit, } - - if err := pull_service.NewPullRequest(ctx, repo, prIssue, []int64{}, []string{}, pr, []int64{}); err != nil { + prOpts := &pull_service.NewPullRequestOptions{ + Repo: repo, + Issue: prIssue, + PullRequest: pr, + } + if err := pull_service.NewPullRequest(ctx, prOpts); err != nil { return nil, err } diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index ddd07a64c..83f2dd6ca 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -447,6 +447,7 @@ type CreateIssueForm struct { Title string `binding:"Required;MaxSize(255)"` LabelIDs string `form:"label_ids"` AssigneeIDs string `form:"assignee_ids"` + ReviewerIDs string `form:"reviewer_ids"` Ref string `form:"ref"` MilestoneID int64 ProjectID int64 diff --git a/services/issue/assignee.go b/services/issue/assignee.go index a0aa5a339..52ee9f2b2 100644 --- a/services/issue/assignee.go +++ b/services/issue/assignee.go @@ -61,7 +61,12 @@ func ToggleAssigneeWithNotify(ctx context.Context, issue *issues_model.Issue, do } // ReviewRequest add or remove a review request from a user for this PR, and make comment for it. -func ReviewRequest(ctx context.Context, issue *issues_model.Issue, doer, reviewer *user_model.User, isAdd bool) (comment *issues_model.Comment, err error) { +func ReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, permDoer *access_model.Permission, reviewer *user_model.User, isAdd bool) (comment *issues_model.Comment, err error) { + err = isValidReviewRequest(ctx, reviewer, doer, isAdd, issue, permDoer) + if err != nil { + return nil, err + } + if isAdd { comment, err = issues_model.AddReviewRequest(ctx, issue, reviewer, doer) } else { @@ -79,8 +84,8 @@ func ReviewRequest(ctx context.Context, issue *issues_model.Issue, doer, reviewe return comment, err } -// IsValidReviewRequest Check permission for ReviewRequest -func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User, isAdd bool, issue *issues_model.Issue, permDoer *access_model.Permission) error { +// isValidReviewRequest Check permission for ReviewRequest +func isValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User, isAdd bool, issue *issues_model.Issue, permDoer *access_model.Permission) error { if reviewer.IsOrganization() { return issues_model.ErrNotValidReviewRequest{ Reason: "Organization can't be added as reviewer", @@ -109,7 +114,7 @@ func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User, } } - lastreview, err := issues_model.GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID) + lastReview, err := issues_model.GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID) if err != nil && !issues_model.IsErrReviewNotExist(err) { return err } @@ -137,7 +142,7 @@ func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User, return nil } - if doer.ID == issue.PosterID && issue.OriginalAuthorID == 0 && lastreview != nil && lastreview.Type != issues_model.ReviewTypeRequest { + if doer.ID == issue.PosterID && issue.OriginalAuthorID == 0 && lastReview != nil && lastReview.Type != issues_model.ReviewTypeRequest { return nil } @@ -152,7 +157,7 @@ func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User, return nil } - if lastreview != nil && lastreview.Type == issues_model.ReviewTypeRequest && lastreview.ReviewerID == doer.ID { + if lastReview != nil && lastReview.Type == issues_model.ReviewTypeRequest && lastReview.ReviewerID == doer.ID { return nil } @@ -163,8 +168,8 @@ func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User, } } -// IsValidTeamReviewRequest Check permission for ReviewRequest Team -func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team, doer *user_model.User, isAdd bool, issue *issues_model.Issue) error { +// isValidTeamReviewRequest Check permission for ReviewRequest Team +func isValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team, doer *user_model.User, isAdd bool, issue *issues_model.Issue) error { if doer.IsOrganization() { return issues_model.ErrNotValidReviewRequest{ Reason: "Organization can't be doer to add reviewer", @@ -212,6 +217,10 @@ func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team, // TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it. func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewer *organization.Team, isAdd bool) (comment *issues_model.Comment, err error) { + err = isValidTeamReviewRequest(ctx, reviewer, doer, isAdd, issue) + if err != nil { + return nil, err + } if isAdd { comment, err = issues_model.AddTeamReviewRequest(ctx, issue, reviewer, doer) } else { @@ -268,6 +277,9 @@ func teamReviewRequestNotify(ctx context.Context, issue *issues_model.Issue, doe // CanDoerChangeReviewRequests returns if the doer can add/remove review requests of a PR func CanDoerChangeReviewRequests(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue) bool { + if repo.IsArchived { + return false + } // The poster of the PR can change the reviewers if doer.ID == issue.PosterID { return true diff --git a/services/mailer/mailer.go b/services/mailer/mailer.go index c5846e610..5cb6d0352 100644 --- a/services/mailer/mailer.go +++ b/services/mailer/mailer.go @@ -382,7 +382,7 @@ func (s *dummySender) Send(from string, to []string, msg io.WriterTo) error { if _, err := msg.WriteTo(&buf); err != nil { return err } - log.Info("Mail From: %s To: %v Body: %s", from, to, buf.String()) + log.Debug("Mail From: %s To: %v Body: %s", from, to, buf.String()) return nil } diff --git a/services/pull/pull.go b/services/pull/pull.go index bab4e4999..3362cb97f 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/organization" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -41,8 +42,20 @@ func getPullWorkingLockKey(prID int64) string { return fmt.Sprintf("pull_working_%d", prID) } +type NewPullRequestOptions struct { + Repo *repo_model.Repository + Issue *issues_model.Issue + LabelIDs []int64 + AttachmentUUIDs []string + PullRequest *issues_model.PullRequest + AssigneeIDs []int64 + Reviewers []*user_model.User + TeamReviewers []*organization.Team +} + // NewPullRequest creates new pull request with labels for repository. -func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, pr *issues_model.PullRequest, assigneeIDs []int64) error { +func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error { + repo, issue, labelIDs, uuids, pr, assigneeIDs := opts.Repo, opts.Issue, opts.LabelIDs, opts.AttachmentUUIDs, opts.PullRequest, opts.AssigneeIDs if err := issue.LoadPoster(ctx); err != nil { return err } @@ -197,7 +210,17 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *iss } notify_service.IssueChangeAssignee(ctx, issue.Poster, issue, assignee, false, assigneeCommentMap[assigneeID]) } - + permDoer, err := access_model.GetUserRepoPermission(ctx, repo, issue.Poster) + for _, reviewer := range opts.Reviewers { + if _, err = issue_service.ReviewRequest(ctx, pr.Issue, issue.Poster, &permDoer, reviewer, true); err != nil { + return err + } + } + for _, teamReviewer := range opts.TeamReviewers { + if _, err = issue_service.TeamReviewRequest(ctx, pr.Issue, issue.Poster, teamReviewer, true); err != nil { + return err + } + } return nil } diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index 6f1bebc03..190d52cf4 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -47,7 +47,11 @@