From 4c29c75968f520123f125e8305b2c29198664251 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 27 Dec 2023 15:24:23 +0800 Subject: [PATCH 01/11] Fix session key conflict with database keyword (#28613) This is a regression from #28220 . `builder.Cond` will not add `` ` `` automatically but xorm method `Get/Find` adds `` ` ``. This PR also adds tests to prevent the method from being implemented incorrectly. The tests are added in `integrations` to test every database. --- models/auth/session.go | 17 ++++++++------ tests/integration/session_test.go | 37 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 tests/integration/session_test.go diff --git a/models/auth/session.go b/models/auth/session.go index 60fdeaba7c203..75a205f702b56 100644 --- a/models/auth/session.go +++ b/models/auth/session.go @@ -41,12 +41,15 @@ func ReadSession(ctx context.Context, key string) (*Session, error) { } defer committer.Close() - session, exist, err := db.Get[Session](ctx, builder.Eq{"key": key}) + session, exist, err := db.Get[Session](ctx, builder.Eq{"`key`": key}) if err != nil { return nil, err } else if !exist { - session.Expiry = timeutil.TimeStampNow() - if err := db.Insert(ctx, &session); err != nil { + session = &Session{ + Key: key, + Expiry: timeutil.TimeStampNow(), + } + if err := db.Insert(ctx, session); err != nil { return nil, err } } @@ -56,7 +59,7 @@ func ReadSession(ctx context.Context, key string) (*Session, error) { // ExistSession checks if a session exists func ExistSession(ctx context.Context, key string) (bool, error) { - return db.Exist[Session](ctx, builder.Eq{"key": key}) + return db.Exist[Session](ctx, builder.Eq{"`key`": key}) } // DestroySession destroys a session @@ -75,13 +78,13 @@ func RegenerateSession(ctx context.Context, oldKey, newKey string) (*Session, er } defer committer.Close() - if has, err := db.Exist[Session](ctx, builder.Eq{"key": newKey}); err != nil { + if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": newKey}); err != nil { return nil, err } else if has { return nil, fmt.Errorf("session Key: %s already exists", newKey) } - if has, err := db.Exist[Session](ctx, builder.Eq{"key": oldKey}); err != nil { + if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": oldKey}); err != nil { return nil, err } else if !has { if err := db.Insert(ctx, &Session{ @@ -96,7 +99,7 @@ func RegenerateSession(ctx context.Context, oldKey, newKey string) (*Session, er return nil, err } - s, _, err := db.Get[Session](ctx, builder.Eq{"key": newKey}) + s, _, err := db.Get[Session](ctx, builder.Eq{"`key`": newKey}) if err != nil { // is not exist, it should be impossible return nil, err diff --git a/tests/integration/session_test.go b/tests/integration/session_test.go new file mode 100644 index 0000000000000..d47148efa23a8 --- /dev/null +++ b/tests/integration/session_test.go @@ -0,0 +1,37 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "testing" + + "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func Test_RegenerateSession(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + assert.NoError(t, unittest.PrepareTestDatabase()) + + key := "new_key890123456" // it must be 16 characters long + key2 := "new_key890123457" // it must be 16 characters + exist, err := auth.ExistSession(db.DefaultContext, key) + assert.NoError(t, err) + assert.False(t, exist) + + sess, err := auth.RegenerateSession(db.DefaultContext, "", key) + assert.NoError(t, err) + assert.EqualValues(t, key, sess.Key) + assert.Len(t, sess.Data, 0) + + sess, err = auth.ReadSession(db.DefaultContext, key2) + assert.NoError(t, err) + assert.EqualValues(t, key2, sess.Key) + assert.Len(t, sess.Data, 0) +} From baf0d402d9cb47849394202fcfc7c2e23b0faac3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 27 Dec 2023 15:57:54 +0800 Subject: [PATCH 02/11] Add get actions runner registration token for API routes, repo, org, user and global level (#27144) Replace #23761 --------- Co-authored-by: Denys Konovalov Co-authored-by: techknowlogick --- routers/api/v1/admin/runners.go | 26 +++++ routers/api/v1/api.go | 49 ++++++--- routers/api/v1/org/runners.go | 31 ++++++ routers/api/v1/org/{action.go => secrets.go} | 0 routers/api/v1/repo/runners.go | 34 +++++++ routers/api/v1/shared/runners.go | 32 ++++++ routers/api/v1/user/runners.go | 26 +++++ templates/swagger/v1_json.tmpl | 101 +++++++++++++++++++ 8 files changed, 285 insertions(+), 14 deletions(-) create mode 100644 routers/api/v1/admin/runners.go create mode 100644 routers/api/v1/org/runners.go rename routers/api/v1/org/{action.go => secrets.go} (100%) create mode 100644 routers/api/v1/repo/runners.go create mode 100644 routers/api/v1/shared/runners.go create mode 100644 routers/api/v1/user/runners.go diff --git a/routers/api/v1/admin/runners.go b/routers/api/v1/admin/runners.go new file mode 100644 index 0000000000000..c0d9364435071 --- /dev/null +++ b/routers/api/v1/admin/runners.go @@ -0,0 +1,26 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package admin + +import ( + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/routers/api/v1/shared" +) + +// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization + +// GetRegistrationToken returns the token to register global runners +func GetRegistrationToken(ctx *context.APIContext) { + // swagger:operation GET /admin/runners/registration-token admin adminGetRunnerRegistrationToken + // --- + // summary: Get an global actions runner registration token + // produces: + // - application/json + // parameters: + // responses: + // "200": + // "$ref": "#/responses/RegistrationToken" + + shared.GetRegistrationToken(ctx, 0, 0) +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index a4c3d6f4440ae..4fe4e20e79485 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -948,11 +948,17 @@ func Routes() *web.Route { Post(bind(api.CreateEmailOption{}), user.AddEmail). Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail) - // create or update a user's actions secrets - m.Group("/actions/secrets", func() { - m.Combo("/{secretname}"). - Put(bind(api.CreateOrUpdateSecretOption{}), user.CreateOrUpdateSecret). - Delete(user.DeleteSecret) + // manage user-level actions features + m.Group("/actions", func() { + m.Group("/secrets", func() { + m.Combo("/{secretname}"). + Put(bind(api.CreateOrUpdateSecretOption{}), user.CreateOrUpdateSecret). + Delete(user.DeleteSecret) + }) + + m.Group("/runners", func() { + m.Get("/registration-token", reqToken(), user.GetRegistrationToken) + }) }) m.Get("/followers", user.ListMyFollowers) @@ -1052,10 +1058,16 @@ func Routes() *web.Route { m.Post("/accept", repo.AcceptTransfer) m.Post("/reject", repo.RejectTransfer) }, reqToken()) - m.Group("/actions/secrets", func() { - m.Combo("/{secretname}"). - Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret). - Delete(reqToken(), reqOwner(), repo.DeleteSecret) + m.Group("/actions", func() { + m.Group("/secrets", func() { + m.Combo("/{secretname}"). + Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret). + Delete(reqToken(), reqOwner(), repo.DeleteSecret) + }) + + m.Group("/runners", func() { + m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken) + }) }) m.Group("/hooks/git", func() { m.Combo("").Get(repo.ListGitHooks) @@ -1422,11 +1434,17 @@ func Routes() *web.Route { m.Combo("/{username}").Get(reqToken(), org.IsMember). Delete(reqToken(), reqOrgOwnership(), org.DeleteMember) }) - m.Group("/actions/secrets", func() { - m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets) - m.Combo("/{secretname}"). - Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret). - Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret) + m.Group("/actions", func() { + m.Group("/secrets", func() { + m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets) + m.Combo("/{secretname}"). + Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret). + Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret) + }) + + m.Group("/runners", func() { + m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken) + }) }) m.Group("/public_members", func() { m.Get("", org.ListPublicMembers) @@ -1518,6 +1536,9 @@ func Routes() *web.Route { Patch(bind(api.EditHookOption{}), admin.EditHook). Delete(admin.DeleteHook) }) + m.Group("/runners", func() { + m.Get("/registration-token", admin.GetRegistrationToken) + }) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin()) m.Group("/topics", func() { diff --git a/routers/api/v1/org/runners.go b/routers/api/v1/org/runners.go new file mode 100644 index 0000000000000..05bce8daefbc9 --- /dev/null +++ b/routers/api/v1/org/runners.go @@ -0,0 +1,31 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package org + +import ( + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/routers/api/v1/shared" +) + +// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization + +// GetRegistrationToken returns the token to register org runners +func GetRegistrationToken(ctx *context.APIContext) { + // swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken + // --- + // summary: Get an organization's actions runner registration token + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/RegistrationToken" + + shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0) +} diff --git a/routers/api/v1/org/action.go b/routers/api/v1/org/secrets.go similarity index 100% rename from routers/api/v1/org/action.go rename to routers/api/v1/org/secrets.go diff --git a/routers/api/v1/repo/runners.go b/routers/api/v1/repo/runners.go new file mode 100644 index 0000000000000..0a2bbf81176c9 --- /dev/null +++ b/routers/api/v1/repo/runners.go @@ -0,0 +1,34 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/routers/api/v1/shared" +) + +// GetRegistrationToken returns the token to register repo runners +func GetRegistrationToken(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/runners/registration-token repository repoGetRunnerRegistrationToken + // --- + // summary: Get a repository's actions runner registration token + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/RegistrationToken" + + shared.GetRegistrationToken(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID) +} diff --git a/routers/api/v1/shared/runners.go b/routers/api/v1/shared/runners.go new file mode 100644 index 0000000000000..a342bd4b63712 --- /dev/null +++ b/routers/api/v1/shared/runners.go @@ -0,0 +1,32 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package shared + +import ( + "errors" + "net/http" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/util" +) + +// RegistrationToken is response related to registeration token +// swagger:response RegistrationToken +type RegistrationToken struct { + Token string `json:"token"` +} + +func GetRegistrationToken(ctx *context.APIContext, ownerID, repoID int64) { + token, err := actions_model.GetLatestRunnerToken(ctx, ownerID, repoID) + if errors.Is(err, util.ErrNotExist) || (token != nil && !token.IsActive) { + token, err = actions_model.NewRunnerToken(ctx, ownerID, repoID) + } + if err != nil { + ctx.InternalServerError(err) + return + } + + ctx.JSON(http.StatusOK, RegistrationToken{Token: token.Token}) +} diff --git a/routers/api/v1/user/runners.go b/routers/api/v1/user/runners.go new file mode 100644 index 0000000000000..51556ae0fb8c0 --- /dev/null +++ b/routers/api/v1/user/runners.go @@ -0,0 +1,26 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package user + +import ( + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/routers/api/v1/shared" +) + +// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization + +// GetRegistrationToken returns the token to register user runners +func GetRegistrationToken(ctx *context.APIContext) { + // swagger:operation GET /user/actions/runners/registration-token user userGetRunnerRegistrationToken + // --- + // summary: Get an user's actions runner registration token + // produces: + // - application/json + // parameters: + // responses: + // "200": + // "$ref": "#/responses/RegistrationToken" + + shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0) +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 215c1692f61a0..de3bc331f160b 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -392,6 +392,23 @@ } } }, + "/admin/runners/registration-token": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get an global actions runner registration token", + "operationId": "adminGetRunnerRegistrationToken", + "responses": { + "200": { + "$ref": "#/responses/RegistrationToken" + } + } + } + }, "/admin/unadopted": { "get": { "produces": [ @@ -1562,6 +1579,32 @@ } } }, + "/orgs/{org}/actions/runners/registration-token": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "organization" + ], + "summary": "Get an organization's actions runner registration token", + "operationId": "orgGetRunnerRegistrationToken", + "parameters": [ + { + "type": "string", + "description": "name of the organization", + "name": "org", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/RegistrationToken" + } + } + } + }, "/orgs/{org}/actions/secrets": { "get": { "produces": [ @@ -12359,6 +12402,39 @@ } } }, + "/repos/{owner}/{repo}/runners/registration-token": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Get a repository's actions runner registration token", + "operationId": "repoGetRunnerRegistrationToken", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/RegistrationToken" + } + } + } + }, "/repos/{owner}/{repo}/signing-key.gpg": { "get": { "produces": [ @@ -14517,6 +14593,23 @@ } } }, + "/user/actions/runners/registration-token": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get an user's actions runner registration token", + "operationId": "userGetRunnerRegistrationToken", + "responses": { + "200": { + "$ref": "#/responses/RegistrationToken" + } + } + } + }, "/user/actions/secrets/{secretname}": { "put": { "consumes": [ @@ -23726,6 +23819,14 @@ } } }, + "RegistrationToken": { + "description": "RegistrationToken is response related to registeration token", + "headers": { + "token": { + "type": "string" + } + } + }, "Release": { "description": "Release", "schema": { From 42149ff1a816501643ec2407ed61a83bf5b65059 Mon Sep 17 00:00:00 2001 From: katsu Date: Wed, 27 Dec 2023 16:32:27 +0800 Subject: [PATCH 03/11] fix wrong link in user and organization profile when using relative url (#28617) fix #28436. the doc https://docs.gitea.com/usage/profile-readme maybe also need to be updated to tell that the main branch is necessary,which means the following three conditions should be satisfied: - repo: **.profile** - branch: **[default branch]** - markdown: **README.md** --- routers/web/org/home.go | 17 +++++++++++------ routers/web/shared/user/header.go | 6 +++--- routers/web/user/profile.go | 20 ++++++++++++++------ 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/routers/web/org/home.go b/routers/web/org/home.go index a9adfdc03cd8f..cdf280ed4acc3 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" shared_user "code.gitea.io/gitea/routers/web/shared/user" ) @@ -157,14 +158,14 @@ func Home(ctx *context.Context) { ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0 - profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer) + profileDbRepo, profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer) defer profileClose() - prepareOrgProfileReadme(ctx, profileGitRepo, profileReadmeBlob) + prepareOrgProfileReadme(ctx, profileGitRepo, profileDbRepo, profileReadmeBlob) ctx.HTML(http.StatusOK, tplOrgHome) } -func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repository, profileReadme *git.Blob) { +func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repository, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) { if profileGitRepo == nil || profileReadme == nil { return } @@ -172,10 +173,14 @@ func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repositor if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil { log.Error("failed to GetBlobContent: %v", err) } else { + // Pass URLPrefix to markdown render for the full link of media elements. + // The profile of default branch would be shown. + prefix := profileDbRepo.Link() + "/src/branch/" + util.PathEscapeSegments(profileDbRepo.DefaultBranch) if profileContent, err := markdown.RenderString(&markup.RenderContext{ - Ctx: ctx, - GitRepo: profileGitRepo, - Metas: map[string]string{"mode": "document"}, + Ctx: ctx, + GitRepo: profileGitRepo, + URLPrefix: prefix, + Metas: map[string]string{"mode": "document"}, }, bytes); err != nil { log.Error("failed to RenderString: %v", err) } else { diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go index 411d499eb4f70..919a080b42c65 100644 --- a/routers/web/shared/user/header.go +++ b/routers/web/shared/user/header.go @@ -87,7 +87,7 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) { } } -func FindUserProfileReadme(ctx *context.Context, doer *user_model.User) (profileGitRepo *git.Repository, profileReadmeBlob *git.Blob, profileClose func()) { +func FindUserProfileReadme(ctx *context.Context, doer *user_model.User) (profileDbRepo *repo_model.Repository, profileGitRepo *git.Repository, profileReadmeBlob *git.Blob, profileClose func()) { profileDbRepo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, ".profile") if err == nil { perm, err := access_model.GetUserRepoPermission(ctx, profileDbRepo, doer) @@ -105,7 +105,7 @@ func FindUserProfileReadme(ctx *context.Context, doer *user_model.User) (profile } else if !repo_model.IsErrRepoNotExist(err) { log.Error("FindUserProfileReadme failed to GetRepositoryByName: %v", err) } - return profileGitRepo, profileReadmeBlob, func() { + return profileDbRepo, profileGitRepo, profileReadmeBlob, func() { if profileGitRepo != nil { _ = profileGitRepo.Close() } @@ -115,7 +115,7 @@ func FindUserProfileReadme(ctx *context.Context, doer *user_model.User) (profile func RenderUserHeader(ctx *context.Context) { prepareContextForCommonProfile(ctx) - _, profileReadmeBlob, profileClose := FindUserProfileReadme(ctx, ctx.Doer) + _, _, profileReadmeBlob, profileClose := FindUserProfileReadme(ctx, ctx.Doer) defer profileClose() ctx.Data["HasProfileReadme"] = profileReadmeBlob != nil } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index ac278e300d701..a8ab3dde81d01 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -64,17 +64,17 @@ func userProfile(ctx *context.Context) { ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) } - profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer) + profileDbRepo, profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer) defer profileClose() showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) - prepareUserProfileTabData(ctx, showPrivate, profileGitRepo, profileReadmeBlob) + prepareUserProfileTabData(ctx, showPrivate, profileDbRepo, profileGitRepo, profileReadmeBlob) // call PrepareContextForProfileBigAvatar later to avoid re-querying the NumFollowers & NumFollowing shared_user.PrepareContextForProfileBigAvatar(ctx) ctx.HTML(http.StatusOK, tplProfile) } -func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileGitRepo *git.Repository, profileReadme *git.Blob) { +func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDbRepo *repo_model.Repository, profileGitRepo *git.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") @@ -233,10 +233,18 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileGi if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil { log.Error("failed to GetBlobContent: %v", err) } else { + // Give the URLPrefix to the markdown render for the full link of media element. + // the media link usually be like /[user]/[repoName]/media/branch/[branchName], + // Eg. /Tom/.profile/media/branch/main + // The branch shown on the profile page is the default branch, this need to be in sync with doc, see: + // https://docs.gitea.com/usage/profile-readme + + prefix := profileDbRepo.Link() + "/src/branch/" + util.PathEscapeSegments(profileDbRepo.DefaultBranch) if profileContent, err := markdown.RenderString(&markup.RenderContext{ - Ctx: ctx, - GitRepo: profileGitRepo, - Metas: map[string]string{"mode": "document"}, + Ctx: ctx, + GitRepo: profileGitRepo, + URLPrefix: prefix, + Metas: map[string]string{"mode": "document"}, }, bytes); err != nil { log.Error("failed to RenderString: %v", err) } else { From c706b3e4366689ce5afca19476eaf18045e874db Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Thu, 28 Dec 2023 00:24:21 +0000 Subject: [PATCH 04/11] [skip ci] Updated translations via Crowdin --- options/locale/locale_fr-FR.ini | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index d9fcf14b5eeb6..f234f801f8973 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -1665,10 +1665,10 @@ issues.review.un_resolve_conversation=Rouvrir la conversation issues.review.resolved_by=a marqué cette conversation comme résolue. issues.assignee.error=Tous les assignés n'ont pas été ajoutés en raison d'une erreur inattendue. issues.reference_issue.body=Corps -issues.content_history.deleted=supprimé -issues.content_history.edited=édité -issues.content_history.created=créé -issues.content_history.delete_from_history=Supprimé de l’historique +issues.content_history.deleted=a supprimé +issues.content_history.edited=a édité +issues.content_history.created=a créé +issues.content_history.delete_from_history=Supprimer de l’historique issues.content_history.delete_from_history_confirm=Supprimer de l’historique ? issues.content_history.options=Options issues.reference_link=Référence : %s @@ -3521,6 +3521,7 @@ runs.actors_no_select=Tous les acteurs runs.status_no_select=Touts les statuts runs.no_results=Aucun résultat correspondant. runs.no_runs=Le flux de travail n'a pas encore d'exécution. +runs.empty_commit_message=(message de révision vide) workflow.disable=Désactiver le flux de travail workflow.disable_success=Le flux de travail « %s » a bien été désactivé. From 4cd666d7dcc7531806cde65c6468f93529cc23dd Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Thu, 28 Dec 2023 04:59:00 +0100 Subject: [PATCH 05/11] Do not set `Accept` header twice (#28598) Revert #28550 Don't add the `Accept` header twice. --- modules/lfs/http_client.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/lfs/http_client.go b/modules/lfs/http_client.go index 4177473362387..de0b1e4fede49 100644 --- a/modules/lfs/http_client.go +++ b/modules/lfs/http_client.go @@ -79,10 +79,7 @@ func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Poin return nil, err } - req, err := createRequest(ctx, http.MethodPost, url, map[string]string{ - "Content-Type": MediaType, - "Accept": MediaType, - }, payload) + req, err := createRequest(ctx, http.MethodPost, url, map[string]string{"Content-Type": MediaType}, payload) if err != nil { return nil, err } From 921df1cbad83dcba37ff12cba88c7d3a69f8588b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Dec 2023 15:28:57 +0800 Subject: [PATCH 06/11] Remove unnecessary syncbranchToDB with tests (#28624) #28361 introduced `syncBranchToDB` in `CreateNewBranchFromCommit`. This PR will revert the change because it's unnecessary. Every push will already be checked by `syncBranchToDB`. This PR also created a test to ensure it's right. --- services/repository/branch.go | 29 +++++++--------------- tests/integration/api_branch_test.go | 36 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/services/repository/branch.go b/services/repository/branch.go index dca938444aa35..7254778763371 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -276,28 +276,17 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo return err } - return db.WithTx(ctx, func(ctx context.Context) error { - commit, err := gitRepo.GetCommit(commitID) - if err != nil { - return err - } - // database operation should be done before git operation so that we can rollback if git operation failed - if err := syncBranchToDB(ctx, repo.ID, doer.ID, branchName, commit); err != nil { + if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{ + Remote: repo.RepoPath(), + Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName), + Env: repo_module.PushingEnvironment(doer, repo), + }); err != nil { + if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { return err } - - if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{ - Remote: repo.RepoPath(), - Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName), - Env: repo_module.PushingEnvironment(doer, repo), - }); err != nil { - if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { - return err - } - return fmt.Errorf("push: %w", err) - } - return nil - }) + return fmt.Errorf("push: %w", err) + } + return nil } // RenameBranch rename a branch diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index 28e690b356890..103f8f707f6da 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -9,6 +9,8 @@ import ( "testing" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/tests" @@ -217,3 +219,37 @@ func TestAPIBranchProtection(t *testing.T) { testAPIDeleteBranch(t, "master", http.StatusForbidden) testAPIDeleteBranch(t, "branch2", http.StatusNoContent) } + +func TestAPICreateBranchWithSyncBranches(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + branches, err := db.Find[git_model.Branch](db.DefaultContext, git_model.FindBranchOptions{ + RepoID: 1, + }) + assert.NoError(t, err) + assert.Len(t, branches, 4) + + // make a broke repository with no branch on database + _, err = db.DeleteByBean(db.DefaultContext, git_model.Branch{RepoID: 1}) + assert.NoError(t, err) + + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + giteaURL.Path = ctx.GitPath() + + testAPICreateBranch(t, ctx.Session, "user2", "repo1", "", "new_branch", http.StatusCreated) + }) + + branches, err = db.Find[git_model.Branch](db.DefaultContext, git_model.FindBranchOptions{ + RepoID: 1, + }) + assert.NoError(t, err) + assert.Len(t, branches, 5) + + branches, err = db.Find[git_model.Branch](db.DefaultContext, git_model.FindBranchOptions{ + RepoID: 1, + Keyword: "new_branch", + }) + assert.NoError(t, err) + assert.Len(t, branches, 1) +} From f3999888c0b765380003f814f9c3d5cf80128167 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 28 Dec 2023 17:38:59 +0800 Subject: [PATCH 07/11] Refactor some legacy code and remove unused code (#28622) 1. use slices.Contains, remove Int64sContains 2. use HashEmail, remove base.EncodeMD5 3. remove BasicAuthEncode, IsLetter --- models/avatars/avatar.go | 7 ++++-- models/git/protected_branch.go | 7 +++--- models/git/protected_tag.go | 4 ++-- models/issues/review.go | 4 ++-- modules/base/tool.go | 30 ------------------------- modules/base/tool_test.go | 35 ----------------------------- routers/web/user/setting/profile.go | 3 ++- 7 files changed, 14 insertions(+), 76 deletions(-) diff --git a/models/avatars/avatar.go b/models/avatars/avatar.go index c197a22dc1aea..bbe16483bf7f6 100644 --- a/models/avatars/avatar.go +++ b/models/avatars/avatar.go @@ -5,6 +5,8 @@ package avatars import ( "context" + "crypto/md5" + "encoding/hex" "fmt" "net/url" "path" @@ -13,7 +15,6 @@ import ( "sync/atomic" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -90,7 +91,9 @@ func DefaultAvatarLink() string { // HashEmail hashes email address to MD5 string. https://en.gravatar.com/site/implement/hash/ func HashEmail(email string) string { - return base.EncodeMD5(strings.ToLower(strings.TrimSpace(email))) + m := md5.New() + _, _ = m.Write([]byte(strings.ToLower(strings.TrimSpace(email)))) + return hex.EncodeToString(m.Sum(nil)) } // GetEmailForHash converts a provided md5sum to the email diff --git a/models/git/protected_branch.go b/models/git/protected_branch.go index 528acc175a27a..66a4b52b1790e 100644 --- a/models/git/protected_branch.go +++ b/models/git/protected_branch.go @@ -17,7 +17,6 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -127,7 +126,7 @@ func (protectBranch *ProtectedBranch) CanUserPush(ctx context.Context, user *use return writeAccess } - if base.Int64sContains(protectBranch.WhitelistUserIDs, user.ID) { + if slices.Contains(protectBranch.WhitelistUserIDs, user.ID) { return true } @@ -150,7 +149,7 @@ func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, return permissionInRepo.CanWrite(unit.TypeCode) } - if base.Int64sContains(protectBranch.MergeWhitelistUserIDs, userID) { + if slices.Contains(protectBranch.MergeWhitelistUserIDs, userID) { return true } @@ -182,7 +181,7 @@ func IsUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch, return writeAccess, nil } - if base.Int64sContains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) { + if slices.Contains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) { return true, nil } diff --git a/models/git/protected_tag.go b/models/git/protected_tag.go index 12e53a3331e99..8a0504565125b 100644 --- a/models/git/protected_tag.go +++ b/models/git/protected_tag.go @@ -6,11 +6,11 @@ package git import ( "context" "regexp" + "slices" "strings" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/timeutil" "github.com/gobwas/glob" @@ -76,7 +76,7 @@ func DeleteProtectedTag(ctx context.Context, pt *ProtectedTag) error { // IsUserAllowedModifyTag returns true if the user is allowed to modify the tag func IsUserAllowedModifyTag(ctx context.Context, pt *ProtectedTag, userID int64) (bool, error) { - if base.Int64sContains(pt.AllowlistUserIDs, userID) { + if slices.Contains(pt.AllowlistUserIDs, userID) { return true, nil } diff --git a/models/issues/review.go b/models/issues/review.go index e06670c9a10a4..4cbfa4f44357e 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -6,6 +6,7 @@ package issues import ( "context" "fmt" + "slices" "strings" "code.gitea.io/gitea/models/db" @@ -15,7 +16,6 @@ import ( access_model "code.gitea.io/gitea/models/perm/access" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -279,7 +279,7 @@ func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio return team.UnitAccessMode(ctx, unit.TypeCode) >= perm.AccessModeWrite, nil } - return base.Int64sContains(pb.ApprovalsWhitelistTeamIDs, team.ID), nil + return slices.Contains(pb.ApprovalsWhitelistTeamIDs, team.ID), nil } // CreateReview creates a new review based on opts diff --git a/modules/base/tool.go b/modules/base/tool.go index 71dcb83fb4850..e9f4dfa279475 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -4,7 +4,6 @@ package base import ( - "crypto/md5" "crypto/sha1" "encoding/base64" "encoding/hex" @@ -16,7 +15,6 @@ import ( "strconv" "strings" "time" - "unicode" "unicode/utf8" "code.gitea.io/gitea/modules/git" @@ -27,13 +25,6 @@ import ( "github.com/minio/sha256-simd" ) -// EncodeMD5 encodes string to md5 hex value. -func EncodeMD5(str string) string { - m := md5.New() - _, _ = m.Write([]byte(str)) - return hex.EncodeToString(m.Sum(nil)) -} - // EncodeSha1 string to sha1 hex value. func EncodeSha1(str string) string { h := sha1.New() @@ -70,11 +61,6 @@ func BasicAuthDecode(encoded string) (string, string, error) { return auth[0], auth[1], nil } -// BasicAuthEncode encode basic auth string -func BasicAuthEncode(username, password string) string { - return base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) -} - // VerifyTimeLimitCode verify time limit code func VerifyTimeLimitCode(data string, minutes int, code string) bool { if len(code) <= 18 { @@ -184,22 +170,6 @@ func Int64sToStrings(ints []int64) []string { return strs } -// Int64sContains returns if a int64 in a slice of int64 -func Int64sContains(intsSlice []int64, a int64) bool { - for _, c := range intsSlice { - if c == a { - return true - } - } - return false -} - -// IsLetter reports whether the rune is a letter (category L). -// https://github.com/golang/go/blob/c3b4918/src/go/scanner/scanner.go#L342 -func IsLetter(ch rune) bool { - return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch) -} - // EntryIcon returns the octicon class for displaying files/directories func EntryIcon(entry *git.TreeEntry) string { switch { diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go index 0c3e76704ea75..d28deb593d419 100644 --- a/modules/base/tool_test.go +++ b/modules/base/tool_test.go @@ -11,13 +11,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestEncodeMD5(t *testing.T) { - assert.Equal(t, - "3858f62230ac3c915f300c664312c63f", - EncodeMD5("foobar"), - ) -} - func TestEncodeSha1(t *testing.T) { assert.Equal(t, "8843d7f92416211de9ebb963ff4ce28125932878", @@ -52,11 +45,6 @@ func TestBasicAuthDecode(t *testing.T) { assert.Error(t, err) } -func TestBasicAuthEncode(t *testing.T) { - assert.Equal(t, "Zm9vOmJhcg==", BasicAuthEncode("foo", "bar")) - assert.Equal(t, "MjM6IjotLS0t", BasicAuthEncode("23:\"", "----")) -} - func TestVerifyTimeLimitCode(t *testing.T) { tc := []struct { data string @@ -167,29 +155,6 @@ func TestInt64sToStrings(t *testing.T) { ) } -func TestInt64sContains(t *testing.T) { - assert.True(t, Int64sContains([]int64{6, 44324, 4324, 32, 1, 2323}, 1)) - assert.True(t, Int64sContains([]int64{2323}, 2323)) - assert.False(t, Int64sContains([]int64{6, 44324, 4324, 32, 1, 2323}, 232)) -} - -func TestIsLetter(t *testing.T) { - assert.True(t, IsLetter('a')) - assert.True(t, IsLetter('e')) - assert.True(t, IsLetter('q')) - assert.True(t, IsLetter('z')) - assert.True(t, IsLetter('A')) - assert.True(t, IsLetter('E')) - assert.True(t, IsLetter('Q')) - assert.True(t, IsLetter('Z')) - assert.True(t, IsLetter('_')) - assert.False(t, IsLetter('-')) - assert.False(t, IsLetter('1')) - assert.False(t, IsLetter('$')) - assert.False(t, IsLetter(0x00)) - assert.False(t, IsLetter(0x93)) -} - // TODO: Test EntryIcon func TestSetupGiteaRoot(t *testing.T) { diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index d8331fef43f5a..00614565d2035 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -14,6 +14,7 @@ import ( "path/filepath" "strings" + "code.gitea.io/gitea/models/avatars" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" @@ -130,7 +131,7 @@ func UpdateAvatarSetting(ctx *context.Context, form *forms.AvatarForm, ctxUser * ctxUser.UseCustomAvatar = form.Source == forms.AvatarLocal if len(form.Gravatar) > 0 { if form.Avatar != nil { - ctxUser.Avatar = base.EncodeMD5(form.Gravatar) + ctxUser.Avatar = avatars.HashEmail(form.Gravatar) } else { ctxUser.Avatar = "" } From e743570f65b533552337d9606ac1d906ec054127 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 28 Dec 2023 18:09:57 +0800 Subject: [PATCH 08/11] Refactor timeutil package (#28623) 1. make names more readable 2. remove unused FormatLong/FormatShort 3. use `FormatDate` instead of `Format "2006-01-02"` --- models/activities/user_heatmap_test.go | 4 +-- models/asymkey/gpg_key_verify.go | 5 +-- models/issues/comment.go | 6 ++-- models/issues/milestone.go | 2 +- modules/timeutil/timestamp.go | 34 +++++++------------ services/auth/auth_token_test.go | 10 +++--- .../repo/issue/view_content/sidebar.tmpl | 2 +- templates/shared/issuelist.tmpl | 2 +- tests/integration/api_user_heatmap_test.go | 4 +-- 9 files changed, 30 insertions(+), 39 deletions(-) diff --git a/models/activities/user_heatmap_test.go b/models/activities/user_heatmap_test.go index 657f0f043cf51..b7babcbde1d92 100644 --- a/models/activities/user_heatmap_test.go +++ b/models/activities/user_heatmap_test.go @@ -59,8 +59,8 @@ func TestGetUserHeatmapDataByUser(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) // Mock time - timeutil.Set(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)) - defer timeutil.Unset() + timeutil.MockSet(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)) + defer timeutil.MockUnset() for _, tc := range testCases { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: tc.userID}) diff --git a/models/asymkey/gpg_key_verify.go b/models/asymkey/gpg_key_verify.go index be36482c745ca..98a3e7d390cfa 100644 --- a/models/asymkey/gpg_key_verify.go +++ b/models/asymkey/gpg_key_verify.go @@ -107,8 +107,9 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st // VerificationToken returns token for the user that will be valid in minutes (time) func VerificationToken(user *user_model.User, minutes int) string { return base.EncodeSha256( - time.Now().Truncate(1*time.Minute).Add(time.Duration(minutes)*time.Minute).Format(time.RFC1123Z) + ":" + - user.CreatedUnix.FormatLong() + ":" + + time.Now().Truncate(1*time.Minute).Add(time.Duration(minutes)*time.Minute).Format( + time.RFC1123Z) + ":" + + user.CreatedUnix.Format(time.RFC1123Z) + ":" + user.Name + ":" + user.Email + ":" + strconv.FormatInt(user.ID, 10)) diff --git a/models/issues/comment.go b/models/issues/comment.go index ba5aed9c652e9..d92e49a444d74 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -899,15 +899,15 @@ func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Is // newDeadline = 0 means deleting if newDeadlineUnix == 0 { commentType = CommentTypeRemovedDeadline - content = issue.DeadlineUnix.Format("2006-01-02") + content = issue.DeadlineUnix.FormatDate() } else if issue.DeadlineUnix == 0 { // Check if the new date was added or modified // If the actual deadline is 0 => deadline added commentType = CommentTypeAddedDeadline - content = newDeadlineUnix.Format("2006-01-02") + content = newDeadlineUnix.FormatDate() } else { // Otherwise modified commentType = CommentTypeModifiedDeadline - content = newDeadlineUnix.Format("2006-01-02") + "|" + issue.DeadlineUnix.Format("2006-01-02") + content = newDeadlineUnix.FormatDate() + "|" + issue.DeadlineUnix.FormatDate() } if err := issue.LoadRepo(ctx); err != nil { diff --git a/models/issues/milestone.go b/models/issues/milestone.go index eb42df8263ba9..f663d42fe92bc 100644 --- a/models/issues/milestone.go +++ b/models/issues/milestone.go @@ -86,7 +86,7 @@ func (m *Milestone) AfterLoad() { return } - m.DeadlineString = m.DeadlineUnix.Format("2006-01-02") + m.DeadlineString = m.DeadlineUnix.FormatDate() if m.IsClosed { m.IsOverdue = m.ClosedDateUnix >= m.DeadlineUnix } else { diff --git a/modules/timeutil/timestamp.go b/modules/timeutil/timestamp.go index c60d287faee13..27a80b668238b 100644 --- a/modules/timeutil/timestamp.go +++ b/modules/timeutil/timestamp.go @@ -13,27 +13,27 @@ import ( type TimeStamp int64 var ( - // mock is NOT concurrency-safe!! - mock time.Time + // mockNow is NOT concurrency-safe!! + mockNow time.Time // Used for IsZero, to check if timestamp is the zero time instant. timeZeroUnix = time.Time{}.Unix() ) -// Set sets the time to a mocked time.Time -func Set(now time.Time) { - mock = now +// MockSet sets the time to a mocked time.Time +func MockSet(now time.Time) { + mockNow = now } -// Unset will unset the mocked time.Time -func Unset() { - mock = time.Time{} +// MockUnset will unset the mocked time.Time +func MockUnset() { + mockNow = time.Time{} } // TimeStampNow returns now int64 func TimeStampNow() TimeStamp { - if !mock.IsZero() { - return TimeStamp(mock.Unix()) + if !mockNow.IsZero() { + return TimeStamp(mockNow.Unix()) } return TimeStamp(time.Now().Unix()) } @@ -89,19 +89,9 @@ func (ts TimeStamp) FormatInLocation(f string, loc *time.Location) string { return ts.AsTimeInLocation(loc).Format(f) } -// FormatLong formats as RFC1123Z -func (ts TimeStamp) FormatLong() string { - return ts.Format(time.RFC1123Z) -} - -// FormatShort formats as short -func (ts TimeStamp) FormatShort() string { - return ts.Format("Jan 02, 2006") -} - -// FormatDate formats a date in YYYY-MM-DD server time zone +// FormatDate formats a date in YYYY-MM-DD func (ts TimeStamp) FormatDate() string { - return time.Unix(int64(ts), 0).String()[:10] + return ts.Format("2006-01-02") } // IsZero is zero time diff --git a/services/auth/auth_token_test.go b/services/auth/auth_token_test.go index 654275df17327..23c8d17e59710 100644 --- a/services/auth/auth_token_test.go +++ b/services/auth/auth_token_test.go @@ -37,14 +37,14 @@ func TestCheckAuthToken(t *testing.T) { }) t.Run("Expired", func(t *testing.T) { - timeutil.Set(time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)) + timeutil.MockSet(time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)) at, token, err := CreateAuthTokenForUserID(db.DefaultContext, 2) assert.NoError(t, err) assert.NotNil(t, at) assert.NotEmpty(t, token) - timeutil.Unset() + timeutil.MockUnset() at2, err := CheckAuthToken(db.DefaultContext, at.ID+":"+token) assert.ErrorIs(t, err, ErrAuthTokenExpired) @@ -83,15 +83,15 @@ func TestCheckAuthToken(t *testing.T) { func TestRegenerateAuthToken(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - timeutil.Set(time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)) - defer timeutil.Unset() + timeutil.MockSet(time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)) + defer timeutil.MockUnset() at, token, err := CreateAuthTokenForUserID(db.DefaultContext, 2) assert.NoError(t, err) assert.NotNil(t, at) assert.NotEmpty(t, token) - timeutil.Set(time.Date(2023, 1, 1, 0, 0, 1, 0, time.UTC)) + timeutil.MockSet(time.Date(2023, 1, 1, 0, 0, 1, 0, time.UTC)) at2, token2, err := RegenerateAuthToken(db.DefaultContext, at) assert.NoError(t, err) diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index c81cc5c10a641..4334e4bcbdc21 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -392,7 +392,7 @@
{{$.CsrfTokenHtml}} - +