diff options
Diffstat (limited to 'routers')
-rw-r--r-- | routers/api/packages/container/container.go | 31 | ||||
-rw-r--r-- | routers/api/v1/admin/user.go | 2 | ||||
-rw-r--r-- | routers/api/v1/org/org.go | 8 | ||||
-rw-r--r-- | routers/common/markup.go | 6 | ||||
-rw-r--r-- | routers/web/admin/users.go | 9 | ||||
-rw-r--r-- | routers/web/auth/auth.go | 2 | ||||
-rw-r--r-- | routers/web/auth/oauth.go | 8 | ||||
-rw-r--r-- | routers/web/org/org_labels.go | 36 | ||||
-rw-r--r-- | routers/web/repo/commit.go | 3 | ||||
-rw-r--r-- | routers/web/repo/issue.go | 4 | ||||
-rw-r--r-- | routers/web/repo/issue_comment.go | 5 | ||||
-rw-r--r-- | routers/web/repo/issue_label.go | 36 | ||||
-rw-r--r-- | routers/web/repo/issue_label_test.go | 94 | ||||
-rw-r--r-- | routers/web/repo/issue_view.go | 16 | ||||
-rw-r--r-- | routers/web/repo/release.go | 5 | ||||
-rw-r--r-- | routers/web/shared/label/label.go | 26 | ||||
-rw-r--r-- | routers/web/shared/user/header.go | 7 | ||||
-rw-r--r-- | routers/web/user/profile.go | 32 | ||||
-rw-r--r-- | routers/web/user/setting/profile.go | 5 |
19 files changed, 207 insertions, 128 deletions
diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go index a235923aac..53f1f130f8 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/optional" packages_module "code.gitea.io/gitea/modules/packages" container_module "code.gitea.io/gitea/modules/packages/container" "code.gitea.io/gitea/modules/setting" @@ -50,7 +51,7 @@ type containerHeaders struct { Range string Location string ContentType string - ContentLength int64 + ContentLength optional.Option[int64] } // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#legacy-docker-support-http-headers @@ -64,8 +65,8 @@ func setResponseHeaders(resp http.ResponseWriter, h *containerHeaders) { if h.ContentType != "" { resp.Header().Set("Content-Type", h.ContentType) } - if h.ContentLength != 0 { - resp.Header().Set("Content-Length", strconv.FormatInt(h.ContentLength, 10)) + if h.ContentLength.Has() { + resp.Header().Set("Content-Length", strconv.FormatInt(h.ContentLength.Value(), 10)) } if h.UploadUUID != "" { resp.Header().Set("Docker-Upload-Uuid", h.UploadUUID) @@ -403,12 +404,7 @@ func EndUploadBlob(ctx *context.Context) { } return } - doClose := true - defer func() { - if doClose { - uploader.Close() - } - }() + defer uploader.Close() if ctx.Req.Body != nil { if err := uploader.Append(ctx, ctx.Req.Body); err != nil { @@ -441,11 +437,10 @@ func EndUploadBlob(ctx *context.Context) { return } - if err := uploader.Close(); err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - doClose = false + // There was a strange bug: the "Close" fails with error "close .../tmp/package-upload/....: file already closed" + // AFAIK there should be no other "Close" call to the uploader between NewBlobUploader and this line. + // At least it's safe to call Close twice, so ignore the error. + _ = uploader.Close() if err := container_service.RemoveBlobUploadByID(ctx, uploader.ID); err != nil { apiError(ctx, http.StatusInternalServerError, err) @@ -511,7 +506,7 @@ func HeadBlob(ctx *context.Context) { setResponseHeaders(ctx.Resp, &containerHeaders{ ContentDigest: blob.Properties.GetByName(container_module.PropertyDigest), - ContentLength: blob.Blob.Size, + ContentLength: optional.Some(blob.Blob.Size), Status: http.StatusOK, }) } @@ -650,7 +645,7 @@ func HeadManifest(ctx *context.Context) { setResponseHeaders(ctx.Resp, &containerHeaders{ ContentDigest: manifest.Properties.GetByName(container_module.PropertyDigest), ContentType: manifest.Properties.GetByName(container_module.PropertyMediaType), - ContentLength: manifest.Blob.Size, + ContentLength: optional.Some(manifest.Blob.Size), Status: http.StatusOK, }) } @@ -714,14 +709,14 @@ func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor) headers := &containerHeaders{ ContentDigest: pfd.Properties.GetByName(container_module.PropertyDigest), ContentType: pfd.Properties.GetByName(container_module.PropertyMediaType), - ContentLength: pfd.Blob.Size, + ContentLength: optional.Some(pfd.Blob.Size), Status: http.StatusOK, } if u != nil { headers.Status = http.StatusTemporaryRedirect headers.Location = u.String() - headers.ContentLength = 0 // do not set Content-Length for redirect responses + headers.ContentLength = optional.None[int64]() // do not set Content-Length for redirect responses setResponseHeaders(ctx.Resp, headers) return } diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 85dd547ac1..c29f4e4622 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -239,7 +239,7 @@ func EditUser(ctx *context.APIContext) { Location: optional.FromPtr(form.Location), Description: optional.FromPtr(form.Description), IsActive: optional.FromPtr(form.Active), - IsAdmin: optional.FromPtr(form.Admin), + IsAdmin: user_service.UpdateOptionFieldFromPtr(form.Admin), Visibility: optional.FromNonDefault(api.VisibilityModes[form.Visibility]), AllowGitHook: optional.FromPtr(form.AllowGitHook), AllowImportLocal: optional.FromPtr(form.AllowImportLocal), diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index 27c646896a..adb117c4e8 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -26,12 +26,10 @@ import ( func listUserOrgs(ctx *context.APIContext, u *user_model.User) { listOptions := utils.GetListOptions(ctx) - showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == u.ID) - opts := organization.FindOrgOptions{ - ListOptions: listOptions, - UserID: u.ID, - IncludePrivate: showPrivate, + ListOptions: listOptions, + UserID: u.ID, + IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, u), } orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts) if err != nil { diff --git a/routers/common/markup.go b/routers/common/markup.go index 4c77ff33ed..00b2dd07c6 100644 --- a/routers/common/markup.go +++ b/routers/common/markup.go @@ -76,7 +76,11 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur }) rctx = rctx.WithMarkupType(markdown.MarkupName) case "comment": - rctx = renderhelper.NewRenderContextRepoComment(ctx, repoModel, renderhelper.RepoCommentOptions{DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName}) + rctx = renderhelper.NewRenderContextRepoComment(ctx, repoModel, renderhelper.RepoCommentOptions{ + DeprecatedOwnerName: repoOwnerName, + DeprecatedRepoName: repoName, + FootnoteContextID: "preview", + }) rctx = rctx.WithMarkupType(markdown.MarkupName) case "wiki": rctx = renderhelper.NewRenderContextRepoWiki(ctx, repoModel, renderhelper.RepoWikiOptions{DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName}) diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 83e207c717..27577cd35b 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/web/explore" @@ -292,9 +293,9 @@ func ViewUser(ctx *context.Context) { ctx.Data["EmailsTotal"] = len(emails) orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{ - ListOptions: db.ListOptionsAll, - UserID: u.ID, - IncludePrivate: true, + ListOptions: db.ListOptionsAll, + UserID: u.ID, + IncludeVisibility: structs.VisibleTypePrivate, }) if err != nil { ctx.ServerError("FindOrgs", err) @@ -431,7 +432,7 @@ func EditUserPost(ctx *context.Context) { Website: optional.Some(form.Website), Location: optional.Some(form.Location), IsActive: optional.Some(form.Active), - IsAdmin: optional.Some(form.Admin), + IsAdmin: user_service.UpdateOptionFieldFromValue(form.Admin), AllowGitHook: optional.Some(form.AllowGitHook), AllowImportLocal: optional.Some(form.AllowImportLocal), MaxRepoCreation: optional.Some(form.MaxRepoCreation), diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 69b9d285b7..87edbc357b 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -613,7 +613,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth. if user_model.CountUsers(ctx, nil) == 1 { opts := &user_service.UpdateOptions{ IsActive: optional.Some(true), - IsAdmin: optional.Some(true), + IsAdmin: user_service.UpdateOptionFieldFromValue(true), SetLastLogin: true, } if err := user_service.UpdateUser(ctx, u, opts); err != nil { diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 96c1dcf358..a13b987aab 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -193,8 +193,8 @@ func SignInOAuthCallback(ctx *context.Context) { source := authSource.Cfg.(*oauth2.Source) isAdmin, isRestricted := getUserAdminAndRestrictedFromGroupClaims(source, &gothUser) - u.IsAdmin = isAdmin.ValueOrDefault(false) - u.IsRestricted = isRestricted.ValueOrDefault(false) + u.IsAdmin = isAdmin.ValueOrDefault(user_service.UpdateOptionField[bool]{FieldValue: false}).FieldValue + u.IsRestricted = isRestricted.ValueOrDefault(setting.Service.DefaultUserIsRestricted) if !createAndHandleCreatedUser(ctx, templates.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) { // error already handled @@ -258,11 +258,11 @@ func getClaimedGroups(source *oauth2.Source, gothUser *goth.User) container.Set[ return claimValueToStringSet(groupClaims) } -func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *goth.User) (isAdmin, isRestricted optional.Option[bool]) { +func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *goth.User) (isAdmin optional.Option[user_service.UpdateOptionField[bool]], isRestricted optional.Option[bool]) { groups := getClaimedGroups(source, gothUser) if source.AdminGroup != "" { - isAdmin = optional.Some(groups.Contains(source.AdminGroup)) + isAdmin = user_service.UpdateOptionFieldFromSync(groups.Contains(source.AdminGroup)) } if source.RestrictedGroup != "" { isRestricted = optional.Some(groups.Contains(source.RestrictedGroup)) diff --git a/routers/web/org/org_labels.go b/routers/web/org/org_labels.go index 456ed3f01e..2a4aa7f557 100644 --- a/routers/web/org/org_labels.go +++ b/routers/web/org/org_labels.go @@ -4,13 +4,15 @@ package org import ( - "net/http" + "errors" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/modules/label" repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + shared_label "code.gitea.io/gitea/routers/web/shared/label" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" ) @@ -32,14 +34,8 @@ func RetrieveLabels(ctx *context.Context) { // NewLabel create new label for organization func NewLabel(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.CreateLabelForm) - ctx.Data["Title"] = ctx.Tr("repo.labels") - ctx.Data["PageIsLabels"] = true - ctx.Data["PageIsOrgSettings"] = true - - if ctx.HasError() { - ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) - ctx.Redirect(ctx.Org.OrgLink + "/settings/labels") + form := shared_label.GetLabelEditForm(ctx) + if ctx.Written() { return } @@ -55,20 +51,22 @@ func NewLabel(ctx *context.Context) { ctx.ServerError("NewLabel", err) return } - ctx.Redirect(ctx.Org.OrgLink + "/settings/labels") + ctx.JSONRedirect(ctx.Org.OrgLink + "/settings/labels") } // UpdateLabel update a label's name and color func UpdateLabel(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.CreateLabelForm) + form := shared_label.GetLabelEditForm(ctx) + if ctx.Written() { + return + } + l, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, form.ID) - if err != nil { - switch { - case issues_model.IsErrOrgLabelNotExist(err): - ctx.HTTPError(http.StatusNotFound) - default: - ctx.ServerError("UpdateLabel", err) - } + if errors.Is(err, util.ErrNotExist) { + ctx.JSONErrorNotFound() + return + } else if err != nil { + ctx.ServerError("GetLabelInOrgByID", err) return } @@ -82,7 +80,7 @@ func UpdateLabel(ctx *context.Context) { ctx.ServerError("UpdateLabel", err) return } - ctx.Redirect(ctx.Org.OrgLink + "/settings/labels") + ctx.JSONRedirect(ctx.Org.OrgLink + "/settings/labels") } // DeleteLabel delete a label diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 1ea881c2a7..9dd6988825 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -457,6 +457,9 @@ func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) ([]*git_m } if !ctx.Repo.CanRead(unit_model.TypeActions) { for _, commit := range commits { + if commit.Status == nil { + continue + } commit.Status.HideActionsURL(ctx) git_model.CommitStatusesHideActionsURL(ctx, commit.Statuses) } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index e70e8fdd7b..a4747964c6 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -364,7 +364,9 @@ func UpdateIssueContent(ctx *context.Context) { } } - rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository) + rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{ + FootnoteContextID: "0", + }) content, err := markdown.RenderString(rctx, issue.Content) if err != nil { ctx.ServerError("RenderString", err) diff --git a/routers/web/repo/issue_comment.go b/routers/web/repo/issue_comment.go index 9b51999fbd..c2a7f6b682 100644 --- a/routers/web/repo/issue_comment.go +++ b/routers/web/repo/issue_comment.go @@ -8,6 +8,7 @@ import ( "fmt" "html/template" "net/http" + "strconv" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/renderhelper" @@ -278,7 +279,9 @@ func UpdateCommentContent(ctx *context.Context) { var renderedContent template.HTML if comment.Content != "" { - rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository) + rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{ + FootnoteContextID: strconv.FormatInt(comment.ID, 10), + }) renderedContent, err = markdown.RenderString(rctx, comment.Content) if err != nil { ctx.ServerError("RenderString", err) diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go index f9c41adbcf..72a316e98d 100644 --- a/routers/web/repo/issue_label.go +++ b/routers/web/repo/issue_label.go @@ -4,6 +4,7 @@ package repo import ( + "errors" "net/http" "code.gitea.io/gitea/models/db" @@ -13,7 +14,9 @@ import ( "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + shared_label "code.gitea.io/gitea/routers/web/shared/label" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" issue_service "code.gitea.io/gitea/services/issue" @@ -100,13 +103,8 @@ func RetrieveLabelsForList(ctx *context.Context) { // NewLabel create new label for repository func NewLabel(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.CreateLabelForm) - ctx.Data["Title"] = ctx.Tr("repo.labels") - ctx.Data["PageIsLabels"] = true - - if ctx.HasError() { - ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) - ctx.Redirect(ctx.Repo.RepoLink + "/labels") + form := shared_label.GetLabelEditForm(ctx) + if ctx.Written() { return } @@ -122,34 +120,36 @@ func NewLabel(ctx *context.Context) { ctx.ServerError("NewLabel", err) return } - ctx.Redirect(ctx.Repo.RepoLink + "/labels") + ctx.JSONRedirect(ctx.Repo.RepoLink + "/labels") } // UpdateLabel update a label's name and color func UpdateLabel(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.CreateLabelForm) + form := shared_label.GetLabelEditForm(ctx) + if ctx.Written() { + return + } + l, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, form.ID) - if err != nil { - switch { - case issues_model.IsErrRepoLabelNotExist(err): - ctx.HTTPError(http.StatusNotFound) - default: - ctx.ServerError("UpdateLabel", err) - } + if errors.Is(err, util.ErrNotExist) { + ctx.JSONErrorNotFound() + return + } else if err != nil { + ctx.ServerError("GetLabelInRepoByID", err) return } + l.Name = form.Title l.Exclusive = form.Exclusive l.ExclusiveOrder = form.ExclusiveOrder l.Description = form.Description l.Color = form.Color - l.SetArchived(form.IsArchived) if err := issues_model.UpdateLabel(ctx, l); err != nil { ctx.ServerError("UpdateLabel", err) return } - ctx.Redirect(ctx.Repo.RepoLink + "/labels") + ctx.JSONRedirect(ctx.Repo.RepoLink + "/labels") } // DeleteLabel delete a label diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go index c3fba07034..f4eca26f8e 100644 --- a/routers/web/repo/issue_label_test.go +++ b/routers/web/repo/issue_label_test.go @@ -6,10 +6,12 @@ package repo import ( "net/http" "strconv" + "strings" "testing" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/web" @@ -19,19 +21,20 @@ import ( "github.com/stretchr/testify/assert" ) -func int64SliceToCommaSeparated(a []int64) string { - s := "" - for i, n := range a { - if i > 0 { - s += "," - } - s += strconv.Itoa(int(n)) - } - return s +func TestIssueLabel(t *testing.T) { + unittest.PrepareTestEnv(t) + t.Run("RetrieveLabels", testRetrieveLabels) + t.Run("NewLabel", testNewLabel) + t.Run("NewLabelInvalidColor", testNewLabelInvalidColor) + t.Run("UpdateLabel", testUpdateLabel) + t.Run("UpdateLabelInvalidColor", testUpdateLabelInvalidColor) + t.Run("UpdateIssueLabelClear", testUpdateIssueLabelClear) + t.Run("UpdateIssueLabelToggle", testUpdateIssueLabelToggle) + t.Run("InitializeLabels", testInitializeLabels) + t.Run("DeleteLabel", testDeleteLabel) } -func TestInitializeLabels(t *testing.T) { - unittest.PrepareTestEnv(t) +func testInitializeLabels(t *testing.T) { assert.NoError(t, repository.LoadRepoConfig()) ctx, _ := contexttest.MockContext(t, "user2/repo1/labels/initialize") contexttest.LoadUser(t, ctx, 2) @@ -47,8 +50,7 @@ func TestInitializeLabels(t *testing.T) { assert.Equal(t, "/user2/repo2/labels", test.RedirectURL(ctx.Resp)) } -func TestRetrieveLabels(t *testing.T) { - unittest.PrepareTestEnv(t) +func testRetrieveLabels(t *testing.T) { for _, testCase := range []struct { RepoID int64 Sort string @@ -74,9 +76,8 @@ func TestRetrieveLabels(t *testing.T) { } } -func TestNewLabel(t *testing.T) { - unittest.PrepareTestEnv(t) - ctx, _ := contexttest.MockContext(t, "user2/repo1/labels/edit") +func testNewLabel(t *testing.T) { + ctx, respWriter := contexttest.MockContext(t, "user2/repo1/labels/edit") contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) web.SetForm(ctx, &forms.CreateLabelForm{ @@ -84,17 +85,32 @@ func TestNewLabel(t *testing.T) { Color: "#abcdef", }) NewLabel(ctx) - assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ Name: "newlabel", Color: "#abcdef", }) - assert.Equal(t, "/user2/repo1/labels", test.RedirectURL(ctx.Resp)) + assert.Equal(t, "/user2/repo1/labels", test.RedirectURL(respWriter)) } -func TestUpdateLabel(t *testing.T) { - unittest.PrepareTestEnv(t) - ctx, _ := contexttest.MockContext(t, "user2/repo1/labels/edit") +func testNewLabelInvalidColor(t *testing.T) { + ctx, respWriter := contexttest.MockContext(t, "user2/repo1/labels/edit") + contexttest.LoadUser(t, ctx, 2) + contexttest.LoadRepo(t, ctx, 1) + web.SetForm(ctx, &forms.CreateLabelForm{ + Title: "newlabel-x", + Color: "bad-label-code", + }) + NewLabel(ctx) + assert.Equal(t, http.StatusBadRequest, ctx.Resp.WrittenStatus()) + assert.Equal(t, "repo.issues.label_color_invalid", test.ParseJSONError(respWriter.Body.Bytes()).ErrorMessage) + unittest.AssertNotExistsBean(t, &issues_model.Label{ + Name: "newlabel-x", + }) +} + +func testUpdateLabel(t *testing.T) { + ctx, respWriter := contexttest.MockContext(t, "user2/repo1/labels/edit") contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) web.SetForm(ctx, &forms.CreateLabelForm{ @@ -104,17 +120,37 @@ func TestUpdateLabel(t *testing.T) { IsArchived: true, }) UpdateLabel(ctx) - assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ ID: 2, Name: "newnameforlabel", Color: "#abcdef", }) - assert.Equal(t, "/user2/repo1/labels", test.RedirectURL(ctx.Resp)) + assert.Equal(t, "/user2/repo1/labels", test.RedirectURL(respWriter)) } -func TestDeleteLabel(t *testing.T) { - unittest.PrepareTestEnv(t) +func testUpdateLabelInvalidColor(t *testing.T) { + ctx, respWriter := contexttest.MockContext(t, "user2/repo1/labels/edit") + contexttest.LoadUser(t, ctx, 2) + contexttest.LoadRepo(t, ctx, 1) + web.SetForm(ctx, &forms.CreateLabelForm{ + ID: 1, + Title: "label1", + Color: "bad-label-code", + }) + + UpdateLabel(ctx) + + assert.Equal(t, http.StatusBadRequest, ctx.Resp.WrittenStatus()) + assert.Equal(t, "repo.issues.label_color_invalid", test.ParseJSONError(respWriter.Body.Bytes()).ErrorMessage) + unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ + ID: 1, + Name: "label1", + Color: "#abcdef", + }) +} + +func testDeleteLabel(t *testing.T) { ctx, _ := contexttest.MockContext(t, "user2/repo1/labels/delete") contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) @@ -126,8 +162,7 @@ func TestDeleteLabel(t *testing.T) { assert.EqualValues(t, ctx.Tr("repo.issues.label_deletion_success"), ctx.Flash.SuccessMsg) } -func TestUpdateIssueLabel_Clear(t *testing.T) { - unittest.PrepareTestEnv(t) +func testUpdateIssueLabelClear(t *testing.T) { ctx, _ := contexttest.MockContext(t, "user2/repo1/issues/labels") contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) @@ -140,7 +175,7 @@ func TestUpdateIssueLabel_Clear(t *testing.T) { unittest.CheckConsistencyFor(t, &issues_model.Label{}) } -func TestUpdateIssueLabel_Toggle(t *testing.T) { +func testUpdateIssueLabelToggle(t *testing.T) { for _, testCase := range []struct { Action string IssueIDs []int64 @@ -156,7 +191,8 @@ func TestUpdateIssueLabel_Toggle(t *testing.T) { ctx, _ := contexttest.MockContext(t, "user2/repo1/issues/labels") contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) - ctx.Req.Form.Set("issue_ids", int64SliceToCommaSeparated(testCase.IssueIDs)) + + ctx.Req.Form.Set("issue_ids", strings.Join(base.Int64sToStrings(testCase.IssueIDs), ",")) ctx.Req.Form.Set("action", testCase.Action) ctx.Req.Form.Set("id", strconv.Itoa(int(testCase.LabelID))) UpdateIssueLabel(ctx) diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index 13b9d83da4..2897652d51 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "sort" + "strconv" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" @@ -624,7 +625,9 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue comment.Issue = issue if comment.Type == issues_model.CommentTypeComment || comment.Type == issues_model.CommentTypeReview { - rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo) + rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo, renderhelper.RepoCommentOptions{ + FootnoteContextID: strconv.FormatInt(comment.ID, 10), + }) comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content) if err != nil { ctx.ServerError("RenderString", err) @@ -700,7 +703,9 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue } } } else if comment.Type.HasContentSupport() { - rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo) + rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo, renderhelper.RepoCommentOptions{ + FootnoteContextID: strconv.FormatInt(comment.ID, 10), + }) comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content) if err != nil { ctx.ServerError("RenderString", err) @@ -757,6 +762,9 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue } if !ctx.Repo.CanRead(unit.TypeActions) { for _, commit := range comment.Commits { + if commit.Status == nil { + continue + } commit.Status.HideActionsURL(ctx) git_model.CommitStatusesHideActionsURL(ctx, commit.Statuses) } @@ -981,7 +989,9 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss func prepareIssueViewContent(ctx *context.Context, issue *issues_model.Issue) { var err error - rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository) + rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{ + FootnoteContextID: "0", // Set footnote context ID to 0 for the issue content + }) issue.RenderedContent, err = markdown.RenderString(rctx, issue.Content) if err != nil { ctx.ServerError("RenderString", err) diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 2baf434e75..8e586adde9 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "strings" "code.gitea.io/gitea/models/db" @@ -113,7 +114,9 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) cacheUsers[r.PublisherID] = r.Publisher } - rctx := renderhelper.NewRenderContextRepoComment(ctx, r.Repo) + rctx := renderhelper.NewRenderContextRepoComment(ctx, r.Repo, renderhelper.RepoCommentOptions{ + FootnoteContextID: strconv.FormatInt(r.ID, 10), + }) r.RenderedNote, err = markdown.RenderString(rctx, r.Note) if err != nil { return nil, err diff --git a/routers/web/shared/label/label.go b/routers/web/shared/label/label.go new file mode 100644 index 0000000000..6968a318c4 --- /dev/null +++ b/routers/web/shared/label/label.go @@ -0,0 +1,26 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package label + +import ( + "code.gitea.io/gitea/modules/label" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/forms" +) + +func GetLabelEditForm(ctx *context.Context) *forms.CreateLabelForm { + form := web.GetForm(ctx).(*forms.CreateLabelForm) + if ctx.HasError() { + ctx.JSONError(ctx.Data["ErrorMsg"].(string)) + return nil + } + var err error + form.Color, err = label.NormalizeColor(form.Color) + if err != nil { + ctx.JSONError(ctx.Tr("repo.issues.label_color_invalid")) + return nil + } + return form +} diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go index d3b196b6a3..2bd0abc4c0 100644 --- a/routers/web/shared/user/header.go +++ b/routers/web/shared/user/header.go @@ -47,13 +47,12 @@ func prepareContextForProfileBigAvatar(ctx *context.Context) { ctx.Data["RenderedDescription"] = content } - showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{ - UserID: ctx.ContextUser.ID, - IncludePrivate: showPrivate, + UserID: ctx.ContextUser.ID, + IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser), ListOptions: db.ListOptions{ Page: 1, - // query one more results (without a separate counting) to see whether we need to add the "show more orgs" link + // query one more result (without a separate counting) to see whether we need to add the "show more orgs" link PageSize: setting.UI.User.OrgPagingNum + 1, }, }) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 58acfb9518..f6d50cf5fe 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -63,21 +63,9 @@ func userProfile(ctx *context.Context) { ctx.Data["Title"] = ctx.ContextUser.DisplayName() ctx.Data["PageIsUserProfile"] = true - // prepare heatmap data - if setting.Service.EnableUserHeatmap { - data, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer) - if err != nil { - ctx.ServerError("GetUserHeatmapDataByUser", err) - return - } - ctx.Data["HeatmapData"] = data - ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) - } - profileDbRepo, profileReadmeBlob := shared_user.FindOwnerProfileReadme(ctx, ctx.Doer) - showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) - prepareUserProfileTabData(ctx, showPrivate, profileDbRepo, profileReadmeBlob) + prepareUserProfileTabData(ctx, profileDbRepo, profileReadmeBlob) // prepare the user nav header data after "prepareUserProfileTabData" to avoid re-querying the NumFollowers & NumFollowing // because ctx.Data["NumFollowers"] and "NumFollowing" logic duplicates in both of them @@ -90,7 +78,7 @@ func userProfile(ctx *context.Context) { ctx.HTML(http.StatusOK, tplProfile) } -func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) { +func prepareUserProfileTabData(ctx *context.Context, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) { // if there is a profile readme, default to "overview" page, otherwise, default to "repositories" page // if there is not a profile readme, the overview tab should be treated as the repositories tab tab := ctx.FormString("tab") @@ -173,8 +161,20 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb ctx.Data["Cards"] = following total = int(numFollowing) case "activity": + // prepare heatmap data + if setting.Service.EnableUserHeatmap { + data, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer) + if err != nil { + ctx.ServerError("GetUserHeatmapDataByUser", err) + return + } + ctx.Data["HeatmapData"] = data + ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) + } + date := ctx.FormString("date") pagingNum = setting.UI.FeedPagingNum + showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) items, count, err := feed_service.GetFeeds(ctx, activities_model.GetFeedsOptions{ RequestedUser: ctx.ContextUser, Actor: ctx.Doer, @@ -265,8 +265,8 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb } case "organizations": orgs, count, err := db.FindAndCount[organization.Organization](ctx, organization.FindOrgOptions{ - UserID: ctx.ContextUser.ID, - IncludePrivate: showPrivate, + UserID: ctx.ContextUser.ID, + IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser), ListOptions: db.ListOptions{ Page: page, PageSize: pagingNum, diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 368837ce1b..98995cd69c 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/typesniffer" @@ -206,8 +207,8 @@ func Organization(ctx *context.Context) { PageSize: setting.UI.Admin.UserPagingNum, Page: ctx.FormInt("page"), }, - UserID: ctx.Doer.ID, - IncludePrivate: ctx.IsSigned, + UserID: ctx.Doer.ID, + IncludeVisibility: structs.VisibleTypePrivate, } if opts.Page <= 0 { |