diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index f201ff1d199..7d5b3961bc8 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1007,6 +1007,14 @@ LEVEL = Info ;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS. ;DEFAULT_FORK_REPO_UNITS = repo.code,repo.pulls ;; +;; Comma separated list of default mirror repo units. +;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS. +;DEFAULT_MIRROR_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.wiki,repo.projects,repo.packages +;; +;; Comma separated list of default template repo units. +;; The set of allowed values and rules are the same as DEFAULT_REPO_UNITS. +;DEFAULT_TEMPLATE_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects,repo.packages +;; ;; Prefix archive files by placing them in a directory named after the repository ;PREFIX_ARCHIVE_FILES = true ;; diff --git a/models/organization/org.go b/models/organization/org.go index b33d15d29c1..28a46ec8f50 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -141,8 +141,9 @@ func (org *Organization) LoadTeams(ctx context.Context) ([]*Team, error) { } // GetMembers returns all members of organization. -func (org *Organization) GetMembers(ctx context.Context) (user_model.UserList, map[int64]bool, error) { +func (org *Organization) GetMembers(ctx context.Context, doer *user_model.User) (user_model.UserList, map[int64]bool, error) { return FindOrgMembers(ctx, &FindOrgMembersOpts{ + Doer: doer, OrgID: org.ID, }) } @@ -195,16 +196,22 @@ func (org *Organization) CanCreateRepo() bool { // FindOrgMembersOpts represensts find org members conditions type FindOrgMembersOpts struct { db.ListOptions - OrgID int64 - PublicOnly bool + Doer *user_model.User + IsDoerMember bool + OrgID int64 +} + +func (opts FindOrgMembersOpts) PublicOnly() bool { + return opts.Doer == nil || !(opts.IsDoerMember || opts.Doer.IsAdmin) } // CountOrgMembers counts the organization's members func CountOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (int64, error) { sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID) - if opts.PublicOnly { + if opts.PublicOnly() { sess.And("is_public = ?", true) } + return sess.Count(new(OrgUser)) } @@ -525,9 +532,10 @@ func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organiz // GetOrgUsersByOrgID returns all organization-user relations by organization ID. func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) { sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID) - if opts.PublicOnly { + if opts.PublicOnly() { sess.And("is_public = ?", true) } + if opts.ListOptions.PageSize > 0 { sess = db.SetSessionPagination(sess, opts) diff --git a/models/organization/org_test.go b/models/organization/org_test.go index 23ef22e2fba..5442c37ccc9 100644 --- a/models/organization/org_test.go +++ b/models/organization/org_test.go @@ -4,6 +4,7 @@ package organization_test import ( + "sort" "testing" "code.gitea.io/gitea/models/db" @@ -103,7 +104,7 @@ func TestUser_GetTeams(t *testing.T) { func TestUser_GetMembers(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) - members, _, err := org.GetMembers(db.DefaultContext) + members, _, err := org.GetMembers(db.DefaultContext, &user_model.User{IsAdmin: true}) assert.NoError(t, err) if assert.Len(t, members, 3) { assert.Equal(t, int64(2), members[0].ID) @@ -210,37 +211,42 @@ func TestFindOrgs(t *testing.T) { func TestGetOrgUsersByOrgID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - orgUsers, err := organization.GetOrgUsersByOrgID(db.DefaultContext, &organization.FindOrgMembersOpts{ - ListOptions: db.ListOptions{}, - OrgID: 3, - PublicOnly: false, - }) - assert.NoError(t, err) - if assert.Len(t, orgUsers, 3) { - assert.Equal(t, organization.OrgUser{ - ID: orgUsers[0].ID, - OrgID: 3, - UID: 2, - IsPublic: true, - }, *orgUsers[0]) - assert.Equal(t, organization.OrgUser{ - ID: orgUsers[1].ID, - OrgID: 3, - UID: 4, - IsPublic: false, - }, *orgUsers[1]) - assert.Equal(t, organization.OrgUser{ - ID: orgUsers[2].ID, - OrgID: 3, - UID: 28, - IsPublic: true, - }, *orgUsers[2]) + opts := &organization.FindOrgMembersOpts{ + Doer: &user_model.User{IsAdmin: true}, + OrgID: 3, } + assert.False(t, opts.PublicOnly()) + orgUsers, err := organization.GetOrgUsersByOrgID(db.DefaultContext, opts) + assert.NoError(t, err) + sort.Slice(orgUsers, func(i, j int) bool { + return orgUsers[i].ID < orgUsers[j].ID + }) + assert.EqualValues(t, []*organization.OrgUser{{ + ID: 1, + OrgID: 3, + UID: 2, + IsPublic: true, + }, { + ID: 2, + OrgID: 3, + UID: 4, + IsPublic: false, + }, { + ID: 9, + OrgID: 3, + UID: 28, + IsPublic: true, + }}, orgUsers) + + opts = &organization.FindOrgMembersOpts{OrgID: 3} + assert.True(t, opts.PublicOnly()) + orgUsers, err = organization.GetOrgUsersByOrgID(db.DefaultContext, opts) + assert.NoError(t, err) + assert.Len(t, orgUsers, 2) orgUsers, err = organization.GetOrgUsersByOrgID(db.DefaultContext, &organization.FindOrgMembersOpts{ ListOptions: db.ListOptions{}, OrgID: unittest.NonexistentID, - PublicOnly: false, }) assert.NoError(t, err) assert.Len(t, orgUsers, 0) diff --git a/models/organization/org_user_test.go b/models/organization/org_user_test.go index cf7acdf83ba..55abb63203e 100644 --- a/models/organization/org_user_test.go +++ b/models/organization/org_user_test.go @@ -94,7 +94,7 @@ func TestUserListIsPublicMember(t *testing.T) { func testUserListIsPublicMember(t *testing.T, orgID int64, expected map[int64]bool) { org, err := organization.GetOrgByID(db.DefaultContext, orgID) assert.NoError(t, err) - _, membersIsPublic, err := org.GetMembers(db.DefaultContext) + _, membersIsPublic, err := org.GetMembers(db.DefaultContext, &user_model.User{IsAdmin: true}) assert.NoError(t, err) assert.Equal(t, expected, membersIsPublic) } @@ -121,7 +121,7 @@ func TestUserListIsUserOrgOwner(t *testing.T) { func testUserListIsUserOrgOwner(t *testing.T, orgID int64, expected map[int64]bool) { org, err := organization.GetOrgByID(db.DefaultContext, orgID) assert.NoError(t, err) - members, _, err := org.GetMembers(db.DefaultContext) + members, _, err := org.GetMembers(db.DefaultContext, &user_model.User{IsAdmin: true}) assert.NoError(t, err) assert.Equal(t, expected, organization.IsUserOrgOwner(db.DefaultContext, members, orgID)) } diff --git a/models/unit/unit.go b/models/unit/unit.go index 3b62e5f9822..c816fc6c688 100644 --- a/models/unit/unit.go +++ b/models/unit/unit.go @@ -80,6 +80,27 @@ var ( TypePullRequests, } + // DefaultMirrorRepoUnits contains the default unit types for mirrors + DefaultMirrorRepoUnits = []Type{ + TypeCode, + TypeIssues, + TypeReleases, + TypeWiki, + TypeProjects, + TypePackages, + } + + // DefaultTemplateRepoUnits contains the default unit types for templates + DefaultTemplateRepoUnits = []Type{ + TypeCode, + TypeIssues, + TypePullRequests, + TypeReleases, + TypeWiki, + TypeProjects, + TypePackages, + } + // NotAllowedDefaultRepoUnits contains units that can't be default NotAllowedDefaultRepoUnits = []Type{ TypeExternalWiki, @@ -147,6 +168,7 @@ func LoadUnitConfig() error { if len(DefaultRepoUnits) == 0 { return errors.New("no default repository units found") } + // default fork repo units setDefaultForkRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultForkRepoUnits...) if len(invalidKeys) > 0 { log.Warn("Invalid keys in default fork repo units: %s", strings.Join(invalidKeys, ", ")) @@ -155,6 +177,24 @@ func LoadUnitConfig() error { if len(DefaultForkRepoUnits) == 0 { return errors.New("no default fork repository units found") } + // default mirror repo units + setDefaultMirrorRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultMirrorRepoUnits...) + if len(invalidKeys) > 0 { + log.Warn("Invalid keys in default mirror repo units: %s", strings.Join(invalidKeys, ", ")) + } + DefaultMirrorRepoUnits = validateDefaultRepoUnits(DefaultMirrorRepoUnits, setDefaultMirrorRepoUnits) + if len(DefaultMirrorRepoUnits) == 0 { + return errors.New("no default mirror repository units found") + } + // default template repo units + setDefaultTemplateRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultTemplateRepoUnits...) + if len(invalidKeys) > 0 { + log.Warn("Invalid keys in default template repo units: %s", strings.Join(invalidKeys, ", ")) + } + DefaultTemplateRepoUnits = validateDefaultRepoUnits(DefaultTemplateRepoUnits, setDefaultTemplateRepoUnits) + if len(DefaultTemplateRepoUnits) == 0 { + return errors.New("no default template repository units found") + } return nil } diff --git a/modules/base/tool.go b/modules/base/tool.go index 9e43030f400..928c80700b8 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -147,6 +147,9 @@ func StringsToInt64s(strs []string) ([]int64, error) { } ints := make([]int64, 0, len(strs)) for _, s := range strs { + if s == "" { + continue + } n, err := strconv.ParseInt(s, 10, 64) if err != nil { return nil, err diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go index 4af8b9bc4d5..86cccdf2092 100644 --- a/modules/base/tool_test.go +++ b/modules/base/tool_test.go @@ -152,6 +152,7 @@ func TestStringsToInt64s(t *testing.T) { } testSuccess(nil, nil) testSuccess([]string{}, []int64{}) + testSuccess([]string{""}, []int64{}) testSuccess([]string{"-1234"}, []int64{-1234}) testSuccess([]string{"1", "4", "16", "64", "256"}, []int64{1, 4, 16, 64, 256}) diff --git a/modules/container/set.go b/modules/container/set.go index adb77dcac7a..105533f2033 100644 --- a/modules/container/set.go +++ b/modules/container/set.go @@ -31,8 +31,8 @@ func (s Set[T]) AddMultiple(values ...T) { } } -// Contains determines whether a set contains the specified elements. -// Returns true if the set contains the specified element; otherwise, false. +// Contains determines whether a set contains all these elements. +// Returns true if the set contains all these elements; otherwise, false. func (s Set[T]) Contains(values ...T) bool { ret := true for _, value := range values { diff --git a/modules/container/set_test.go b/modules/container/set_test.go index 1502236034a..a8b7ff81908 100644 --- a/modules/container/set_test.go +++ b/modules/container/set_test.go @@ -18,7 +18,9 @@ func TestSet(t *testing.T) { assert.True(t, s.Contains("key1")) assert.True(t, s.Contains("key2")) + assert.True(t, s.Contains("key1", "key2")) assert.False(t, s.Contains("key3")) + assert.False(t, s.Contains("key1", "key3")) assert.True(t, s.Remove("key2")) assert.False(t, s.Contains("key2")) diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 8656ebc7ecf..14cf5805c02 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -43,6 +43,8 @@ var ( DisabledRepoUnits []string DefaultRepoUnits []string DefaultForkRepoUnits []string + DefaultMirrorRepoUnits []string + DefaultTemplateRepoUnits []string PrefixArchiveFiles bool DisableMigrations bool DisableStars bool `ini:"DISABLE_STARS"` @@ -161,6 +163,8 @@ var ( DisabledRepoUnits: []string{}, DefaultRepoUnits: []string{}, DefaultForkRepoUnits: []string{}, + DefaultMirrorRepoUnits: []string{}, + DefaultTemplateRepoUnits: []string{}, PrefixArchiveFiles: true, DisableMigrations: false, DisableStars: false, diff --git a/modules/structs/pull.go b/modules/structs/pull.go index ab627666c94..55831e642c1 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/modules/templates/helper.go b/modules/templates/helper.go index efaa10624bd..3ef11772dc7 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -31,6 +31,7 @@ func NewFuncMap() template.FuncMap { "ctx": func() any { return nil }, // template context function "DumpVar": dumpVar, + "NIL": func() any { return nil }, // ----------------------------------------------------------------- // html/template related functions diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 679e64b4241..c3639fb72e2 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/org/member.go b/routers/api/v1/org/member.go index 9db9ad964b6..edcee1e2077 100644 --- a/routers/api/v1/org/member.go +++ b/routers/api/v1/org/member.go @@ -18,11 +18,12 @@ import ( ) // listMembers list an organization's members -func listMembers(ctx *context.APIContext, publicOnly bool) { +func listMembers(ctx *context.APIContext, isMember bool) { opts := &organization.FindOrgMembersOpts{ - OrgID: ctx.Org.Organization.ID, - PublicOnly: publicOnly, - ListOptions: utils.GetListOptions(ctx), + Doer: ctx.Doer, + IsDoerMember: isMember, + OrgID: ctx.Org.Organization.ID, + ListOptions: utils.GetListOptions(ctx), } count, err := organization.CountOrgMembers(ctx, opts) @@ -73,16 +74,19 @@ func ListMembers(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - publicOnly := true + var ( + isMember bool + err error + ) + if ctx.Doer != nil { - isMember, err := ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID) + isMember, err = ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID) if err != nil { ctx.Error(http.StatusInternalServerError, "IsOrgMember", err) return } - publicOnly = !isMember && !ctx.Doer.IsAdmin } - listMembers(ctx, publicOnly) + listMembers(ctx, isMember) } // ListPublicMembers list an organization's public members @@ -112,7 +116,7 @@ func ListPublicMembers(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - listMembers(ctx, true) + listMembers(ctx, false) } // IsMember check if a user is a member of an organization diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 34ebcb42d5a..28d7379f07b 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 34bbaf56000..def860eee8f 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/org/home.go b/routers/web/org/home.go index 069bd549c1a..544f5362b89 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -95,10 +95,12 @@ func home(ctx *context.Context, viewRepositories bool) { } opts := &organization.FindOrgMembersOpts{ - OrgID: org.ID, - PublicOnly: ctx.Org.PublicMemberOnly, - ListOptions: db.ListOptions{Page: 1, PageSize: 25}, + Doer: ctx.Doer, + OrgID: org.ID, + IsDoerMember: ctx.Org.IsMember, + ListOptions: db.ListOptions{Page: 1, PageSize: 25}, } + members, _, err := organization.FindOrgMembers(ctx, opts) if err != nil { ctx.ServerError("FindOrgMembers", err) diff --git a/routers/web/org/members.go b/routers/web/org/members.go index 8ff75b06511..97dfff3afe6 100644 --- a/routers/web/org/members.go +++ b/routers/web/org/members.go @@ -34,8 +34,8 @@ func Members(ctx *context.Context) { } opts := &organization.FindOrgMembersOpts{ - OrgID: org.ID, - PublicOnly: true, + Doer: ctx.Doer, + OrgID: org.ID, } if ctx.Doer != nil { @@ -44,9 +44,9 @@ func Members(ctx *context.Context) { ctx.Error(http.StatusInternalServerError, "IsOrgMember") return } - opts.PublicOnly = !isMember && !ctx.Doer.IsAdmin + opts.IsDoerMember = isMember } - ctx.Data["PublicOnly"] = opts.PublicOnly + ctx.Data["PublicOnly"] = opts.PublicOnly() total, err := organization.CountOrgMembers(ctx, opts) if err != nil { diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index eaea91b5849..12a78f7ccb2 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -790,10 +790,14 @@ func CompareDiff(ctx *context.Context) { if !nothingToCompare { // Setup information for new form. - RetrieveRepoMetas(ctx, ctx.Repo.Repository, true) + pageMetaData := retrieveRepoIssueMetaData(ctx, ctx.Repo.Repository, nil, true) if ctx.Written() { return } + _, templateErrs := setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates, pageMetaData) + if len(templateErrs) > 0 { + ctx.Flash.Warning(renderErrorOfTemplates(ctx, templateErrs), true) + } } } beforeCommitID := ctx.Data["BeforeCommitID"].(string) @@ -806,11 +810,6 @@ func CompareDiff(ctx *context.Context) { ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + separator + base.ShortSha(afterCommitID) ctx.Data["IsDiffCompare"] = true - _, templateErrs := setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates) - - if len(templateErrs) > 0 { - ctx.Flash.Warning(renderErrorOfTemplates(ctx, templateErrs), true) - } if content, ok := ctx.Data["content"].(string); ok && content != "" { // If a template content is set, prepend the "content". In this case that's only diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index c4fc5354469..72f89bd27d0 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -431,7 +431,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt return 0 } - retrieveProjects(ctx, repo) + retrieveProjectsForIssueList(ctx, repo) if ctx.Written() { return } @@ -556,37 +556,147 @@ func renderMilestones(ctx *context.Context) { ctx.Data["ClosedMilestones"] = closedMilestones } -// RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository -func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.Repository) { +type issueSidebarMilestoneData struct { + SelectedMilestoneID int64 + OpenMilestones []*issues_model.Milestone + ClosedMilestones []*issues_model.Milestone +} + +type issueSidebarAssigneesData struct { + SelectedAssigneeIDs string + CandidateAssignees []*user_model.User +} + +type IssuePageMetaData struct { + RepoLink string + Repository *repo_model.Repository + Issue *issues_model.Issue + IsPullRequest bool + CanModifyIssueOrPull bool + + ReviewersData *issueSidebarReviewersData + LabelsData *issueSidebarLabelsData + MilestonesData *issueSidebarMilestoneData + ProjectsData *issueSidebarProjectsData + AssigneesData *issueSidebarAssigneesData +} + +func retrieveRepoIssueMetaData(ctx *context.Context, repo *repo_model.Repository, issue *issues_model.Issue, isPull bool) *IssuePageMetaData { + data := &IssuePageMetaData{ + RepoLink: ctx.Repo.RepoLink, + Repository: repo, + Issue: issue, + IsPullRequest: isPull, + + ReviewersData: &issueSidebarReviewersData{}, + LabelsData: &issueSidebarLabelsData{}, + MilestonesData: &issueSidebarMilestoneData{}, + ProjectsData: &issueSidebarProjectsData{}, + AssigneesData: &issueSidebarAssigneesData{}, + } + ctx.Data["IssuePageMetaData"] = data + + if isPull { + data.retrieveReviewersData(ctx) + if ctx.Written() { + return data + } + } + data.retrieveLabelsData(ctx) + if ctx.Written() { + return data + } + + data.CanModifyIssueOrPull = ctx.Repo.CanWriteIssuesOrPulls(isPull) && !ctx.Repo.Repository.IsArchived + if !data.CanModifyIssueOrPull { + return data + } + + data.retrieveAssigneesDataForIssueWriter(ctx) + if ctx.Written() { + return data + } + + data.retrieveMilestonesDataForIssueWriter(ctx) + if ctx.Written() { + return data + } + + data.retrieveProjectsDataForIssueWriter(ctx) + if ctx.Written() { + return data + } + + PrepareBranchList(ctx) + if ctx.Written() { + return data + } + + ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx, ctx.Doer, isPull) + return data +} + +func (d *IssuePageMetaData) retrieveMilestonesDataForIssueWriter(ctx *context.Context) { var err error - ctx.Data["OpenMilestones"], err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ - RepoID: repo.ID, + if d.Issue != nil { + d.MilestonesData.SelectedMilestoneID = d.Issue.MilestoneID + } + d.MilestonesData.OpenMilestones, err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ + RepoID: d.Repository.ID, IsClosed: optional.Some(false), }) if err != nil { ctx.ServerError("GetMilestones", err) return } - ctx.Data["ClosedMilestones"], err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ - RepoID: repo.ID, + d.MilestonesData.ClosedMilestones, err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ + RepoID: d.Repository.ID, IsClosed: optional.Some(true), }) if err != nil { ctx.ServerError("GetMilestones", err) return } +} - assigneeUsers, err := repo_model.GetRepoAssignees(ctx, repo) +func (d *IssuePageMetaData) retrieveAssigneesDataForIssueWriter(ctx *context.Context) { + var err error + d.AssigneesData.CandidateAssignees, err = repo_model.GetRepoAssignees(ctx, d.Repository) if err != nil { ctx.ServerError("GetRepoAssignees", err) return } - ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers) - + d.AssigneesData.CandidateAssignees = shared_user.MakeSelfOnTop(ctx.Doer, d.AssigneesData.CandidateAssignees) + if d.Issue != nil { + _ = d.Issue.LoadAssignees(ctx) + ids := make([]string, 0, len(d.Issue.Assignees)) + for _, a := range d.Issue.Assignees { + ids = append(ids, strconv.FormatInt(a.ID, 10)) + } + d.AssigneesData.SelectedAssigneeIDs = strings.Join(ids, ",") + } + // FIXME: this is a tricky part which writes ctx.Data["Mentionable*"] handleTeamMentions(ctx) } -func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { +func retrieveProjectsForIssueList(ctx *context.Context, repo *repo_model.Repository) { + ctx.Data["OpenProjects"], ctx.Data["ClosedProjects"] = retrieveProjectsInternal(ctx, repo) +} + +type issueSidebarProjectsData struct { + SelectedProjectID int64 + OpenProjects []*project_model.Project + ClosedProjects []*project_model.Project +} + +func (d *IssuePageMetaData) retrieveProjectsDataForIssueWriter(ctx *context.Context) { + if d.Issue != nil && d.Issue.Project != nil { + d.ProjectsData.SelectedProjectID = d.Issue.Project.ID + } + d.ProjectsData.OpenProjects, d.ProjectsData.ClosedProjects = retrieveProjectsInternal(ctx, ctx.Repo.Repository) +} + +func retrieveProjectsInternal(ctx *context.Context, repo *repo_model.Repository) (open, closed []*project_model.Project) { // Distinguish whether the owner of the repository // is an individual or an organization repoOwnerType := project_model.TypeIndividual @@ -609,7 +719,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { }) if err != nil { ctx.ServerError("GetProjects", err) - return + return nil, nil } closedProjects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{ ListOptions: db.ListOptionsAll, @@ -619,7 +729,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { }) if err != nil { ctx.ServerError("GetProjects", err) - return + return nil, nil } } @@ -632,7 +742,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { }) if err != nil { ctx.ServerError("GetProjects", err) - return + return nil, nil } openProjects = append(openProjects, openProjects2...) closedProjects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{ @@ -643,45 +753,74 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { }) if err != nil { ctx.ServerError("GetProjects", err) - return + return nil, nil } closedProjects = append(closedProjects, closedProjects2...) } - - ctx.Data["OpenProjects"] = openProjects - ctx.Data["ClosedProjects"] = closedProjects + return openProjects, closedProjects } // 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 -func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, issue *issues_model.Issue, canChooseReviewer bool) { - ctx.Data["CanChooseReviewer"] = canChooseReviewer +type issueSidebarReviewersData struct { + CanChooseReviewer bool + OriginalReviews issues_model.ReviewList + TeamReviewers []*repoReviewerSelection + Reviewers []*repoReviewerSelection + CurrentPullReviewers []*repoReviewerSelection +} - originalAuthorReviews, err := issues_model.GetReviewersFromOriginalAuthorsByIssueID(ctx, issue.ID) - if err != nil { - ctx.ServerError("GetReviewersFromOriginalAuthorsByIssueID", err) - return - } - ctx.Data["OriginalReviews"] = originalAuthorReviews - - reviews, err := issues_model.GetReviewsByIssueID(ctx, issue.ID) - if err != nil { - ctx.ServerError("GetReviewersByIssueID", err) - return +// RetrieveRepoReviewers find all reviewers of a repository. If issue is nil, it means the doer is creating a new PR. +func (d *IssuePageMetaData) retrieveReviewersData(ctx *context.Context) { + data := d.ReviewersData + repo := d.Repository + if ctx.Doer != nil && ctx.IsSigned { + if d.Issue == nil { + data.CanChooseReviewer = true + } else { + data.CanChooseReviewer = issue_service.CanDoerChangeReviewRequests(ctx, ctx.Doer, repo, d.Issue) + } } - if len(reviews) == 0 && !canChooseReviewer { - return + var posterID int64 + var isClosed bool + var reviews issues_model.ReviewList + + if d.Issue == nil { + posterID = ctx.Doer.ID + } else { + posterID = d.Issue.PosterID + if d.Issue.OriginalAuthorID > 0 { + posterID = 0 // for migrated PRs, no poster ID + } + + isClosed = d.Issue.IsClosed || d.Issue.PullRequest.HasMerged + + originalAuthorReviews, err := issues_model.GetReviewersFromOriginalAuthorsByIssueID(ctx, d.Issue.ID) + if err != nil { + ctx.ServerError("GetReviewersFromOriginalAuthorsByIssueID", err) + return + } + data.OriginalReviews = originalAuthorReviews + + reviews, err = issues_model.GetReviewsByIssueID(ctx, d.Issue.ID) + if err != nil { + ctx.ServerError("GetReviewersByIssueID", err) + return + } + if len(reviews) == 0 && !data.CanChooseReviewer { + return + } } var ( @@ -692,12 +831,8 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is reviewers []*user_model.User ) - if canChooseReviewer { - posterID := issue.PosterID - if issue.OriginalAuthorID > 0 { - posterID = 0 - } - + if data.CanChooseReviewer { + var err error reviewers, err = repo_model.GetReviewers(ctx, repo, ctx.Doer.ID, posterID) if err != nil { ctx.ServerError("GetReviewers", err) @@ -723,16 +858,16 @@ 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 tmp.ItemID = -review.ReviewerTeamID } - if canChooseReviewer { + if data.CanChooseReviewer { // Users who can choose reviewers can also remove review requests tmp.CanChange = true } else if ctx.Doer != nil && ctx.Doer.ID == review.ReviewerID && review.Type == issues_model.ReviewTypeRequest { @@ -742,7 +877,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is pullReviews = append(pullReviews, tmp) - if canChooseReviewer { + if data.CanChooseReviewer { if tmp.IsTeam { teamReviewersResult = append(teamReviewersResult, tmp) } else { @@ -756,7 +891,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 +900,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,13 +911,14 @@ 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 { + if data.CanChooseReviewer && reviewersResult != nil { preadded := len(reviewersResult) for _, reviewer := range reviewers { found := false @@ -807,10 +943,10 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is }) } - ctx.Data["Reviewers"] = reviewersResult + data.Reviewers = reviewersResult } - if canChooseReviewer && teamReviewersResult != nil { + if data.CanChooseReviewer && teamReviewersResult != nil { preadded := len(teamReviewersResult) for _, team := range teamReviewers { found := false @@ -835,55 +971,82 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is }) } - ctx.Data["TeamReviewers"] = teamReviewersResult + data.TeamReviewers = teamReviewersResult } } -// RetrieveRepoMetas find all the meta information of a repository -func RetrieveRepoMetas(ctx *context.Context, repo *repo_model.Repository, isPull bool) []*issues_model.Label { - if !ctx.Repo.CanWriteIssuesOrPulls(isPull) { - return nil +type issueSidebarLabelsData struct { + AllLabels []*issues_model.Label + RepoLabels []*issues_model.Label + OrgLabels []*issues_model.Label + SelectedLabelIDs string +} + +func makeSelectedStringIDs[KeyType, ItemType comparable]( + allLabels []*issues_model.Label, candidateKey func(candidate *issues_model.Label) KeyType, + selectedItems []ItemType, selectedKey func(selected ItemType) KeyType, +) string { + selectedIDSet := make(container.Set[string]) + allLabelMap := map[KeyType]*issues_model.Label{} + for _, label := range allLabels { + allLabelMap[candidateKey(label)] = label } + for _, item := range selectedItems { + if label, ok := allLabelMap[selectedKey(item)]; ok { + label.IsChecked = true + selectedIDSet.Add(strconv.FormatInt(label.ID, 10)) + } + } + ids := selectedIDSet.Values() + sort.Strings(ids) + return strings.Join(ids, ",") +} + +func (d *issueSidebarLabelsData) SetSelectedLabels(labels []*issues_model.Label) { + d.SelectedLabelIDs = makeSelectedStringIDs( + d.AllLabels, func(label *issues_model.Label) int64 { return label.ID }, + labels, func(label *issues_model.Label) int64 { return label.ID }, + ) +} + +func (d *issueSidebarLabelsData) SetSelectedLabelNames(labelNames []string) { + d.SelectedLabelIDs = makeSelectedStringIDs( + d.AllLabels, func(label *issues_model.Label) string { return strings.ToLower(label.Name) }, + labelNames, strings.ToLower, + ) +} + +func (d *issueSidebarLabelsData) SetSelectedLabelIDs(labelIDs []int64) { + d.SelectedLabelIDs = makeSelectedStringIDs( + d.AllLabels, func(label *issues_model.Label) int64 { return label.ID }, + labelIDs, func(labelID int64) int64 { return labelID }, + ) +} + +func (d *IssuePageMetaData) retrieveLabelsData(ctx *context.Context) { + repo := d.Repository + labelsData := d.LabelsData labels, err := issues_model.GetLabelsByRepoID(ctx, repo.ID, "", db.ListOptions{}) if err != nil { ctx.ServerError("GetLabelsByRepoID", err) - return nil + return } - ctx.Data["Labels"] = labels + labelsData.RepoLabels = labels + if repo.Owner.IsOrganization() { orgLabels, err := issues_model.GetLabelsByOrgID(ctx, repo.Owner.ID, ctx.FormString("sort"), db.ListOptions{}) if err != nil { - return nil + return } - - ctx.Data["OrgLabels"] = orgLabels - labels = append(labels, orgLabels...) + labelsData.OrgLabels = orgLabels } - - RetrieveRepoMilestonesAndAssignees(ctx, repo) - if ctx.Written() { - return nil - } - - retrieveProjects(ctx, repo) - if ctx.Written() { - return nil - } - - PrepareBranchList(ctx) - if ctx.Written() { - return nil - } - - // Contains true if the user can create issue dependencies - ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx, ctx.Doer, isPull) - - return labels + labelsData.AllLabels = append(labelsData.AllLabels, labelsData.RepoLabels...) + labelsData.AllLabels = append(labelsData.AllLabels, labelsData.OrgLabels...) } // Tries to load and set an issue template. The first return value indicates if a template was loaded. -func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles []string) (bool, map[string]error) { +func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles []string, metaData *IssuePageMetaData) (bool, map[string]error) { commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) if err != nil { return false, nil @@ -920,43 +1083,21 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles ctx.Data["Fields"] = template.Fields ctx.Data["TemplateFile"] = template.FileName } - labelIDs := make([]string, 0, len(template.Labels)) - if repoLabels, err := issues_model.GetLabelsByRepoID(ctx, ctx.Repo.Repository.ID, "", db.ListOptions{}); err == nil { - ctx.Data["Labels"] = repoLabels - if ctx.Repo.Owner.IsOrganization() { - if orgLabels, err := issues_model.GetLabelsByOrgID(ctx, ctx.Repo.Owner.ID, ctx.FormString("sort"), db.ListOptions{}); err == nil { - ctx.Data["OrgLabels"] = orgLabels - repoLabels = append(repoLabels, orgLabels...) - } - } - for _, metaLabel := range template.Labels { - for _, repoLabel := range repoLabels { - if strings.EqualFold(repoLabel.Name, metaLabel) { - repoLabel.IsChecked = true - labelIDs = append(labelIDs, strconv.FormatInt(repoLabel.ID, 10)) - break - } - } - } - } - selectedAssigneeIDs := make([]int64, 0, len(template.Assignees)) + metaData.LabelsData.SetSelectedLabelNames(template.Labels) + selectedAssigneeIDStrings := make([]string, 0, len(template.Assignees)) - if userIDs, err := user_model.GetUserIDsByNames(ctx, template.Assignees, false); err == nil { + if userIDs, err := user_model.GetUserIDsByNames(ctx, template.Assignees, true); err == nil { for _, userID := range userIDs { - selectedAssigneeIDs = append(selectedAssigneeIDs, userID) selectedAssigneeIDStrings = append(selectedAssigneeIDStrings, strconv.FormatInt(userID, 10)) } } + metaData.AssigneesData.SelectedAssigneeIDs = strings.Join(selectedAssigneeIDStrings, ",") if template.Ref != "" && !strings.HasPrefix(template.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/ template.Ref = git.BranchPrefix + template.Ref } - ctx.Data["HasSelectedLabel"] = len(labelIDs) > 0 - ctx.Data["label_ids"] = strings.Join(labelIDs, ",") - ctx.Data["HasSelectedAssignee"] = len(selectedAssigneeIDs) > 0 - ctx.Data["assignee_ids"] = strings.Join(selectedAssigneeIDStrings, ",") - ctx.Data["SelectedAssigneeIDs"] = selectedAssigneeIDs + ctx.Data["Reference"] = template.Ref ctx.Data["RefEndName"] = git.RefName(template.Ref).ShortName() return true, templateErrs @@ -983,36 +1124,19 @@ func NewIssue(ctx *context.Context) { ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled upload.AddUploadContext(ctx, "comment") - milestoneID := ctx.FormInt64("milestone") - if milestoneID > 0 { - milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID) - if err != nil { - log.Error("GetMilestoneByID: %d: %v", milestoneID, err) - } else { - ctx.Data["milestone_id"] = milestoneID - ctx.Data["Milestone"] = milestone - } + pageMetaData := retrieveRepoIssueMetaData(ctx, ctx.Repo.Repository, nil, false) + if ctx.Written() { + return } - projectID := ctx.FormInt64("project") - if projectID > 0 && isProjectsEnabled { - project, err := project_model.GetProjectByID(ctx, projectID) - if err != nil { - log.Error("GetProjectByID: %d: %v", projectID, err) - } else if project.RepoID != ctx.Repo.Repository.ID { - log.Error("GetProjectByID: %d: %v", projectID, fmt.Errorf("project[%d] not in repo [%d]", project.ID, ctx.Repo.Repository.ID)) - } else { - ctx.Data["project_id"] = projectID - ctx.Data["Project"] = project - } - + pageMetaData.MilestonesData.SelectedMilestoneID = ctx.FormInt64("milestone") + pageMetaData.ProjectsData.SelectedProjectID = ctx.FormInt64("project") + if pageMetaData.ProjectsData.SelectedProjectID > 0 { if len(ctx.Req.URL.Query().Get("project")) > 0 { ctx.Data["redirect_after_creation"] = "project" } } - RetrieveRepoMetas(ctx, ctx.Repo.Repository, false) - tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) if err != nil { ctx.ServerError("GetTagNamesByRepoID", err) @@ -1021,7 +1145,7 @@ func NewIssue(ctx *context.Context) { ctx.Data["Tags"] = tags ret := issue_service.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) - templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates) + templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates, pageMetaData) for k, v := range errs { ret.TemplateErrors[k] = v } @@ -1116,107 +1240,95 @@ func DeleteIssue(ctx *context.Context) { ctx.Redirect(fmt.Sprintf("%s/issues", ctx.Repo.Repository.Link()), http.StatusSeeOther) } -// ValidateRepoMetas check and returns repository's meta information -func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull bool) ([]int64, []int64, int64, int64) { - var ( - repo = ctx.Repo.Repository - err error - ) +func toSet[ItemType any, KeyType comparable](slice []ItemType, keyFunc func(ItemType) KeyType) container.Set[KeyType] { + s := make(container.Set[KeyType]) + for _, item := range slice { + s.Add(keyFunc(item)) + } + return s +} - labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository, isPull) +// ValidateRepoMetasForNewIssue check and returns repository's meta information +func ValidateRepoMetasForNewIssue(ctx *context.Context, form forms.CreateIssueForm, isPull bool) (ret struct { + LabelIDs, AssigneeIDs []int64 + MilestoneID, ProjectID int64 + + Reviewers []*user_model.User + TeamReviewers []*organization.Team +}, +) { + pageMetaData := retrieveRepoIssueMetaData(ctx, ctx.Repo.Repository, nil, isPull) if ctx.Written() { - return nil, nil, 0, 0 + return ret } - var labelIDs []int64 - hasSelected := false - // Check labels. - if len(form.LabelIDs) > 0 { - labelIDs, err = base.StringsToInt64s(strings.Split(form.LabelIDs, ",")) - if err != nil { - return nil, nil, 0, 0 - } - labelIDMark := make(container.Set[int64]) - labelIDMark.AddMultiple(labelIDs...) + inputLabelIDs, _ := base.StringsToInt64s(strings.Split(form.LabelIDs, ",")) + candidateLabels := toSet(pageMetaData.LabelsData.AllLabels, func(label *issues_model.Label) int64 { return label.ID }) + if len(inputLabelIDs) > 0 && !candidateLabels.Contains(inputLabelIDs...) { + ctx.NotFound("", nil) + return ret + } + pageMetaData.LabelsData.SetSelectedLabelIDs(inputLabelIDs) - for i := range labels { - if labelIDMark.Contains(labels[i].ID) { - labels[i].IsChecked = true - hasSelected = true + allMilestones := append(slices.Clone(pageMetaData.MilestonesData.OpenMilestones), pageMetaData.MilestonesData.ClosedMilestones...) + candidateMilestones := toSet(allMilestones, func(milestone *issues_model.Milestone) int64 { return milestone.ID }) + if form.MilestoneID > 0 && !candidateMilestones.Contains(form.MilestoneID) { + ctx.NotFound("", nil) + return ret + } + pageMetaData.MilestonesData.SelectedMilestoneID = form.MilestoneID + + allProjects := append(slices.Clone(pageMetaData.ProjectsData.OpenProjects), pageMetaData.ProjectsData.ClosedProjects...) + candidateProjects := toSet(allProjects, func(project *project_model.Project) int64 { return project.ID }) + if form.ProjectID > 0 && !candidateProjects.Contains(form.ProjectID) { + ctx.NotFound("", nil) + return ret + } + pageMetaData.ProjectsData.SelectedProjectID = form.ProjectID + + candidateAssignees := toSet(pageMetaData.AssigneesData.CandidateAssignees, func(user *user_model.User) int64 { return user.ID }) + inputAssigneeIDs, _ := base.StringsToInt64s(strings.Split(form.AssigneeIDs, ",")) + if len(inputAssigneeIDs) > 0 && !candidateAssignees.Contains(inputAssigneeIDs...) { + ctx.NotFound("", nil) + return ret + } + pageMetaData.AssigneesData.SelectedAssigneeIDs = form.AssigneeIDs + + // Check if the passed reviewers (user/team) actually exist + var reviewers []*user_model.User + var teamReviewers []*organization.Team + reviewerIDs, _ := base.StringsToInt64s(strings.Split(form.ReviewerIDs, ",")) + if isPull && len(reviewerIDs) > 0 { + userReviewersMap := map[int64]*user_model.User{} + teamReviewersMap := map[int64]*organization.Team{} + for _, r := range pageMetaData.ReviewersData.Reviewers { + userReviewersMap[r.User.ID] = r.User + } + for _, r := range pageMetaData.ReviewersData.TeamReviewers { + teamReviewersMap[r.Team.ID] = r.Team + } + for _, rID := range reviewerIDs { + if rID < 0 { // negative reviewIDs represent team requests + team, ok := teamReviewersMap[-rID] + if !ok { + ctx.NotFound("", nil) + return ret + } + teamReviewers = append(teamReviewers, team) + } else { + user, ok := userReviewersMap[rID] + if !ok { + ctx.NotFound("", nil) + return ret + } + reviewers = append(reviewers, user) } } } - ctx.Data["Labels"] = labels - ctx.Data["HasSelectedLabel"] = hasSelected - ctx.Data["label_ids"] = form.LabelIDs - - // Check milestone. - milestoneID := form.MilestoneID - if milestoneID > 0 { - milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID) - if err != nil { - ctx.ServerError("GetMilestoneByID", err) - return nil, nil, 0, 0 - } - if milestone.RepoID != repo.ID { - ctx.ServerError("GetMilestoneByID", err) - return nil, nil, 0, 0 - } - ctx.Data["Milestone"] = milestone - ctx.Data["milestone_id"] = milestoneID - } - - if form.ProjectID > 0 { - p, err := project_model.GetProjectByID(ctx, form.ProjectID) - if err != nil { - ctx.ServerError("GetProjectByID", err) - return nil, nil, 0, 0 - } - if p.RepoID != ctx.Repo.Repository.ID && p.OwnerID != ctx.Repo.Repository.OwnerID { - ctx.NotFound("", nil) - return nil, nil, 0, 0 - } - - ctx.Data["Project"] = p - ctx.Data["project_id"] = form.ProjectID - } - - // Check assignees - var assigneeIDs []int64 - if len(form.AssigneeIDs) > 0 { - assigneeIDs, err = base.StringsToInt64s(strings.Split(form.AssigneeIDs, ",")) - if err != nil { - return nil, nil, 0, 0 - } - - // Check if the passed assignees actually exists and is assignable - for _, aID := range assigneeIDs { - assignee, err := user_model.GetUserByID(ctx, aID) - if err != nil { - ctx.ServerError("GetUserByID", err) - return nil, nil, 0, 0 - } - - valid, err := access_model.CanBeAssigned(ctx, assignee, repo, isPull) - if err != nil { - ctx.ServerError("CanBeAssigned", err) - return nil, nil, 0, 0 - } - - if !valid { - ctx.ServerError("canBeAssigned", repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name}) - return nil, nil, 0, 0 - } - } - } - - // Keep the old assignee id thingy for compatibility reasons - if form.AssigneeID > 0 { - assigneeIDs = append(assigneeIDs, form.AssigneeID) - } - - return labelIDs, assigneeIDs, milestoneID, form.ProjectID + ret.LabelIDs, ret.AssigneeIDs, ret.MilestoneID, ret.ProjectID = inputLabelIDs, inputAssigneeIDs, form.MilestoneID, form.ProjectID + ret.Reviewers, ret.TeamReviewers = reviewers, teamReviewers + return ret } // NewIssuePost response for creating new issue @@ -1234,11 +1346,13 @@ func NewIssuePost(ctx *context.Context) { attachments []string ) - labelIDs, assigneeIDs, milestoneID, projectID := ValidateRepoMetas(ctx, *form, false) + validateRet := ValidateRepoMetasForNewIssue(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. @@ -1507,60 +1621,11 @@ func ViewIssue(ctx *context.Context) { } } - // Metas. - // Check labels. - labelIDMark := make(container.Set[int64]) - for _, label := range issue.Labels { - labelIDMark.Add(label.ID) - } - labels, err := issues_model.GetLabelsByRepoID(ctx, repo.ID, "", db.ListOptions{}) - if err != nil { - ctx.ServerError("GetLabelsByRepoID", err) + pageMetaData := retrieveRepoIssueMetaData(ctx, repo, issue, issue.IsPull) + if ctx.Written() { return } - ctx.Data["Labels"] = labels - - if repo.Owner.IsOrganization() { - orgLabels, err := issues_model.GetLabelsByOrgID(ctx, repo.Owner.ID, ctx.FormString("sort"), db.ListOptions{}) - if err != nil { - ctx.ServerError("GetLabelsByOrgID", err) - return - } - ctx.Data["OrgLabels"] = orgLabels - - labels = append(labels, orgLabels...) - } - - hasSelected := false - for i := range labels { - if labelIDMark.Contains(labels[i].ID) { - labels[i].IsChecked = true - hasSelected = true - } - } - ctx.Data["HasSelectedLabel"] = hasSelected - - // Check milestone and assignee. - if ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { - RetrieveRepoMilestonesAndAssignees(ctx, repo) - retrieveProjects(ctx, repo) - - if ctx.Written() { - return - } - } - - if issue.IsPull { - canChooseReviewer := false - if ctx.Doer != nil && ctx.IsSigned { - canChooseReviewer = issue_service.CanDoerChangeReviewRequests(ctx, ctx.Doer, repo, issue) - } - - RetrieveRepoReviewers(ctx, repo, issue, canChooseReviewer) - if ctx.Written() { - return - } - } + pageMetaData.LabelsData.SetSelectedLabels(issue.Labels) if ctx.IsSigned { // Update issue-user. @@ -2479,7 +2544,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 +2555,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 +2576,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 +2587,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/issue_label.go b/routers/web/repo/issue_label.go index 81bee4dbb53..4874baaa543 100644 --- a/routers/web/repo/issue_label.go +++ b/routers/web/repo/issue_label.go @@ -53,11 +53,11 @@ func InitializeLabels(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink + "/labels") } -// RetrieveLabels find all the labels of a repository and organization -func RetrieveLabels(ctx *context.Context) { +// RetrieveLabelsForList find all the labels of a repository and organization, it is only used by "/labels" page to list all labels +func RetrieveLabelsForList(ctx *context.Context) { labels, err := issues_model.GetLabelsByRepoID(ctx, ctx.Repo.Repository.ID, ctx.FormString("sort"), db.ListOptions{}) if err != nil { - ctx.ServerError("RetrieveLabels.GetLabels", err) + ctx.ServerError("RetrieveLabelsForList.GetLabels", err) return } diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go index 93fc72300b3..c86a03da51a 100644 --- a/routers/web/repo/issue_label_test.go +++ b/routers/web/repo/issue_label_test.go @@ -62,7 +62,7 @@ func TestRetrieveLabels(t *testing.T) { contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, testCase.RepoID) ctx.Req.Form.Set("sort", testCase.Sort) - RetrieveLabels(ctx) + RetrieveLabelsForList(ctx) assert.False(t, ctx.Written()) labels, ok := ctx.Data["Labels"].([]*issues_model.Label) assert.True(t, ok) diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index cc554a71ff6..bb814eab6e7 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 := ValidateRepoMetasForNewIssue(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/routers/web/web.go b/routers/web/web.go index 7573724134a..3aa9ba9f09b 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1163,7 +1163,7 @@ func registerRoutes(m *web.Router) { m.Get("/issues/posters", repo.IssuePosters) // it can't use {type:issues|pulls} because it would conflict with other routes like "/pulls/{index}" m.Get("/pulls/posters", repo.PullPosters) m.Get("/comments/{id}/attachments", repo.GetCommentAttachments) - m.Get("/labels", repo.RetrieveLabels, repo.Labels) + m.Get("/labels", repo.RetrieveLabelsForList, repo.Labels) m.Get("/milestones", repo.Milestones) m.Get("/milestone/{id}", context.RepoRef(), repo.MilestoneIssuesAndPulls) m.Group("/{type:issues|pulls}", func() { diff --git a/services/agit/agit.go b/services/agit/agit.go index 82aa2791aa7..83b12dfcdb8 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/context/org.go b/services/context/org.go index 7eba80ff96b..e4206293724 100644 --- a/services/context/org.go +++ b/services/context/org.go @@ -26,7 +26,6 @@ type Organization struct { Organization *organization.Organization OrgLink string CanCreateOrgRepo bool - PublicMemberOnly bool // Only display public members Team *organization.Team Teams []*organization.Team @@ -176,10 +175,10 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { ctx.Data["OrgLink"] = ctx.Org.OrgLink // Member - ctx.Org.PublicMemberOnly = ctx.Doer == nil || !ctx.Org.IsMember && !ctx.Doer.IsAdmin opts := &organization.FindOrgMembersOpts{ - OrgID: org.ID, - PublicOnly: ctx.Org.PublicMemberOnly, + Doer: ctx.Doer, + OrgID: org.ID, + IsDoerMember: ctx.Org.IsMember, } ctx.Data["NumMembers"], err = organization.CountOrgMembers(ctx, opts) if err != nil { diff --git a/services/doctor/actions.go b/services/doctor/actions.go new file mode 100644 index 00000000000..7c44fb83920 --- /dev/null +++ b/services/doctor/actions.go @@ -0,0 +1,70 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package doctor + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + unit_model "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/optional" + repo_service "code.gitea.io/gitea/services/repository" +) + +func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bool) error { + var reposToFix []*repo_model.Repository + + for page := 1; ; page++ { + repos, _, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ + ListOptions: db.ListOptions{ + PageSize: repo_model.RepositoryListDefaultPageSize, + Page: page, + }, + Mirror: optional.Some(true), + }) + if err != nil { + return fmt.Errorf("SearchRepository: %w", err) + } + if len(repos) == 0 { + break + } + + for _, repo := range repos { + if repo.UnitEnabled(ctx, unit_model.TypeActions) { + reposToFix = append(reposToFix, repo) + } + } + } + + if len(reposToFix) == 0 { + logger.Info("Found no mirror with actions unit enabled") + } else { + logger.Warn("Found %d mirrors with actions unit enabled", len(reposToFix)) + } + if !autofix || len(reposToFix) == 0 { + return nil + } + + for _, repo := range reposToFix { + if err := repo_service.UpdateRepositoryUnits(ctx, repo, nil, []unit_model.Type{unit_model.TypeActions}); err != nil { + return err + } + } + logger.Info("Fixed %d mirrors with actions unit enabled", len(reposToFix)) + + return nil +} + +func init() { + Register(&Check{ + Title: "Disable the actions unit for all mirrors", + Name: "disable-mirror-actions-unit", + IsDefault: false, + Run: disableMirrorActionsUnit, + Priority: 9, + }) +} diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index ddd07a64cbf..d27bbca8948 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -447,10 +447,10 @@ 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 - AssigneeID int64 Content string Files []string AllowMaintainerEdit bool diff --git a/services/issue/assignee.go b/services/issue/assignee.go index a0aa5a339b1..52ee9f2b22c 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 c5846e6104d..5cb6d035213 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 bab4e49998e..3362cb97ff7 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/services/repository/create.go b/services/repository/create.go index 261ac7fccca..0207f12a33f 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -381,8 +381,13 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re // insert units for repo defaultUnits := unit.DefaultRepoUnits - if isFork { + switch { + case isFork: defaultUnits = unit.DefaultForkRepoUnits + case repo.IsMirror: + defaultUnits = unit.DefaultMirrorRepoUnits + case repo.IsTemplate: + defaultUnits = unit.DefaultTemplateRepoUnits } units := make([]repo_model.RepoUnit, 0, len(defaultUnits)) for _, tp := range defaultUnits { diff --git a/templates/repo/issue/branch_selector_field.tmpl b/templates/repo/issue/branch_selector_field.tmpl index 643222ca72c..286ac0cd051 100644 --- a/templates/repo/issue/branch_selector_field.tmpl +++ b/templates/repo/issue/branch_selector_field.tmpl @@ -44,4 +44,5 @@ +
{{end}} diff --git a/templates/repo/issue/labels/label.tmpl b/templates/repo/issue/labels/label.tmpl deleted file mode 100644 index f40c792da70..00000000000 --- a/templates/repo/issue/labels/label.tmpl +++ /dev/null @@ -1,7 +0,0 @@ - - {{- ctx.RenderUtils.RenderLabel .label -}} - diff --git a/templates/repo/issue/labels/labels_selector_field.tmpl b/templates/repo/issue/labels/labels_selector_field.tmpl deleted file mode 100644 index 96fb6586640..00000000000 --- a/templates/repo/issue/labels/labels_selector_field.tmpl +++ /dev/null @@ -1,46 +0,0 @@ - diff --git a/templates/repo/issue/labels/labels_sidebar.tmpl b/templates/repo/issue/labels/labels_sidebar.tmpl deleted file mode 100644 index 0b7b9b89696..00000000000 --- a/templates/repo/issue/labels/labels_sidebar.tmpl +++ /dev/null @@ -1,11 +0,0 @@ -
- - {{ctx.Locale.Tr "repo.issues.new.no_label"}} - {{range .root.Labels}} - {{template "repo/issue/labels/label" dict "root" $.root "label" .}} - {{end}} - {{range .root.OrgLabels}} - {{template "repo/issue/labels/label" dict "root" $.root "label" .}} - {{end}} - -
diff --git a/templates/repo/issue/milestone/select_menu.tmpl b/templates/repo/issue/milestone/select_menu.tmpl deleted file mode 100644 index 9b0492ce524..00000000000 --- a/templates/repo/issue/milestone/select_menu.tmpl +++ /dev/null @@ -1,38 +0,0 @@ -{{if or .OpenMilestones .ClosedMilestones}} - -
-{{end}} -
{{ctx.Locale.Tr "repo.issues.new.clear_milestone"}}
-{{if and (not .OpenMilestones) (not .ClosedMilestones)}} -
- {{ctx.Locale.Tr "repo.issues.new.no_items"}} -
-{{else}} - {{if .OpenMilestones}} -
-
- {{ctx.Locale.Tr "repo.issues.new.open_milestone"}} -
- {{range .OpenMilestones}} - - {{svg "octicon-milestone" 16 "tw-mr-1"}} - {{.Name}} - - {{end}} - {{end}} - {{if .ClosedMilestones}} -
-
- {{ctx.Locale.Tr "repo.issues.new.closed_milestone"}} -
- {{range .ClosedMilestones}} - - {{svg "octicon-milestone" 16 "tw-mr-1"}} - {{.Name}} - - {{end}} - {{end}} -{{end}} diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index 6f1bebc0329..ceaaebc4d54 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -18,15 +18,15 @@ {{range .Fields}} {{if eq .Type "input"}} - {{template "repo/issue/fields/input" "item" .}} + {{template "repo/issue/fields/input" dict "item" .}} {{else if eq .Type "markdown"}} - {{template "repo/issue/fields/markdown" "item" .}} + {{template "repo/issue/fields/markdown" dict "item" .}} {{else if eq .Type "textarea"}} - {{template "repo/issue/fields/textarea" "item" . "root" $}} + {{template "repo/issue/fields/textarea" dict "item" . "root" $}} {{else if eq .Type "dropdown"}} - {{template "repo/issue/fields/dropdown" "item" .}} + {{template "repo/issue/fields/dropdown" dict "item" .}} {{else if eq .Type "checkboxes"}} - {{template "repo/issue/fields/checkboxes" "item" .}} + {{template "repo/issue/fields/checkboxes" dict "item" .}} {{end}} {{end}} {{else}} @@ -47,142 +47,24 @@
- {{template "repo/issue/branch_selector_field" .}} - - - {{template "repo/issue/labels/labels_selector_field" .}} - {{template "repo/issue/labels/labels_sidebar" dict "root" $}} - -
- - - -
- {{ctx.Locale.Tr "repo.issues.new.no_milestone"}} - -
- - {{if .IsProjectsEnabled}} -
- - - -
- {{ctx.Locale.Tr "repo.issues.new.no_projects"}} - -
+ {{template "repo/issue/branch_selector_field" $}} + {{if .PageIsComparePull}} + {{template "repo/issue/sidebar/reviewer_list" $.IssuePageMetaData}} +
{{end}} -
- - -
- - {{ctx.Locale.Tr "repo.issues.new.no_assignees"}} - - -
+ + {{template "repo/issue/sidebar/label_list" $.IssuePageMetaData}} + {{template "repo/issue/sidebar/milestone_list" $.IssuePageMetaData}} + {{if .IsProjectsEnabled}} + {{template "repo/issue/sidebar/project_list" $.IssuePageMetaData}} + {{end}} + {{template "repo/issue/sidebar/assignee_list" $.IssuePageMetaData}} + {{if and .PageIsComparePull (not (eq .HeadRepo.FullName .BaseCompareRepo.FullName)) .CanWriteToHeadRepo}}
-
-
- - -
+
+ +
{{end}}
diff --git a/templates/repo/issue/sidebar/allow_maintainer_edit.tmpl b/templates/repo/issue/sidebar/allow_maintainer_edit.tmpl index 43736deeed8..ad4ce96a475 100644 --- a/templates/repo/issue/sidebar/allow_maintainer_edit.tmpl +++ b/templates/repo/issue/sidebar/allow_maintainer_edit.tmpl @@ -1,15 +1,13 @@ {{if and .Issue.IsPull .IsIssuePoster (not .Issue.IsClosed) .Issue.PullRequest.HeadRepo}} {{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
-
-
- - -
+
+ +
{{end}} {{end}} diff --git a/templates/repo/issue/sidebar/assignee_list.tmpl b/templates/repo/issue/sidebar/assignee_list.tmpl index 260f7c5be4d..bee6123e52c 100644 --- a/templates/repo/issue/sidebar/assignee_list.tmpl +++ b/templates/repo/issue/sidebar/assignee_list.tmpl @@ -1,46 +1,35 @@ +{{$pageMeta := .}} +{{$data := .AssigneesData}} +{{$issueAssignees := NIL}}{{if $pageMeta.Issue}}{{$issueAssignees = $pageMeta.Issue.Assignees}}{{end}}
- -