loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[gitea] week 2025-13 cherry pick (gitea/main -> forgejo) (#7397)

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7397
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>

Gusted 979cc5cd c9c8fb65

+182 -143
+3
.editorconfig
··· 12 12 [{*.{go,tmpl,html},Makefile,go.mod}] 13 13 indent_style = tab 14 14 15 + [go.*] 16 + indent_style = tab 17 + 15 18 [templates/custom/*.tmpl] 16 19 insert_final_newline = false 17 20
+1 -1
Makefile
··· 523 523 524 524 .PHONY: security-check 525 525 security-check: 526 - go run $(GOVULNCHECK_PACKAGE) ./... 526 + go run $(GOVULNCHECK_PACKAGE) -show color ./... 527 527 528 528 ### 529 529 # Development and testing targets
+2 -1
modules/indexer/code/bleve/bleve.go
··· 179 179 return err 180 180 } else if !typesniffer.DetectContentType(fileContents).IsText() { 181 181 // FIXME: UTF-16 files will probably fail here 182 - return nil 182 + // Even if the file is not recognized as a "text file", we could still put its name into the indexers to make the filename become searchable, while leave the content to empty. 183 + fileContents = nil 183 184 } 184 185 185 186 if _, err = batchReader.Discard(1); err != nil {
+11
modules/optional/option.go
··· 3 3 4 4 package optional 5 5 6 + import "strconv" 7 + 6 8 type Option[T any] []T 7 9 8 10 func None[T any]() Option[T] { ··· 43 45 } 44 46 return v 45 47 } 48 + 49 + // ParseBool get the corresponding optional.Option[bool] of a string using strconv.ParseBool 50 + func ParseBool(s string) Option[bool] { 51 + v, e := strconv.ParseBool(s) 52 + if e != nil { 53 + return None[bool]() 54 + } 55 + return Some(v) 56 + }
+13
modules/optional/option_test.go
··· 57 57 assert.True(t, opt3.Has()) 58 58 assert.Equal(t, int(1), opt3.Value()) 59 59 } 60 + 61 + func Test_ParseBool(t *testing.T) { 62 + assert.Equal(t, optional.None[bool](), optional.ParseBool("")) 63 + assert.Equal(t, optional.None[bool](), optional.ParseBool("x")) 64 + 65 + assert.Equal(t, optional.Some(false), optional.ParseBool("0")) 66 + assert.Equal(t, optional.Some(false), optional.ParseBool("f")) 67 + assert.Equal(t, optional.Some(false), optional.ParseBool("False")) 68 + 69 + assert.Equal(t, optional.Some(true), optional.ParseBool("1")) 70 + assert.Equal(t, optional.Some(true), optional.ParseBool("t")) 71 + assert.Equal(t, optional.Some(true), optional.ParseBool("True")) 72 + }
+2 -2
modules/setting/setting.go
··· 12 12 "time" 13 13 14 14 "forgejo.org/modules/log" 15 + "forgejo.org/modules/optional" 15 16 "forgejo.org/modules/user" 16 - "forgejo.org/modules/util" 17 17 ) 18 18 19 19 var ForgejoVersion = "1.0.0" ··· 162 162 // The following is a purposefully undocumented option. Please do not run Forgejo as root. It will only cause future headaches. 163 163 // Please don't use root as a bandaid to "fix" something that is broken, instead the broken thing should instead be fixed properly. 164 164 unsafeAllowRunAsRoot := ConfigSectionKeyBool(rootSec, "I_AM_BEING_UNSAFE_RUNNING_AS_ROOT") 165 - unsafeAllowRunAsRoot = unsafeAllowRunAsRoot || util.OptionalBoolParse(os.Getenv("GITEA_I_AM_BEING_UNSAFE_RUNNING_AS_ROOT")).Value() 165 + unsafeAllowRunAsRoot = unsafeAllowRunAsRoot || optional.ParseBool(os.Getenv("GITEA_I_AM_BEING_UNSAFE_RUNNING_AS_ROOT")).Value() 166 166 RunMode = os.Getenv("GITEA_RUN_MODE") 167 167 if RunMode == "" { 168 168 RunMode = rootSec.Key("RUN_MODE").MustString("prod")
-11
modules/util/util.go
··· 14 14 "strconv" 15 15 "strings" 16 16 17 - "forgejo.org/modules/optional" 18 - 19 17 "golang.org/x/crypto/ssh" 20 18 "golang.org/x/text/cases" 21 19 "golang.org/x/text/language" 22 20 ) 23 - 24 - // OptionalBoolParse get the corresponding optional.Option[bool] of a string using strconv.ParseBool 25 - func OptionalBoolParse(s string) optional.Option[bool] { 26 - v, e := strconv.ParseBool(s) 27 - if e != nil { 28 - return optional.None[bool]() 29 - } 30 - return optional.Some(v) 31 - } 32 21 33 22 // IsEmptyString checks if the provided string is empty 34 23 func IsEmptyString(s string) bool {
-14
modules/util/util_test.go
··· 11 11 "strings" 12 12 "testing" 13 13 14 - "forgejo.org/modules/optional" 15 14 "forgejo.org/modules/test" 16 15 "forgejo.org/modules/util" 17 16 ··· 179 178 require.NoError(t, err) 180 179 181 180 assert.NotEqual(t, bytes3, bytes4) 182 - } 183 - 184 - func TestOptionalBoolParse(t *testing.T) { 185 - assert.Equal(t, optional.None[bool](), util.OptionalBoolParse("")) 186 - assert.Equal(t, optional.None[bool](), util.OptionalBoolParse("x")) 187 - 188 - assert.Equal(t, optional.Some(false), util.OptionalBoolParse("0")) 189 - assert.Equal(t, optional.Some(false), util.OptionalBoolParse("f")) 190 - assert.Equal(t, optional.Some(false), util.OptionalBoolParse("False")) 191 - 192 - assert.Equal(t, optional.Some(true), util.OptionalBoolParse("1")) 193 - assert.Equal(t, optional.Some(true), util.OptionalBoolParse("t")) 194 - assert.Equal(t, optional.Some(true), util.OptionalBoolParse("True")) 195 181 } 196 182 197 183 // Test case for any function which accepts and returns a single string.
+5 -6
routers/web/admin/users.go
··· 22 22 "forgejo.org/modules/log" 23 23 "forgejo.org/modules/optional" 24 24 "forgejo.org/modules/setting" 25 - "forgejo.org/modules/util" 26 25 "forgejo.org/modules/validation" 27 26 "forgejo.org/modules/web" 28 27 "forgejo.org/routers/web/explore" ··· 77 76 PageSize: setting.UI.Admin.UserPagingNum, 78 77 }, 79 78 SearchByEmail: true, 80 - IsActive: util.OptionalBoolParse(statusFilterMap["is_active"]), 81 - IsAdmin: util.OptionalBoolParse(statusFilterMap["is_admin"]), 82 - IsRestricted: util.OptionalBoolParse(statusFilterMap["is_restricted"]), 83 - IsTwoFactorEnabled: util.OptionalBoolParse(statusFilterMap["is_2fa_enabled"]), 84 - IsProhibitLogin: util.OptionalBoolParse(statusFilterMap["is_prohibit_login"]), 79 + IsActive: optional.ParseBool(statusFilterMap["is_active"]), 80 + IsAdmin: optional.ParseBool(statusFilterMap["is_admin"]), 81 + IsRestricted: optional.ParseBool(statusFilterMap["is_restricted"]), 82 + IsTwoFactorEnabled: optional.ParseBool(statusFilterMap["is_2fa_enabled"]), 83 + IsProhibitLogin: optional.ParseBool(statusFilterMap["is_prohibit_login"]), 85 84 IncludeReserved: true, // administrator needs to list all accounts include reserved, bot, remote ones 86 85 Load2FAStatus: true, 87 86 ExtraParamStrings: extraParamStrings,
+1 -1
routers/web/repo/code_frequency.go
··· 34 34 ctx.Status(http.StatusAccepted) 35 35 return 36 36 } 37 - ctx.ServerError("GetCodeFrequencyData", err) 37 + ctx.ServerError("GetContributorStats", err) 38 38 } else { 39 39 ctx.JSON(http.StatusOK, contributorStats["total"].Weeks) 40 40 }
-15
routers/web/repo/recent_commits.go
··· 4 4 package repo 5 5 6 6 import ( 7 - "errors" 8 7 "net/http" 9 8 10 9 "forgejo.org/modules/base" 11 10 "forgejo.org/services/context" 12 - contributors_service "forgejo.org/services/repository" 13 11 ) 14 12 15 13 const ( ··· 26 24 27 25 ctx.HTML(http.StatusOK, tplRecentCommits) 28 26 } 29 - 30 - // RecentCommitsData returns JSON of recent commits data 31 - func RecentCommitsData(ctx *context.Context) { 32 - if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.CommitID); err != nil { 33 - if errors.Is(err, contributors_service.ErrAwaitGeneration) { 34 - ctx.Status(http.StatusAccepted) 35 - return 36 - } 37 - ctx.ServerError("RecentCommitsData", err) 38 - } else { 39 - ctx.JSON(http.StatusOK, contributorStats["total"].Weeks) 40 - } 41 - }
+1 -1
routers/web/web.go
··· 1455 1455 }, repo.MustBeNotEmpty, context.RequireRepoReaderOr(unit.TypeCode)) 1456 1456 m.Group("/recent-commits", func() { 1457 1457 m.Get("", repo.RecentCommits) 1458 - m.Get("/data", repo.RecentCommitsData) 1458 + m.Get("/data", repo.CodeFrequencyData) 1459 1459 }, repo.MustBeNotEmpty, context.RequireRepoReaderOr(unit.TypeCode)) 1460 1460 }, context.RepoRef(), context.RequireRepoReaderOr(unit.TypeCode, unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases)) 1461 1461
+3 -1
services/context/repo.go
··· 361 361 if ctx.Req.URL.RawQuery != "" { 362 362 redirectPath += "?" + ctx.Req.URL.RawQuery 363 363 } 364 - ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect) 364 + // Git client needs a 301 redirect by default to follow the new location 365 + // It's not documentated in git documentation, but it's the behavior of git client 366 + ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusMovedPermanently) 365 367 } 366 368 367 369 func repoAssignment(ctx *Context, repo *repo_model.Repository) {
+29 -11
services/mirror/mirror_pull.go
··· 244 244 return pruneErr 245 245 } 246 246 247 + // checkRecoverableSyncError takes an error message from a git fetch command and returns false if it should be a fatal/blocking error 248 + func checkRecoverableSyncError(stderrMessage string) bool { 249 + switch { 250 + case strings.Contains(stderrMessage, "unable to resolve reference") && strings.Contains(stderrMessage, "reference broken"): 251 + return true 252 + case strings.Contains(stderrMessage, "remote error") && strings.Contains(stderrMessage, "not our ref"): 253 + return true 254 + case strings.Contains(stderrMessage, "cannot lock ref") && strings.Contains(stderrMessage, "but expected"): 255 + return true 256 + case strings.Contains(stderrMessage, "cannot lock ref") && strings.Contains(stderrMessage, "unable to resolve reference"): 257 + return true 258 + case strings.Contains(stderrMessage, "Unable to create") && strings.Contains(stderrMessage, ".lock"): 259 + return true 260 + default: 261 + return false 262 + } 263 + } 264 + 247 265 // runSync returns true if sync finished without error. 248 266 func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bool) { 249 267 repoPath := m.Repo.RepoPath() ··· 286 304 stdoutMessage := util.SanitizeCredentialURLs(stdout) 287 305 288 306 // Now check if the error is a resolve reference due to broken reference 289 - if strings.Contains(stderr, "unable to resolve reference") && strings.Contains(stderr, "reference broken") { 307 + if checkRecoverableSyncError(stderr) { 290 308 log.Warn("SyncMirrors [repo: %-v]: failed to update mirror repository due to broken references:\nStdout: %s\nStderr: %s\nErr: %v\nAttempting Prune", m.Repo, stdoutMessage, stderrMessage, err) 291 309 err = nil 292 310 ··· 337 355 return nil, false 338 356 } 339 357 358 + if m.LFS && setting.LFS.StartServer { 359 + log.Trace("SyncMirrors [repo: %-v]: syncing LFS objects...", m.Repo) 360 + endpoint := lfs.DetermineEndpoint(remoteURL.String(), m.LFSEndpoint) 361 + lfsClient := lfs.NewClient(endpoint, nil) 362 + if err = repo_module.StoreMissingLfsObjectsInRepository(ctx, m.Repo, gitRepo, lfsClient); err != nil { 363 + log.Error("SyncMirrors [repo: %-v]: failed to synchronize LFS objects for repository: %v", m.Repo, err) 364 + } 365 + } 366 + 340 367 log.Trace("SyncMirrors [repo: %-v]: syncing branches...", m.Repo) 341 368 if _, err = repo_module.SyncRepoBranchesWithRepo(ctx, m.Repo, gitRepo, 0); err != nil { 342 369 log.Error("SyncMirrors [repo: %-v]: failed to synchronize branches: %v", m.Repo, err) ··· 345 372 log.Trace("SyncMirrors [repo: %-v]: syncing releases with tags...", m.Repo) 346 373 if err = repo_module.SyncReleasesWithTags(ctx, m.Repo, gitRepo); err != nil { 347 374 log.Error("SyncMirrors [repo: %-v]: failed to synchronize tags to releases: %v", m.Repo, err) 348 - } 349 - 350 - if m.LFS && setting.LFS.StartServer { 351 - log.Trace("SyncMirrors [repo: %-v]: syncing LFS objects...", m.Repo) 352 - endpoint := lfs.DetermineEndpoint(remoteURL.String(), m.LFSEndpoint) 353 - lfsClient := lfs.NewClient(endpoint, nil) 354 - if err = repo_module.StoreMissingLfsObjectsInRepository(ctx, m.Repo, gitRepo, lfsClient); err != nil { 355 - log.Error("SyncMirrors [repo: %-v]: failed to synchronize LFS objects for repository: %v", m.Repo, err) 356 - } 357 375 } 358 376 gitRepo.Close() 359 377 ··· 382 400 stdoutMessage := util.SanitizeCredentialURLs(stdout) 383 401 384 402 // Now check if the error is a resolve reference due to broken reference 385 - if strings.Contains(stderrMessage, "unable to resolve reference") && strings.Contains(stderrMessage, "reference broken") { 403 + if checkRecoverableSyncError(stderrMessage) { 386 404 log.Warn("SyncMirrors [repo: %-v Wiki]: failed to update mirror wiki repository due to broken references:\nStdout: %s\nStderr: %s\nErr: %v\nAttempting Prune", m.Repo, stdoutMessage, stderrMessage, err) 387 405 err = nil 388 406
+94
services/mirror/mirror_pull_test.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package mirror 5 + 6 + import ( 7 + "testing" 8 + 9 + "github.com/stretchr/testify/assert" 10 + ) 11 + 12 + func Test_parseRemoteUpdateOutput(t *testing.T) { 13 + output := ` 14 + * [new tag] v0.1.8 -> v0.1.8 15 + * [new branch] master -> origin/master 16 + - [deleted] (none) -> origin/test1 17 + - [deleted] (none) -> tag1 18 + + f895a1e...957a993 test2 -> origin/test2 (forced update) 19 + 957a993..a87ba5f test3 -> origin/test3 20 + * [new ref] refs/pull/26595/head -> refs/pull/26595/head 21 + * [new ref] refs/pull/26595/merge -> refs/pull/26595/merge 22 + e0639e38fb..6db2410489 refs/pull/25873/head -> refs/pull/25873/head 23 + + 1c97ebc746...976d27d52f refs/pull/25873/merge -> refs/pull/25873/merge (forced update) 24 + ` 25 + results := parseRemoteUpdateOutput(output, "origin") 26 + assert.Len(t, results, 10) 27 + assert.Equal(t, "refs/tags/v0.1.8", results[0].refName.String()) 28 + assert.Equal(t, gitShortEmptySha, results[0].oldCommitID) 29 + assert.Empty(t, results[0].newCommitID) 30 + 31 + assert.Equal(t, "refs/heads/master", results[1].refName.String()) 32 + assert.Equal(t, gitShortEmptySha, results[1].oldCommitID) 33 + assert.Empty(t, results[1].newCommitID) 34 + 35 + assert.Equal(t, "refs/heads/test1", results[2].refName.String()) 36 + assert.Empty(t, results[2].oldCommitID) 37 + assert.Equal(t, gitShortEmptySha, results[2].newCommitID) 38 + 39 + assert.Equal(t, "refs/tags/tag1", results[3].refName.String()) 40 + assert.Empty(t, results[3].oldCommitID) 41 + assert.Equal(t, gitShortEmptySha, results[3].newCommitID) 42 + 43 + assert.Equal(t, "refs/heads/test2", results[4].refName.String()) 44 + assert.Equal(t, "f895a1e", results[4].oldCommitID) 45 + assert.Equal(t, "957a993", results[4].newCommitID) 46 + 47 + assert.Equal(t, "refs/heads/test3", results[5].refName.String()) 48 + assert.Equal(t, "957a993", results[5].oldCommitID) 49 + assert.Equal(t, "a87ba5f", results[5].newCommitID) 50 + 51 + assert.Equal(t, "refs/pull/26595/head", results[6].refName.String()) 52 + assert.Equal(t, gitShortEmptySha, results[6].oldCommitID) 53 + assert.Empty(t, results[6].newCommitID) 54 + 55 + assert.Equal(t, "refs/pull/26595/merge", results[7].refName.String()) 56 + assert.Equal(t, gitShortEmptySha, results[7].oldCommitID) 57 + assert.Empty(t, results[7].newCommitID) 58 + 59 + assert.Equal(t, "refs/pull/25873/head", results[8].refName.String()) 60 + assert.Equal(t, "e0639e38fb", results[8].oldCommitID) 61 + assert.Equal(t, "6db2410489", results[8].newCommitID) 62 + 63 + assert.Equal(t, "refs/pull/25873/merge", results[9].refName.String()) 64 + assert.Equal(t, "1c97ebc746", results[9].oldCommitID) 65 + assert.Equal(t, "976d27d52f", results[9].newCommitID) 66 + } 67 + 68 + func Test_checkRecoverableSyncError(t *testing.T) { 69 + cases := []struct { 70 + recoverable bool 71 + message string 72 + }{ 73 + // A race condition in http git-fetch where certain refs were listed on the remote and are no longer there, would exit status 128 74 + {true, "fatal: remote error: upload-pack: not our ref 988881adc9fc3655077dc2d4d757d480b5ea0e11"}, 75 + // A race condition where a local gc/prune removes a named ref during a git-fetch would exit status 1 76 + {true, "cannot lock ref 'refs/pull/123456/merge': unable to resolve reference 'refs/pull/134153/merge'"}, 77 + // A race condition in http git-fetch where named refs were listed on the remote and are no longer there 78 + {true, "error: cannot lock ref 'refs/remotes/origin/foo': unable to resolve reference 'refs/remotes/origin/foo': reference broken"}, 79 + // A race condition in http git-fetch where named refs were force-pushed during the update, would exit status 128 80 + {true, "error: cannot lock ref 'refs/pull/123456/merge': is at 988881adc9fc3655077dc2d4d757d480b5ea0e11 but expected 7f894307ffc9553edbd0b671cab829786866f7b2"}, 81 + // A race condition with other local git operations, such as git-maintenance, would exit status 128 (well, "Unable" the "U" is uppercase) 82 + {true, "fatal: Unable to create '/data/gitea-repositories/foo-org/bar-repo.git/./objects/info/commit-graphs/commit-graph-chain.lock': File exists."}, 83 + // Missing or unauthorized credentials, would exit status 128 84 + {false, "fatal: Authentication failed for 'https://example.com/foo-does-not-exist/bar.git/'"}, 85 + // A non-existent remote repository, would exit status 128 86 + {false, "fatal: Could not read from remote repository."}, 87 + // A non-functioning proxy, would exit status 128 88 + {false, "fatal: unable to access 'https://example.com/foo-does-not-exist/bar.git/': Failed to connect to configured-https-proxy port 1080 after 0 ms: Couldn't connect to server"}, 89 + } 90 + 91 + for _, c := range cases { 92 + assert.Equal(t, c.recoverable, checkRecoverableSyncError(c.message), "test case: %s", c.message) 93 + } 94 + }
-66
services/mirror/mirror_test.go
··· 1 - // Copyright 2023 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package mirror 5 - 6 - import ( 7 - "testing" 8 - 9 - "github.com/stretchr/testify/assert" 10 - ) 11 - 12 - func Test_parseRemoteUpdateOutput(t *testing.T) { 13 - output := ` 14 - * [new tag] v0.1.8 -> v0.1.8 15 - * [new branch] master -> origin/master 16 - - [deleted] (none) -> origin/test1 17 - - [deleted] (none) -> tag1 18 - + f895a1e...957a993 test2 -> origin/test2 (forced update) 19 - 957a993..a87ba5f test3 -> origin/test3 20 - * [new ref] refs/pull/26595/head -> refs/pull/26595/head 21 - * [new ref] refs/pull/26595/merge -> refs/pull/26595/merge 22 - e0639e38fb..6db2410489 refs/pull/25873/head -> refs/pull/25873/head 23 - + 1c97ebc746...976d27d52f refs/pull/25873/merge -> refs/pull/25873/merge (forced update) 24 - ` 25 - results := parseRemoteUpdateOutput(output, "origin") 26 - assert.Len(t, results, 10) 27 - assert.Equal(t, "refs/tags/v0.1.8", results[0].refName.String()) 28 - assert.Equal(t, gitShortEmptySha, results[0].oldCommitID) 29 - assert.Empty(t, results[0].newCommitID) 30 - 31 - assert.Equal(t, "refs/heads/master", results[1].refName.String()) 32 - assert.Equal(t, gitShortEmptySha, results[1].oldCommitID) 33 - assert.Empty(t, results[1].newCommitID) 34 - 35 - assert.Equal(t, "refs/heads/test1", results[2].refName.String()) 36 - assert.Empty(t, results[2].oldCommitID) 37 - assert.Equal(t, gitShortEmptySha, results[2].newCommitID) 38 - 39 - assert.Equal(t, "refs/tags/tag1", results[3].refName.String()) 40 - assert.Empty(t, results[3].oldCommitID) 41 - assert.Equal(t, gitShortEmptySha, results[3].newCommitID) 42 - 43 - assert.Equal(t, "refs/heads/test2", results[4].refName.String()) 44 - assert.Equal(t, "f895a1e", results[4].oldCommitID) 45 - assert.Equal(t, "957a993", results[4].newCommitID) 46 - 47 - assert.Equal(t, "refs/heads/test3", results[5].refName.String()) 48 - assert.Equal(t, "957a993", results[5].oldCommitID) 49 - assert.Equal(t, "a87ba5f", results[5].newCommitID) 50 - 51 - assert.Equal(t, "refs/pull/26595/head", results[6].refName.String()) 52 - assert.Equal(t, gitShortEmptySha, results[6].oldCommitID) 53 - assert.Empty(t, results[6].newCommitID) 54 - 55 - assert.Equal(t, "refs/pull/26595/merge", results[7].refName.String()) 56 - assert.Equal(t, gitShortEmptySha, results[7].oldCommitID) 57 - assert.Empty(t, results[7].newCommitID) 58 - 59 - assert.Equal(t, "refs/pull/25873/head", results[8].refName.String()) 60 - assert.Equal(t, "e0639e38fb", results[8].oldCommitID) 61 - assert.Equal(t, "6db2410489", results[8].newCommitID) 62 - 63 - assert.Equal(t, "refs/pull/25873/merge", results[9].refName.String()) 64 - assert.Equal(t, "1c97ebc746", results[9].oldCommitID) 65 - assert.Equal(t, "976d27d52f", results[9].newCommitID) 66 - }
+13 -9
templates/repo/navbar.tmpl
··· 1 + {{$canReadCode := $.Permission.CanRead $.UnitTypeCode}} 2 + 1 3 <div class="ui fluid vertical menu"> 2 4 <a class="{{if .PageIsPulse}}active {{end}}item" href="{{.RepoLink}}/activity"> 3 5 {{ctx.Locale.Tr "repo.activity.navbar.pulse"}} 4 6 </a> 5 - <a class="{{if .PageIsContributors}}active {{end}}item" href="{{.RepoLink}}/activity/contributors"> 6 - {{ctx.Locale.Tr "repo.activity.navbar.contributors"}} 7 - </a> 8 - <a class="{{if .PageIsCodeFrequency}}active{{end}} item" href="{{.RepoLink}}/activity/code-frequency"> 9 - {{ctx.Locale.Tr "repo.activity.navbar.code_frequency"}} 10 - </a> 11 - <a class="{{if .PageIsRecentCommits}}active{{end}} item" href="{{.RepoLink}}/activity/recent-commits"> 12 - {{ctx.Locale.Tr "repo.activity.navbar.recent_commits"}} 13 - </a> 7 + {{if $canReadCode}} 8 + <a class="{{if .PageIsContributors}}active {{end}}item" href="{{.RepoLink}}/activity/contributors"> 9 + {{ctx.Locale.Tr "repo.activity.navbar.contributors"}} 10 + </a> 11 + <a class="{{if .PageIsCodeFrequency}}active{{end}} item" href="{{.RepoLink}}/activity/code-frequency"> 12 + {{ctx.Locale.Tr "repo.activity.navbar.code_frequency"}} 13 + </a> 14 + <a class="{{if .PageIsRecentCommits}}active{{end}} item" href="{{.RepoLink}}/activity/recent-commits"> 15 + {{ctx.Locale.Tr "repo.activity.navbar.recent_commits"}} 16 + </a> 17 + {{end}} 14 18 </div>
+1 -1
templates/shared/actions/runner_edit.tmpl
··· 40 40 41 41 <div class="field"> 42 42 <button class="ui primary button" data-url="{{.Link}}">{{ctx.Locale.Tr "actions.runners.update_runner"}}</button> 43 - <button class="ui red button delete-button show-modal" data-url="{{.Link}}/delete" data-modal="#runner-delete-modal"> 43 + <button class="ui red button delete-button" data-url="{{.Link}}/delete" data-modal="#runner-delete-modal"> 44 44 {{ctx.Locale.Tr "actions.runners.delete_runner"}}</button> 45 45 </div> 46 46 </form>
+3 -3
templates/user/auth/captcha.tmpl
··· 11 11 <div class="inline field tw-text-center required"> 12 12 <div id="captcha" data-captcha-type="g-recaptcha" class="g-recaptcha-style" data-sitekey="{{.RecaptchaSitekey}}"></div> 13 13 </div> 14 - <script src='{{URLJoin .RecaptchaURL "api.js"}}'></script> 14 + <script defer src='{{URLJoin .RecaptchaURL "api.js"}}'></script> 15 15 {{else if eq .CaptchaType "hcaptcha"}} 16 16 <div class="inline field tw-text-center required"> 17 17 <div id="captcha" data-captcha-type="h-captcha" class="h-captcha-style" data-sitekey="{{.HcaptchaSitekey}}"></div> 18 18 </div> 19 - <script src='https://hcaptcha.com/1/api.js'></script> 19 + <script defer src='https://hcaptcha.com/1/api.js'></script> 20 20 {{else if eq .CaptchaType "mcaptcha"}} 21 21 <div class="inline field tw-text-center"> 22 22 <div class="m-captcha-style" id="mcaptcha__widget-container"></div> ··· 26 26 <div class="inline field tw-text-center"> 27 27 <div id="captcha" data-captcha-type="cf-turnstile" data-sitekey="{{.CfTurnstileSitekey}}"></div> 28 28 </div> 29 - <script src='https://challenges.cloudflare.com/turnstile/v0/api.js'></script> 29 + <script defer src='https://challenges.cloudflare.com/turnstile/v0/api.js'></script> 30 30 {{end}}{{end}}