From 1651ae757b31c31023d5e780a4446da5be8951bf Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Thu, 8 Jun 2023 13:50:38 +0200 Subject: [PATCH 01/30] [GITEA] silently ignore obsolete sudo scope Fixes: https://codeberg.org/forgejo/forgejo/issues/820 (cherry picked from commit 6a7022ebbb83bda162974028cff01ebcc7c574ec) (cherry picked from commit 764eac47b50688d76fe90aad4819a426444ddb4a) (cherry picked from commit 1141eb7b6f2deeeca0acf1714058823d32097cfd) (cherry picked from commit 826b6509b6405ac0a0731ee0e1477ad2cbac585a) (cherry picked from commit 9990d932b8b72f9a27b6529b350eb09d44b7ef88) (cherry picked from commit 7eca57074385f296427d06c059d331d3704ccf15) (cherry picked from commit 66e1d3f082a99bb0006daf0f337850f251c235dc) (cherry picked from commit 188226a8e6b2926f1f276462741f7cc4d7a050b0) (cherry picked from commit 4cd1bff25c6cafa33464594c99b39326a6dd5740) (cherry picked from commit fad6b6d2c49492297d9d8512afc0369e544a6e75) (cherry picked from commit 5b25c3d8512466fd5fceea86b550bdb35c3aa04b) (cherry picked from commit 4746ece4dd018af781181744fb8743e83b64c6df) (cherry picked from commit 2a6f85afb33a1a0b7424c30de3cdff030f483294) (cherry picked from commit c027d724ee0b694e48d2b7ee1915ba55222a03e0) (cherry picked from commit be2f1eeaeb92e552b5defcf8b374ceb4c3a6b1ee) (cherry picked from commit 3058a54fe99c7cf0a015166b8b3f56f9ef9e45d9) (cherry picked from commit 53936d38a0cb1649748f02cf86ec684fa76825b6) (cherry picked from commit 311983cc978cc0a3128cdd8a9c12ac9605be62b9) --- models/auth/token_scope.go | 2 +- models/auth/token_scope_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/models/auth/token_scope.go b/models/auth/token_scope.go index fe57276700..003ca5c9ab 100644 --- a/models/auth/token_scope.go +++ b/models/auth/token_scope.go @@ -250,7 +250,7 @@ func (s AccessTokenScope) parse() (accessTokenScopeBitmap, error) { remainingScopes = remainingScopes[i+1:] } singleScope := AccessTokenScope(v) - if singleScope == "" { + if singleScope == "" || singleScope == "sudo" { continue } if singleScope == AccessTokenScopeAll { diff --git a/models/auth/token_scope_test.go b/models/auth/token_scope_test.go index a6097e45d7..d11c5e6a3d 100644 --- a/models/auth/token_scope_test.go +++ b/models/auth/token_scope_test.go @@ -20,7 +20,7 @@ func TestAccessTokenScope_Normalize(t *testing.T) { tests := []scopeTestNormalize{ {"", "", nil}, {"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil}, - {"all", "all", nil}, + {"all,sudo", "all", nil}, {"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user", "all", nil}, {"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,public-only", "public-only,all", nil}, } From 92dfca0c5a8a8d4fd8a93b5468ba593283fc9452 Mon Sep 17 00:00:00 2001 From: "Panagiotis \"Ivory\" Vasilopoulos" Date: Mon, 12 Jun 2023 13:57:01 +0200 Subject: [PATCH 02/30] [GITEA] add option for banning dots in usernames Refs: https://codeberg.org/forgejo/forgejo/pulls/676 Author: Panagiotis "Ivory" Vasilopoulos Date: Mon Jun 12 13:57:01 2023 +0200 Co-authored-by: Gusted (cherry picked from commit fabdda5c6e84017bf75ab5f9ab6cc0e583b70d09) (cherry picked from commit d2c7f45621028d37944659db096bc92c031dd8e7) (cherry picked from commit dfdbaba3d6b7abf1c542b0ea41b7812b729cc217) (cherry picked from commit a3cda092b8897e4d669cfcf2cb8b16236e3c9b32) (cherry picked from commit f0fdb5905c3b22bec043530da15d2c52f6bc41c9) (cherry picked from commit 9697e48c1f8b23d3dd1da246b525b63c3756353d) (cherry picked from commit 46e31009a86db18a9b5bd8e2f535b198df90c437) (cherry picked from commit 5bb2c54b6f55499937396339bcacd3b4d8fb6b5e) (cherry picked from commit 682f9d24e13b83d89bd6b86324960f1b4fc72eeb) (cherry picked from commit 18634810057ef88fd01b54cec33bd4bd04c53221) (cherry picked from commit 4f1b7c4ddbc4099aa9b6fda1e0145d37f638e567) (cherry picked from commit 6afe70bbf1290e604fc476ee27901d1722ac1272) (cherry picked from commit 5cec1d9c2d2a731fa44f761e6c90f0d20ab3ccc4) Conflicts: templates/admin/config.tmpl https://codeberg.org/forgejo/forgejo/pulls/1512 (cherry picked from commit de2d172473217e3437238fd9c691edc8d8524e1a) (cherry picked from commit 37a3172dd9e2646157ec49ca46f94b9b0012b061) --- custom/conf/app.example.ini | 5 +++++ modules/setting/service.go | 2 ++ modules/validation/helpers.go | 13 ++++++++++--- modules/validation/helpers_test.go | 31 +++++++++++++++++++++++++++++- modules/web/middleware/binding.go | 7 ++++++- options/locale/locale_en-US.ini | 2 ++ templates/admin/config.tmpl | 2 ++ 7 files changed, 57 insertions(+), 5 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 09728ee1ad..ec49e42915 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -809,6 +809,11 @@ LEVEL = Info ;; Every new user will have restricted permissions depending on this setting ;DEFAULT_USER_IS_RESTRICTED = false ;; +;; Users will be able to use dots when choosing their username. Disabling this is +;; helpful if your usersare having issues with e.g. RSS feeds or advanced third-party +;; extensions that use strange regex patterns. +; ALLOW_DOTS_IN_USERNAMES = true +;; ;; Either "public", "limited" or "private", default is "public" ;; Limited is for users visible only to signed users ;; Private is for users visible only to members of their organizations diff --git a/modules/setting/service.go b/modules/setting/service.go index befb94b61b..afaee18101 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -68,6 +68,7 @@ var Service = struct { DefaultKeepEmailPrivate bool DefaultAllowCreateOrganization bool DefaultUserIsRestricted bool + AllowDotsInUsernames bool EnableTimetracking bool DefaultEnableTimetracking bool DefaultEnableDependencies bool @@ -180,6 +181,7 @@ func loadServiceFrom(rootCfg ConfigProvider) { Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool() Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true) Service.DefaultUserIsRestricted = sec.Key("DEFAULT_USER_IS_RESTRICTED").MustBool(false) + Service.AllowDotsInUsernames = sec.Key("ALLOW_DOTS_IN_USERNAMES").MustBool(true) Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true) if Service.EnableTimetracking { Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true) diff --git a/modules/validation/helpers.go b/modules/validation/helpers.go index f6e00f3887..567ad867fe 100644 --- a/modules/validation/helpers.go +++ b/modules/validation/helpers.go @@ -117,13 +117,20 @@ func IsValidExternalTrackerURLFormat(uri string) bool { } var ( - validUsernamePattern = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`) - invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`) // No consecutive or trailing non-alphanumeric chars + validUsernamePatternWithDots = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`) + validUsernamePatternWithoutDots = regexp.MustCompile(`^[\da-zA-Z][-\w]*$`) + + // No consecutive or trailing non-alphanumeric chars, catches both cases + invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`) ) // IsValidUsername checks if username is valid func IsValidUsername(name string) bool { // It is difficult to find a single pattern that is both readable and effective, // but it's easier to use positive and negative checks. - return validUsernamePattern.MatchString(name) && !invalidUsernamePattern.MatchString(name) + if setting.Service.AllowDotsInUsernames { + return validUsernamePatternWithDots.MatchString(name) && !invalidUsernamePattern.MatchString(name) + } + + return validUsernamePatternWithoutDots.MatchString(name) && !invalidUsernamePattern.MatchString(name) } diff --git a/modules/validation/helpers_test.go b/modules/validation/helpers_test.go index 52f383f698..a1bdf2a29c 100644 --- a/modules/validation/helpers_test.go +++ b/modules/validation/helpers_test.go @@ -155,7 +155,8 @@ func Test_IsValidExternalTrackerURLFormat(t *testing.T) { } } -func TestIsValidUsername(t *testing.T) { +func TestIsValidUsernameAllowDots(t *testing.T) { + setting.Service.AllowDotsInUsernames = true tests := []struct { arg string want bool @@ -185,3 +186,31 @@ func TestIsValidUsername(t *testing.T) { }) } } + +func TestIsValidUsernameBanDots(t *testing.T) { + setting.Service.AllowDotsInUsernames = false + defer func() { + setting.Service.AllowDotsInUsernames = true + }() + + tests := []struct { + arg string + want bool + }{ + {arg: "a", want: true}, + {arg: "abc", want: true}, + {arg: "0.b-c", want: false}, + {arg: "a.b-c_d", want: false}, + {arg: ".abc", want: false}, + {arg: "abc.", want: false}, + {arg: "a..bc", want: false}, + {arg: "a...bc", want: false}, + {arg: "a.-bc", want: false}, + {arg: "a._bc", want: false}, + } + for _, tt := range tests { + t.Run(tt.arg, func(t *testing.T) { + assert.Equalf(t, tt.want, IsValidUsername(tt.arg), "IsValidUsername[AllowDotsInUsernames=false](%v)", tt.arg) + }) + } +} diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index d9bcdf3b2a..4e7fca80e2 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -8,6 +8,7 @@ import ( "reflect" "strings" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/validation" @@ -135,7 +136,11 @@ func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Lo case validation.ErrRegexPattern: data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message) case validation.ErrUsername: - data["ErrorMsg"] = trName + l.Tr("form.username_error") + if setting.Service.AllowDotsInUsernames { + data["ErrorMsg"] = trName + l.Tr("form.username_error") + } else { + data["ErrorMsg"] = trName + l.Tr("form.username_error_no_dots") + } case validation.ErrInvalidGroupTeamMap: data["ErrorMsg"] = trName + l.Tr("form.invalid_group_team_map_error", errs[0].Message) default: diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 3f48ca5589..1071a61988 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -295,6 +295,7 @@ default_allow_create_organization = Allow Creation of Organizations by Default default_allow_create_organization_popup = Allow new user accounts to create organizations by default. default_enable_timetracking = Enable Time Tracking by Default default_enable_timetracking_popup = Enable time tracking for new repositories by default. +allow_dots_in_usernames = Allow users to use dots in their usernames. Doesn't affect existing accounts. no_reply_address = Hidden Email Domain no_reply_address_helper = Domain name for users with a hidden email address. For example, the username 'joe' will be logged in Git as 'joe@noreply.example.org' if the hidden email domain is set to 'noreply.example.org'. password_algorithm = Password Hash Algorithm @@ -534,6 +535,7 @@ include_error = ` must contain substring "%s".` glob_pattern_error = ` glob pattern is invalid: %s.` regex_pattern_error = ` regex pattern is invalid: %s.` username_error = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-'), underscore ('_') and dot ('.'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.` +username_error_no_dots = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-') and underscore ('_'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.` invalid_group_team_map_error = ` mapping is invalid: %s` unknown_error = Unknown error: captcha_incorrect = The CAPTCHA code is incorrect. diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 7eb9d086e6..d40af66bc4 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -159,6 +159,8 @@
{{if .Service.DefaultKeepEmailPrivate}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}
{{ctx.Locale.Tr "admin.config.default_allow_create_organization"}}
{{if .Service.DefaultAllowCreateOrganization}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}
+
{{ctx.Locale.Tr "admin.config.allow_dots_in_usernames"}}
+
{{if .Service.AllowDotsInUsernames}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}
{{ctx.Locale.Tr "admin.config.enable_timetracking"}}
{{if .Service.EnableTimetracking}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}
{{if .Service.EnableTimetracking}} From 4d305e77742c181f68cd24724dfc685723a41b31 Mon Sep 17 00:00:00 2001 From: Gusted Date: Sat, 24 Jun 2023 13:08:52 +0200 Subject: [PATCH 03/30] [GITEA] Add password length check on install page - Resolves #271 - Ensure that the adminstrator password is at least `MIN_PASSWORD_LENGTH`. (cherry picked from commit 28cb04c3f5040980e716ce66cd5906f324257e02) (cherry picked from commit 95371ebd92cd005e2d50a4754e60525cf6135b86) (cherry picked from commit a134288ab6b0291082d913c4e22456b31af58af9) (cherry picked from commit 4202f052cb32aec71a61dd2afd814035a9d85eea) (cherry picked from commit 510b7467d3ee0bf346ad1843775affe1df0675ae) (cherry picked from commit f3a6e1f121e89aaf608fd9890eaf06ed939d1006) (cherry picked from commit f340508819866f355feec6d01b349fa7df29ace9) (cherry picked from commit b891bb176d48c3855cc5b6e4573e7a337af9d382) (cherry picked from commit 1a1bfc38cc7863f5cb3022560cacb2006d08f113) (cherry picked from commit 083d5aefed10e54814c4438eabcd01973d305502) (cherry picked from commit 4586096be9b6214058245da3227541866ea4312f) (cherry picked from commit 039fa20cc8a5b50d5cc37de4503e8a9a80042bcc) (cherry picked from commit 3ec9cb5f5915cd0bd46ca0d20d0ab798dc7bd135) (cherry picked from commit 00be0eee3727130966c34a3b95b10f2af06ea2ec) (cherry picked from commit a1566030025df8cc83d20cbe2b6fb0f87304a1a5) --- routers/install/install.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/routers/install/install.go b/routers/install/install.go index 185e4bf6bf..1c44e1f5fd 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -358,6 +358,12 @@ func SubmitInstall(ctx *context.Context) { ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplInstall, form) return } + if len(form.AdminPasswd) < setting.MinPasswordLength { + ctx.Data["Err_Admin"] = true + ctx.Data["Err_AdminPasswd"] = true + ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplInstall, form) + return + } } // Init the engine with migration From 6644216b0b9969a7a00aa3977e52d497530866a6 Mon Sep 17 00:00:00 2001 From: Gusted Date: Fri, 21 Jul 2023 15:50:14 +0200 Subject: [PATCH 04/30] [GITEA] Show manual cron run's last time - Currently in the cron tasks, the 'Previous Time' only displays the previous time of when the cron library executes the function, but not any of the manual executions of the task. - Store the last run's time in memory in the Task struct and use that, when that time is later than time that the cron library has executed this task. - This ensures that if an instance admin manually starts a task, there's feedback that this task is/has been run, because the task might be run that quick, that the status icon already has been changed to an checkmark, - Tasks that are executed at startup now reflect this as well, as the time of the execution of that task on startup is now being shown as 'Previous Time'. - Added integration tests for the API part, which is easier to test because querying the HTML table of cron tasks is non-trivial. - Resolves https://codeberg.org/forgejo/forgejo/issues/949 (cherry picked from commit 0475e2048e7641f6ca223d486ffb8e6cecddef87) (cherry picked from commit dcc952f0db883204a1585f3fec0abcacdcab4649) (cherry picked from commit 7168a240e8b5dcba5d6bd6d1395e79eea1e6c5f5) (cherry picked from commit 4bc4cccb1b3836c43fd6f8056fcb3605e7c53bfb) (cherry picked from commit 3fe019ca3c9bbc66ff1ba644c2cb3e118f99948c) [GITEA] Show manual cron run's last time (squash) 26 jobs in cron fixtures (cherry picked from commit 8473030628302f78a954b14d02b423cc180b2751) (cherry picked from commit 871c7297423efe5f2ed33a0dd52070d826f078c8) (cherry picked from commit daefb27d2caaf27ebb8c8142634aec9151515515) (cherry picked from commit 2f66c1e4ce5f3c6c5555de35a68f7cc9a986b62f) (cherry picked from commit cdaa9615f4f4b6563c383e691aaa57b3df308cf0) (cherry picked from commit 9d1701442fb8be4749aeefa0cbabef8d5e3fa0c0) (cherry picked from commit fd34fdac1408ece6b7d9fe6a76501ed9a45d06fa) --- services/cron/cron.go | 6 ++++ services/cron/tasks.go | 9 +++++ tests/integration/api_admin_test.go | 51 +++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/services/cron/cron.go b/services/cron/cron.go index e3f31d08f0..63db75ab3b 100644 --- a/services/cron/cron.go +++ b/services/cron/cron.go @@ -106,6 +106,12 @@ func ListTasks() TaskTable { next = e.NextRun() prev = e.PreviousRun() } + + // If the manual run is after the cron run, use that instead. + if prev.Before(task.LastRun) { + prev = task.LastRun + } + task.lock.Lock() tTable = append(tTable, &TaskTableRow{ Name: task.Name, diff --git a/services/cron/tasks.go b/services/cron/tasks.go index ea1925c26c..d2c3d1d812 100644 --- a/services/cron/tasks.go +++ b/services/cron/tasks.go @@ -9,6 +9,7 @@ import ( "reflect" "strings" "sync" + "time" "code.gitea.io/gitea/models/db" system_model "code.gitea.io/gitea/models/system" @@ -37,6 +38,8 @@ type Task struct { LastMessage string LastDoer string ExecTimes int64 + // This stores the time of the last manual run of this task. + LastRun time.Time } // DoRunAtStart returns if this task should run at the start @@ -88,6 +91,12 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) { } }() graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) { + // Store the time of this run, before the function is executed, so it + // matches the behavior of what the cron library does. + t.lock.Lock() + t.LastRun = time.Now() + t.lock.Unlock() + pm := process.GetManager() doerName := "" if doer != nil && doer.ID != -1 { diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index 6613d4b715..4e222e22e6 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "testing" + "time" asymkey_model "code.gitea.io/gitea/models/asymkey" auth_model "code.gitea.io/gitea/models/auth" @@ -282,3 +283,53 @@ func TestAPIRenameUser(t *testing.T) { }) MakeRequest(t, req, http.StatusOK) } + +func TestAPICron(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // user1 is an admin user + session := loginUser(t, "user1") + + t.Run("List", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin) + urlStr := fmt.Sprintf("/api/v1/admin/cron?token=%s", token) + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "26", resp.Header().Get("X-Total-Count")) + + var crons []api.Cron + DecodeJSON(t, resp, &crons) + assert.Len(t, crons, 26) + }) + + t.Run("Execute", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + now := time.Now() + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) + /// Archive cleanup is harmless, because in the text environment there are none + /// and is thus an NOOP operation and therefore doesn't interfere with any other + /// tests. + urlStr := fmt.Sprintf("/api/v1/admin/cron/archive_cleanup?token=%s", token) + req := NewRequest(t, "POST", urlStr) + MakeRequest(t, req, http.StatusNoContent) + + // Check for the latest run time for this cron, to ensure it + // has been run. + urlStr = fmt.Sprintf("/api/v1/admin/cron?token=%s", token) + req = NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + var crons []api.Cron + DecodeJSON(t, resp, &crons) + + for _, cron := range crons { + if cron.Name == "archive_cleanup" { + assert.True(t, now.Before(cron.Prev)) + } + } + }) +} From 44b29f3a1df81c072737b139cad34435313f086c Mon Sep 17 00:00:00 2001 From: Gusted Date: Sat, 5 Aug 2023 14:47:09 +0200 Subject: [PATCH 05/30] [GITEA] Allow release creation on commit - The code and tests are already there to allow releases to be created on commits. - This patch modifies the web code to take into account that an commitID could've been passed as target. - Added unit test. - Resolves https://codeberg.org/forgejo/forgejo/issues/1196 (cherry picked from commit 90863e0ab51d1b243f67de266bbeeb7a9031c525) (cherry picked from commit c805aa23b5c6c9a8ab79e2e66786a4ef798e827a) (cherry picked from commit cf45567ca60b2a9411694c8e9b649fd77c64bdae) (cherry picked from commit 672a2b91e5612f438bd7951d173f42c223629fd1) (cherry picked from commit 82c930152cd693f8451e9553504365c724e1fced) (cherry picked from commit 95ac2508b3e8dd9fc2b0168600d989dbce0744ec) (cherry picked from commit b13a81ab98a02e30d1b508bb89cdd67a05eae782) (cherry picked from commit 9f463a7c1fa74ce17ab6ff8df49e2bcea3c1bc89) (cherry picked from commit 758ce84dc58e0c689e0fcc34386c7a8ed50f3df9) Conflicts: tests/integration/release_test.go https://codeberg.org/forgejo/forgejo/pulls/1550 (cherry picked from commit edf0531aeead2f68bbb283e437494ace33a8d3b8) --- routers/web/repo/release.go | 4 +++- tests/integration/release_test.go | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 61f1973125..f5e2c4f17c 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -390,7 +390,9 @@ func NewReleasePost(ctx *context.Context) { return } - if !ctx.Repo.GitRepo.IsBranchExist(form.Target) { + // form.Target can be a branch name or a full commitID. + if !ctx.Repo.GitRepo.IsBranchExist(form.Target) && + len(form.Target) == git.SHAFullLength && !ctx.Repo.GitRepo.IsCommitExist(form.Target) { ctx.RenderWithErr(ctx.Tr("form.target_branch_not_exist"), tplReleaseNew, &form) return } diff --git a/tests/integration/release_test.go b/tests/integration/release_test.go index 42d0d00e78..439e315347 100644 --- a/tests/integration/release_test.go +++ b/tests/integration/release_test.go @@ -21,6 +21,10 @@ import ( ) func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title string, preRelease, draft bool) { + createNewReleaseTarget(t, session, repoURL, tag, title, "master", preRelease, draft) +} + +func createNewReleaseTarget(t *testing.T, session *TestSession, repoURL, tag, title, target string, preRelease, draft bool) { req := NewRequest(t, "GET", repoURL+"/releases/new") resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) @@ -31,7 +35,7 @@ func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title st postData := map[string]string{ "_csrf": htmlDoc.GetCSRF(), "tag_name": tag, - "tag_target": "master", + "tag_target": target, "title": title, "content": "", } @@ -217,6 +221,15 @@ func TestViewReleaseListLogin(t *testing.T) { }, links) } +func TestReleaseOnCommit(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + createNewReleaseTarget(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", "65f1bf27bc3bf70f64657658635e66094edbcb4d", false, false) + + checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").Tr("repo.release.stable"), 4) +} + func TestViewTagsList(t *testing.T) { defer tests.PrepareTestEnv(t)() From d706d9e222469c689eb069ec609968296657dfdc Mon Sep 17 00:00:00 2001 From: Gusted Date: Fri, 18 Aug 2023 11:21:24 +0200 Subject: [PATCH 06/30] [GITEA] Improve HTML title on repositories - The `` element that lives inside the `<head>` element is an important element that gives browsers and search engine crawlers the title of the webpage, hence the element name. It's therefor important that this title is accurate. - Currently there are three issues with titles on repositories. It doesn't use the `FullName` and instead only uses the repository name, this doesn't distinguish which user or organisation the repository is on. It doesn't show the full treepath in the title when visiting an file inside a directory and instead only uses the latest path in treepath. It can show the repository name twice if the `.Title` variable also included the repository name such as on the repository homepage. - Use the repository's fullname (which include which user the repository is on) instead of just their name. - Display the repository's fullname if it isn't already in `.Title`. - Use the full treepath in the repository code view instead of just the last path. - Adds integration tests. - Adds a new repository (`repo59`) that has 3 depths for folders, which wasn't in any other fixture repository yet, so the full treepath for could be properly tested. - Resolves https://codeberg.org/forgejo/forgejo/issues/1276 (cherry picked from commit ff9a6a2cda34cf2b2e392cc47125ed0f619b287b) (cherry picked from commit 76dffc862103eb23d51445ef9d611296308c8413) (cherry picked from commit ff0615b9d0f3ea4bd86a28c4ac5b0c4740230c81) (cherry picked from commit 8712eaa394053a8c8f1f4cb17307e094c65c7059) (cherry picked from commit 0c11587582b8837778ee85f4e3b04241e5d71760) (cherry picked from commit 3cbd9fb7922177106b309f010dd34a68751873dc) Conflicts: tests/integration/repo_test.go https://codeberg.org/forgejo/forgejo/pulls/1512 (cherry picked from commit fbfdba8ae9e7cb9811452b30d5424fca41231a1f) Conflicts: models/fixtures/release.yml https://codeberg.org/forgejo/forgejo/pulls/1550 (cherry picked from commit 8b2bf0534ca6a2241c2a10cbecd7c96fb96558a6) --- models/fixtures/release.yml | 14 +++ models/fixtures/repo_unit.yml | 32 ++++++ models/fixtures/repository.yml | 14 +++ models/fixtures/user.yml | 2 +- models/repo/repo_list_test.go | 6 +- routers/web/repo/view.go | 4 +- templates/base/head.tmpl | 3 +- .../user2/repo59.git/HEAD | 1 + .../user2/repo59.git/config | 4 + .../user2/repo59.git/description | 1 + .../user2/repo59.git/info/exclude | 6 + .../40/8bbd3bd1f96950f8cf2f98c479557f6b18817a | Bin 0 -> 63 bytes .../5d/5c87a90af64cc67f22d60a942d5efaef8bc96b | Bin 0 -> 50 bytes .../88/3e2970ed6937cbb63311e941adb97df0ae3a52 | Bin 0 -> 49 bytes .../8c/ac7a8f434451410cc91ab9c04d07baff974ad8 | Bin 0 -> 120 bytes .../a0/ccafed39086ef520be6886d9395eb2100d317e | Bin 0 -> 102 bytes .../ab/e2a9ddfd7f542ff89bc13960a929dc8ca86c99 | Bin 0 -> 36 bytes .../cd/879fb6cf5b7bbe0fbc3a0ef44c8695fde89a56 | Bin 0 -> 108 bytes .../d8/f53dfb33f6ccf4169c34970b5e747511c18beb | Bin 0 -> 784 bytes .../f3/c1ec36c0e7605be54e71f24035caa675b7ba41 | Bin 0 -> 48 bytes .../user2/repo59.git/packed-refs | 3 + .../user2/repo59.git/refs/heads/cake-recipe | 1 + tests/integration/api_repo_test.go | 6 +- tests/integration/integration_test.go | 15 +++ tests/integration/repo_test.go | 104 ++++++++++++++++++ 25 files changed, 206 insertions(+), 10 deletions(-) create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/HEAD create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/config create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/description create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/info/exclude create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/40/8bbd3bd1f96950f8cf2f98c479557f6b18817a create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/5d/5c87a90af64cc67f22d60a942d5efaef8bc96b create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/88/3e2970ed6937cbb63311e941adb97df0ae3a52 create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/8c/ac7a8f434451410cc91ab9c04d07baff974ad8 create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/a0/ccafed39086ef520be6886d9395eb2100d317e create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/ab/e2a9ddfd7f542ff89bc13960a929dc8ca86c99 create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/cd/879fb6cf5b7bbe0fbc3a0ef44c8695fde89a56 create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/d8/f53dfb33f6ccf4169c34970b5e747511c18beb create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/f3/c1ec36c0e7605be54e71f24035caa675b7ba41 create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/packed-refs create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/refs/heads/cake-recipe diff --git a/models/fixtures/release.yml b/models/fixtures/release.yml index 372a79509f..938f2cd7b7 100644 --- a/models/fixtures/release.yml +++ b/models/fixtures/release.yml @@ -150,3 +150,17 @@ is_prerelease: false is_tag: false created_unix: 946684803 + +- id: 12 + repo_id: 59 + publisher_id: 2 + tag_name: "v1.0" + lower_tag_name: "v1.0" + target: "main" + title: "v1.0" + sha1: "d8f53dfb33f6ccf4169c34970b5e747511c18beb" + num_commits: 1 + is_draft: false + is_prerelease: false + is_tag: false + created_unix: 946684803 diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml index 0104419550..b359f547b6 100644 --- a/models/fixtures/repo_unit.yml +++ b/models/fixtures/repo_unit.yml @@ -608,6 +608,38 @@ type: 1 created_unix: 946684810 +# BEGIN Forgejo [GITEA] Improve HTML title on repositories +- + id: 1093 + repo_id: 59 + type: 1 + created_unix: 946684810 + +- + id: 1094 + repo_id: 59 + type: 2 + created_unix: 946684810 + +- + id: 1095 + repo_id: 59 + type: 3 + created_unix: 946684810 + +- + id: 1096 + repo_id: 59 + type: 4 + created_unix: 946684810 + +- + id: 1097 + repo_id: 59 + type: 5 + created_unix: 946684810 +# END Forgejo [GITEA] Improve HTML title on repositories + - id: 91 repo_id: 58 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index 373c1caa62..7fce1d1b28 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -1467,6 +1467,7 @@ owner_name: user27 lower_name: repo49 name: repo49 + description: A wonderful repository with more than just a README.md default_branch: master num_watches: 0 num_stars: 0 @@ -1693,3 +1694,16 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + +- + id: 59 + owner_id: 2 + owner_name: user2 + lower_name: repo59 + name: repo59 + default_branch: master + is_empty: false + is_archived: false + is_private: false + status: 0 + num_issues: 0 diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index fd51379816..79fbb981f6 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -66,7 +66,7 @@ num_followers: 2 num_following: 1 num_stars: 2 - num_repos: 14 + num_repos: 15 num_teams: 0 num_members: 0 visibility: 0 diff --git a/models/repo/repo_list_test.go b/models/repo/repo_list_test.go index 8a1799aac0..a8b958109c 100644 --- a/models/repo/repo_list_test.go +++ b/models/repo/repo_list_test.go @@ -138,12 +138,12 @@ func getTestCases() []struct { { name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse}, - count: 31, + count: 32, }, { name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse}, - count: 36, + count: 37, }, { name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", @@ -158,7 +158,7 @@ func getTestCases() []struct { { name: "AllPublic/PublicRepositoriesOfOrganization", opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse}, - count: 31, + count: 32, }, { name: "AllTemplates", diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 6571790627..a1a15c2919 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -165,7 +165,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { if ctx.Repo.TreePath != "" { ctx.Data["HideRepoInfo"] = true - ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName) + ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+util.PathEscapeSegments(ctx.Repo.TreePath), ctx.Repo.RefName) } subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true) @@ -344,7 +344,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } defer dataRc.Close() - ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName) + ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+util.PathEscapeSegments(ctx.Repo.TreePath), ctx.Repo.RefName) ctx.Data["FileIsSymlink"] = entry.IsLink() ctx.Data["FileName"] = blob.Name() ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 876b42d512..18964f374d 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -2,7 +2,8 @@ <html lang="{{ctx.Locale.Lang}}" data-theme="{{ThemeName .SignedUser}}"> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> - <title>{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}} + {{/* Display `- .Repsository.FullName` only if `.Title` does not already start with that. */}} + {{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if and (.Repository.Name) (not (StringUtils.HasPrefix .Title .Repository.FullName))}}{{.Repository.FullName}} - {{end}}{{AppName}} {{if .ManifestData}}{{end}} diff --git a/tests/gitea-repositories-meta/user2/repo59.git/HEAD b/tests/gitea-repositories-meta/user2/repo59.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo59.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user2/repo59.git/config b/tests/gitea-repositories-meta/user2/repo59.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo59.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/tests/gitea-repositories-meta/user2/repo59.git/description b/tests/gitea-repositories-meta/user2/repo59.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo59.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user2/repo59.git/info/exclude b/tests/gitea-repositories-meta/user2/repo59.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo59.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/40/8bbd3bd1f96950f8cf2f98c479557f6b18817a b/tests/gitea-repositories-meta/user2/repo59.git/objects/40/8bbd3bd1f96950f8cf2f98c479557f6b18817a new file mode 100644 index 0000000000000000000000000000000000000000..567284ef1c21f472841f3d729cbfe024d78c448c GIT binary patch literal 63 zcmV-F0Korv0ZYosPf{>3XHZrMN-fAQ&Me6)6NNXyJgE!N}W0ssfX5``$?8bAO5 literal 0 HcmV?d00001 diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/5d/5c87a90af64cc67f22d60a942d5efaef8bc96b b/tests/gitea-repositories-meta/user2/repo59.git/objects/5d/5c87a90af64cc67f22d60a942d5efaef8bc96b new file mode 100644 index 0000000000000000000000000000000000000000..f23960f4cc8c09af29fc4765f683b697a4547d74 GIT binary patch literal 50 zcmV-20L}k+0V^p=O;s>9VK6ZO0)@QP;*!j~bcW9d-8WL<+jltv I07;Az9utceGXMYp literal 0 HcmV?d00001 diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/88/3e2970ed6937cbb63311e941adb97df0ae3a52 b/tests/gitea-repositories-meta/user2/repo59.git/objects/88/3e2970ed6937cbb63311e941adb97df0ae3a52 new file mode 100644 index 0000000000000000000000000000000000000000..46cc9e3e5ec14999b5424f35c1c548db9e9e33c1 GIT binary patch literal 49 zcmb7FlR6{FfcPQQ3!H%bn$i7%S~Z$=-z96@n>ehkMsI7j#P%$ zXG=6znHT_pLP~0C0Yhv|`%12FKF8{nu5nG#jr;Y!`(!rMjI`9mlG377y^@L&h7LQ; ag14FGr?(jkzI0r>v-ZO}s~`Z9QY~(zy*Y~j literal 0 HcmV?d00001 diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/a0/ccafed39086ef520be6886d9395eb2100d317e b/tests/gitea-repositories-meta/user2/repo59.git/objects/a0/ccafed39086ef520be6886d9395eb2100d317e new file mode 100644 index 0000000000000000000000000000000000000000..3b71228a7ec7b91f1066cda387ef6efa28c2ae68 GIT binary patch literal 102 zcmV-s0Ga=I0V^p=O;xb4U@$Z=Ff%bx2y%6F@paY9O<`F5Xyx6%^&$E{W*@XnSgCoZ zXGP9TsG{Q3M6>2AdT%A&7_dRgi_+j`HiK;pmmM?MU`7Mx>)%bH?6OHM zk5sBnouqA=Y63LcbH7YOmH|GAl4Hc@EW@%K&C)1I1Uia^1hFYP#!;RNM>a}%Dtb?4 zJAn6?4K(;YO4A`5NBYlfjhe2`eoNZs4?rJ;J;Mr|fWQvz5u(27_uQ2I-(JxbV^x4( z|B6Ty%>s_%fUBlh_~u>6-<$#zs9bFmF%~6^Q@J9f=}|(LabtH3q_NxV$YbR02+h-Wy>8SN zL-n>NnOHxD1_ktBKMTFMF~Oeo44c$Nx1zjPg-n1^s{~0y49o8@?{IqT_1l9FDBH+tz~X}{gyB7!qm$KrCW*^)AW)NzRtpG4}}%%=|SNs z@Nz?3mnX@zAyYKZV*tH#nR{bxS}d9i?M(85HRz`|j6_MUVShjg2W|Ppe8h&7*MdL7JHfOBCJr|RNl?>o$nk$Jg%Kk7Ay z>ilZ^!I!SZjq{9}32tH%t54`QEQTUns*)fL4hA0LId@WGsHUmmoZfJs-`Nc O!MB&yMEwL3??r>^yMtc< literal 0 HcmV?d00001 diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/f3/c1ec36c0e7605be54e71f24035caa675b7ba41 b/tests/gitea-repositories-meta/user2/repo59.git/objects/f3/c1ec36c0e7605be54e71f24035caa675b7ba41 new file mode 100644 index 0000000000000000000000000000000000000000..b6803eb5a271b9d33c981d1ba24fa4126ae409db GIT binary patch literal 48 zcmV-00MGw;0V^p=O;s>9W-u`T0)@2voRrieh6QKVzqRDZ`>L=nqwS_;+$I5D!#V&X Gkq<4~#T1zU literal 0 HcmV?d00001 diff --git a/tests/gitea-repositories-meta/user2/repo59.git/packed-refs b/tests/gitea-repositories-meta/user2/repo59.git/packed-refs new file mode 100644 index 0000000000..114c84d2aa --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo59.git/packed-refs @@ -0,0 +1,3 @@ +# pack-refs with: peeled fully-peeled sorted +d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/heads/master +d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/tags/v1.0 diff --git a/tests/gitea-repositories-meta/user2/repo59.git/refs/heads/cake-recipe b/tests/gitea-repositories-meta/user2/repo59.git/refs/heads/cake-recipe new file mode 100644 index 0000000000..63bbea6692 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo59.git/refs/heads/cake-recipe @@ -0,0 +1 @@ +d8f53dfb33f6ccf4169c34970b5e747511c18beb diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index a6d32a89ea..edab964475 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -93,9 +93,9 @@ func TestAPISearchRepo(t *testing.T) { }{ { name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{ - nil: {count: 33}, - user: {count: 33}, - user2: {count: 33}, + nil: {count: 34}, + user: {count: 34}, + user2: {count: 34}, }, }, { diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 49a714c343..a3d0eb1019 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -531,3 +531,18 @@ func GetCSRF(t testing.TB, session *TestSession, urlStr string) string { doc := NewHTMLParser(t, resp.Body) return doc.GetCSRF() } + +func GetHTMLTitle(t testing.TB, session *TestSession, urlStr string) string { + t.Helper() + + req := NewRequest(t, "GET", urlStr) + var resp *httptest.ResponseRecorder + if session == nil { + resp = MakeRequest(t, req, http.StatusOK) + } else { + resp = session.MakeRequest(t, req, http.StatusOK) + } + + doc := NewHTMLParser(t, resp.Body) + return doc.Find("head title").Text() +} diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index d6de72553c..d044174df1 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -203,6 +203,110 @@ func TestViewAsRepoAdmin(t *testing.T) { } } +func TestRepoHTMLTitle(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + t.Run("Repository homepage", func(t *testing.T) { + t.Run("Without description", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user2/repo1") + assert.EqualValues(t, "user2/repo1 - Gitea: Git with a cup of tea", htmlTitle) + }) + t.Run("With description", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user27/repo49") + assert.EqualValues(t, "user27/repo49: A wonderful repository with more than just a README.md - Gitea: Git with a cup of tea", htmlTitle) + }) + }) + + t.Run("Code view", func(t *testing.T) { + t.Run("Directory", func(t *testing.T) { + t.Run("Default branch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/branch/master/deep/nesting") + assert.EqualValues(t, "repo59/deep/nesting at master - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle) + }) + t.Run("Non-default branch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/branch/cake-recipe/deep/nesting") + assert.EqualValues(t, "repo59/deep/nesting at cake-recipe - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle) + }) + t.Run("Commit", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/commit/d8f53dfb33f6ccf4169c34970b5e747511c18beb/deep/nesting/") + assert.EqualValues(t, "repo59/deep/nesting at d8f53dfb33f6ccf4169c34970b5e747511c18beb - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle) + }) + t.Run("Tag", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/tag/v1.0/deep/nesting/") + assert.EqualValues(t, "repo59/deep/nesting at v1.0 - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle) + }) + }) + t.Run("File", func(t *testing.T) { + t.Run("Default branch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/branch/master/deep/nesting/folder/secret_sauce_recipe.txt") + assert.EqualValues(t, "repo59/deep/nesting/folder/secret_sauce_recipe.txt at master - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle) + }) + t.Run("Non-default branch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/branch/cake-recipe/deep/nesting/folder/secret_sauce_recipe.txt") + assert.EqualValues(t, "repo59/deep/nesting/folder/secret_sauce_recipe.txt at cake-recipe - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle) + }) + t.Run("Commit", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/commit/d8f53dfb33f6ccf4169c34970b5e747511c18beb/deep/nesting/folder/secret_sauce_recipe.txt") + assert.EqualValues(t, "repo59/deep/nesting/folder/secret_sauce_recipe.txt at d8f53dfb33f6ccf4169c34970b5e747511c18beb - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle) + }) + t.Run("Tag", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user2/repo59/src/tag/v1.0/deep/nesting/folder/secret_sauce_recipe.txt") + assert.EqualValues(t, "repo59/deep/nesting/folder/secret_sauce_recipe.txt at v1.0 - user2/repo59 - Gitea: Git with a cup of tea", htmlTitle) + }) + }) + }) + + t.Run("Issues view", func(t *testing.T) { + t.Run("Overview page", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user2/repo1/issues") + assert.EqualValues(t, "Issues - user2/repo1 - Gitea: Git with a cup of tea", htmlTitle) + }) + t.Run("View issue page", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user2/repo1/issues/1") + assert.EqualValues(t, "#1 - issue1 - user2/repo1 - Gitea: Git with a cup of tea", htmlTitle) + }) + }) + + t.Run("Pull requests view", func(t *testing.T) { + t.Run("Overview page", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user2/repo1/pulls") + assert.EqualValues(t, "Pull Requests - user2/repo1 - Gitea: Git with a cup of tea", htmlTitle) + }) + t.Run("View pull request", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + htmlTitle := GetHTMLTitle(t, nil, "/user2/repo1/pulls/2") + assert.EqualValues(t, "#2 - issue2 - user2/repo1 - Gitea: Git with a cup of tea", htmlTitle) + }) + }) +} + // TestViewFileInRepo repo description, topics and summary should not be displayed when viewing a file func TestViewFileInRepo(t *testing.T) { defer tests.PrepareTestEnv(t)() From 0d6b934eba1c0e9b27b364791113aae816b6b366 Mon Sep 17 00:00:00 2001 From: Gusted Date: Fri, 18 Aug 2023 04:39:23 +0200 Subject: [PATCH 07/30] [GITEA] Add slow SQL query warning - Databases are one of the most important parts of Forgejo, every interaction with Forgejo uses the database in one way or another. Therefore, it is important to maintain the database and recognize when Forgejo is not doing well with the database. Forgejo already has the option to log *every* SQL query along with its execution time, but monitoring becomes impractical for larger instances and takes up unnecessary storage in the logs. - Add a QoL enhancement that allows instance administrators to specify a threshold value beyond which query execution time is logged as a warning in the xorm logger. The default value is a conservative five seconds to avoid this becoming a source of spam in the logs. - The use case for this patch is that with an instance the size of Codeberg, monitoring SQL logs is not very fruitful and most of them are uninteresting. Recently, in the context of persistent deadlock issues (https://codeberg.org/forgejo/forgejo/issues/220), I have noticed that certain queries hold locks on tables like comment and issue for several seconds. This patch helps to identify which queries these are and when they happen. - Added unit test. (cherry picked from commit 24bbe7886fb4cb9a38c8dab8c44f4c9cbfa25481) (cherry picked from commit 6e29145b3c1455498531593d38e6a914941a12cb) (cherry picked from commit 63731e30712872bd2395eb3cf36d9996e5793645) (cherry picked from commit 3ce1a097369c132654de70df707b867e47bd1c40) (cherry picked from commit a64426907de788cc0937a7a2b16af4d2f26f7fe6) (cherry picked from commit 4b1921569156445c58d9889602733da5934c7b95) (cherry picked from commit e6356744359fa947c049827d60c2ea0e277e03dc) (cherry picked from commit 9cf501f1af4cd870221cef6af489618785b71186) --- models/db/engine.go | 28 +++++++++++++++++++++++++++ models/db/engine_test.go | 38 +++++++++++++++++++++++++++++++++++++ modules/setting/database.go | 2 ++ 3 files changed, 68 insertions(+) diff --git a/models/db/engine.go b/models/db/engine.go index b5a41f93e3..0fcd4849bf 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -11,10 +11,13 @@ import ( "io" "reflect" "strings" + "time" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "xorm.io/xorm" + "xorm.io/xorm/contexts" "xorm.io/xorm/names" "xorm.io/xorm/schemas" @@ -147,6 +150,13 @@ func InitEngine(ctx context.Context) error { xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime) xormEngine.SetDefaultContext(ctx) + if setting.Database.SlowQueryTreshold > 0 { + xormEngine.AddHook(&SlowQueryHook{ + Treshold: setting.Database.SlowQueryTreshold, + Logger: log.GetLogger("xorm"), + }) + } + SetDefaultEngine(ctx, xormEngine) return nil } @@ -300,3 +310,21 @@ func SetLogSQL(ctx context.Context, on bool) { sess.Engine().ShowSQL(on) } } + +type SlowQueryHook struct { + Treshold time.Duration + Logger log.Logger +} + +var _ contexts.Hook = &SlowQueryHook{} + +func (SlowQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) { + return c.Ctx, nil +} + +func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error { + if c.ExecuteTime >= h.Treshold { + h.Logger.Log(8, log.WARN, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime) + } + return nil +} diff --git a/models/db/engine_test.go b/models/db/engine_test.go index c9ae5f1542..ba922821b0 100644 --- a/models/db/engine_test.go +++ b/models/db/engine_test.go @@ -6,15 +6,19 @@ package db_test import ( "path/filepath" "testing" + "time" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" _ "code.gitea.io/gitea/cmd" // for TestPrimaryKeys "github.com/stretchr/testify/assert" + "xorm.io/xorm" ) func TestDumpDatabase(t *testing.T) { @@ -85,3 +89,37 @@ func TestPrimaryKeys(t *testing.T) { } } } + +func TestSlowQuery(t *testing.T) { + lc, cleanup := test.NewLogChecker("slow-query") + lc.StopMark("[Slow SQL Query]") + defer cleanup() + + e := db.GetEngine(db.DefaultContext) + engine, ok := e.(*xorm.Engine) + assert.True(t, ok) + + // It's not possible to clean this up with XORM, but it's luckily not harmful + // to leave around. + engine.AddHook(&db.SlowQueryHook{ + Treshold: time.Second * 10, + Logger: log.GetLogger("slow-query"), + }) + + // NOOP query. + e.Exec("SELECT 1 WHERE false;") + + _, stopped := lc.Check(100 * time.Millisecond) + assert.False(t, stopped) + + engine.AddHook(&db.SlowQueryHook{ + Treshold: 0, // Every query should be logged. + Logger: log.GetLogger("slow-query"), + }) + + // NOOP query. + e.Exec("SELECT 1 WHERE false;") + + _, stopped = lc.Check(100 * time.Millisecond) + assert.True(t, stopped) +} diff --git a/modules/setting/database.go b/modules/setting/database.go index 709655368c..219c96e4d7 100644 --- a/modules/setting/database.go +++ b/modules/setting/database.go @@ -44,6 +44,7 @@ var ( ConnMaxLifetime time.Duration IterateBufferSize int AutoMigration bool + SlowQueryTreshold time.Duration }{ Timeout: 500, IterateBufferSize: 50, @@ -86,6 +87,7 @@ func loadDBSetting(rootCfg ConfigProvider) { Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10) Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second) Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true) + Database.SlowQueryTreshold = sec.Key("SLOW_QUERY_TRESHOLD").MustDuration(5 * time.Second) } // DBConnStr returns database connection string From 960a9786ef62eb8664bca129e9d4fae22e98f378 Mon Sep 17 00:00:00 2001 From: Gusted Date: Fri, 18 Aug 2023 15:18:23 +0200 Subject: [PATCH 08/30] [GITEA] Use vertical tabs on issue filters - This is actually https://github.com/go-gitea/gitea/pull/19978 & https://github.com/go-gitea/gitea/pull/19486 but was removed in one of the UI refactors of v1.20 - This is a very technical fix and is best explained in the CSS comments. But the short version: When there's an overflow being set, but you want an element to 'break out' of that overflow with `position: absolute`, it sometimes doesn't work! You need to set some CSS to let the browser know that the element needs to use an element outside of that overflow as 'clip parent'. - Resolves my internal frustration with the mobile UI constantly getting broken. (cherry picked from commit 879f842bed999190506e1d60508e7aede1a4be21) (cherry picked from commit 6099c9b41b9a135fb14b41304459056050abbbe2) (cherry picked from commit 0749d00b160033de0457e17baa1e000e68810d94) (cherry picked from commit ec6a5428a7f05d8e6d2e8a6c476b2b9d35656b0f) (cherry picked from commit 9d0bee784d387fac026d3dcc09f10e496a99a7c5) (cherry picked from commit 61d6ae48828cb83ca3668a28ba8ddcb7fcb471d5) (cherry picked from commit 8b3f3639b60ac6f3de8f9fcd83ac7a48bbd659f0) (cherry picked from commit 2c600ddb2c3e76598b9bdbd58026ab76d7590470) --- web_src/css/repo/issue-list.css | 35 +++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/web_src/css/repo/issue-list.css b/web_src/css/repo/issue-list.css index d2f8e429f5..15ea70bbc9 100644 --- a/web_src/css/repo/issue-list.css +++ b/web_src/css/repo/issue-list.css @@ -17,10 +17,6 @@ } @media (max-width: 767.98px) { - .issue-list-toolbar-right .dropdown .menu { - left: auto !important; - right: auto !important; - } .issue-list-navbar { order: 0; } @@ -31,6 +27,37 @@ .issue-list-search { order: 2 !important; } + /* Don't use flex wrap on mobile as it takes too much vertical space. + * Only set overflow properties on mobile screens, because while the + * CSS trick to pop out from overflowing works on desktop screen, it + * has a massive flaw that it cannot inherited any max width from it's 'real' + * parent and therefor ends up taking more vertical space than is desired. + **/ + .issue-list-toolbar-right .filter.menu { + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + } + + /* The following few CSS was created with care and built with the information + * from CSS-Tricks: https://css-tricks.com/popping-hidden-overflow/ + */ + + /* It's important that every element up to .issue-list-toolbar-right doesn't + * have a position set, such that element that wants to pop out will use + * .issue-list-toolbar-right as 'clip parent' and thereby avoids the + * overflow-y: hidden. + */ + .issue-list-toolbar-right .filter.menu > .dropdown.item { + position: initial; + } + /* It's important that this element and not an child has `position` set. + * Set width so that overflow-x knows where to stop overflowing. + */ + .issue-list-toolbar-right { + position: relative; + width: 100%; + } } #issue-list .flex-item-title .labels-list { From 7cabcf0a28420d52002bb5f377b8d0b6b84ce8d6 Mon Sep 17 00:00:00 2001 From: Gusted Date: Sat, 19 Aug 2023 12:58:17 +0200 Subject: [PATCH 09/30] [GITEA] Add anchor to review types - The review type '22' is a general comment type that is attached to single codecomments, reviews with multiple comments or to simple approve and request changes comment. This comment can be used to create a link towards this action on an pull request. - Adds an anchor to the review comment type, so that when its getting linked to it, it actually jumps towards that event. - This also now fixes the behavior that after you created a review you will be redirected to that review and because this is an general comment type other mails will also be 'fixed' such as the approved or request changes. - Resolves https://codeberg.org/forgejo/forgejo/issues/1248 (cherry picked from commit b0c3075a794be3ccc768b5018f56ace6f12c11e1) (cherry picked from commit f61505281ced3b19fb76267b81aef0939629bfb4) (cherry picked from commit 1741a5f1fe6adc68bb5f87bdd1c5bdc5bfaa45c7) (cherry picked from commit 5ee4cf2ed952f043cf2d9d5546d7a6c24c6c1ee7) (cherry picked from commit 601f00e5306133fc49488f664ded1b346b5894ff) (cherry picked from commit 9ce8aa57fb36e4bec1a5749b33569d68da621fc5) (cherry picked from commit 854b5a13796578cfc7d14426fd44e22a44794730) (cherry picked from commit 96fbeb44168671fb5e35511a463d7a75fcff3acb) --- templates/repo/issue/view_content/comments.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index d3fe424b84..98e25529a7 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -364,7 +364,7 @@ {{end}} {{else if eq .Type 22}} -
+
{{if .OriginalAuthor}} {{else}} From 6b66fe449d5ee409ae5590ad08cdf46b7dfe8aa9 Mon Sep 17 00:00:00 2001 From: zareck Date: Sun, 20 Aug 2023 11:16:30 -0300 Subject: [PATCH 10/30] [GITEA] add GitHub repo migration test Signed-off-by: zareck (cherry picked from commit f48e3ff0db027c6420446c0bab3089d9a46194a8) Removing comments and make command (cherry picked from commit 7664a423a5abf051383374b4156857e83faee7c0) (cherry picked from commit b2fb43536424f90373fdc177bd2c79c374efd2be) (cherry picked from commit 0a24a819a9561c8355adb00b7b202438c5c1bc1a) (cherry picked from commit 155cc19f75662998fcb2a1a08e345e0724437a58) (cherry picked from commit 223537f71a05107d69eb5edb8d62d40e5fac5fee) (cherry picked from commit ffbe2970cc7a778bfbcd9a93cc03bbc4bce38897) (cherry picked from commit 836836bd73a5635ee13b032d1600e7da842db42c) --- tests/integration/repo_migrate_test.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/integration/repo_migrate_test.go b/tests/integration/repo_migrate_test.go index 91e2961d6d..9fb7a73379 100644 --- a/tests/integration/repo_migrate_test.go +++ b/tests/integration/repo_migrate_test.go @@ -15,8 +15,8 @@ import ( "github.com/stretchr/testify/assert" ) -func testRepoMigrate(t testing.TB, session *TestSession, cloneAddr, repoName string) *httptest.ResponseRecorder { - req := NewRequest(t, "GET", fmt.Sprintf("/repo/migrate?service_type=%d", structs.PlainGitService)) // render plain git migration page +func testRepoMigrate(t testing.TB, session *TestSession, cloneAddr, repoName string, service structs.GitServiceType) *httptest.ResponseRecorder { + req := NewRequest(t, "GET", fmt.Sprintf("/repo/migrate?service_type=%d", service)) // render plain git migration page resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) @@ -31,7 +31,7 @@ func testRepoMigrate(t testing.TB, session *TestSession, cloneAddr, repoName str "clone_addr": cloneAddr, "uid": uid, "repo_name": repoName, - "service": fmt.Sprintf("%d", structs.PlainGitService), + "service": fmt.Sprintf("%d", service), }) resp = session.MakeRequest(t, req, http.StatusSeeOther) @@ -41,5 +41,17 @@ func testRepoMigrate(t testing.TB, session *TestSession, cloneAddr, repoName str func TestRepoMigrate(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user2") - testRepoMigrate(t, session, "https://github.com/go-gitea/test_repo.git", "git") + for _, s := range []struct { + testName string + cloneAddr string + repoName string + service structs.GitServiceType + }{ + {"TestMigrateGithub", "https://github.com/go-gitea/test_repo.git", "git", structs.PlainGitService}, + {"TestMigrateGithub", "https://github.com/go-gitea/test_repo.git", "github", structs.GithubService}, + } { + t.Run(s.testName, func(t *testing.T) { + testRepoMigrate(t, session, s.cloneAddr, s.repoName, s.service) + }) + } } From 67dbc3fda78ce1b04f443c4304f82a39ff943281 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Tue, 29 Aug 2023 10:33:02 +0200 Subject: [PATCH 11/30] [GITEA] Show manual cron run's last time (squash) 27 jobs in cron fixtures (cherry picked from commit bb71759454071a3f555afdb33702e2af6037fc9b) (cherry picked from commit cc258d342e80dd40d8107b6581c452d2b71c2598) (cherry picked from commit f3c615e7d254e259357ff0b75873cb1551d8ccf5) (cherry picked from commit 360ac8e69d69f7306e74cf476b3b5f16d8fe6163) (cherry picked from commit 30c276085dccdbfd07eea47813fcdee7c9646e5d) (cherry picked from commit aad366c085e5cf513083bbec1f7c749623040b4a) --- tests/integration/api_admin_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index 4e222e22e6..85b9103e53 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -298,11 +298,11 @@ func TestAPICron(t *testing.T) { req := NewRequest(t, "GET", urlStr) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "26", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "27", resp.Header().Get("X-Total-Count")) var crons []api.Cron DecodeJSON(t, resp, &crons) - assert.Len(t, crons, 26) + assert.Len(t, crons, 27) }) t.Run("Execute", func(t *testing.T) { From 7209cb2d3faebfdbb4ee79ad26dd08b3cea82fe9 Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 29 Aug 2023 22:00:16 +0200 Subject: [PATCH 12/30] [GITEA] Simplify cron list test (squash) - As per https://codeberg.org/forgejo/forgejo/pulls/1352#issuecomment-1076074 - Only test if the returned stuff is correct, not necessarily accurate. (cherry picked from commit 55bcaf60ec21aec000ff79635981a16566c77d43) (cherry picked from commit 4f03e4810619455e7dca65741cb53a9fd2d98bfe) (cherry picked from commit f35c31b9beef33e0aafc4e62e5b8fa38b3623494) (cherry picked from commit d08f486da2649c344fa447f41274c39ddd88e82e) (cherry picked from commit 5d035c01c59fc7d10f7f71ea9589ca316db33a53) (cherry picked from commit e2d76957e50fb0ec66d4c1dd6501817d83607293) --- tests/integration/api_admin_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index 85b9103e53..423a27eb52 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -298,11 +298,12 @@ func TestAPICron(t *testing.T) { req := NewRequest(t, "GET", urlStr) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "27", resp.Header().Get("X-Total-Count")) + assert.NotEmpty(t, resp.Header().Get("X-Total-Count")) var crons []api.Cron DecodeJSON(t, resp, &crons) - assert.Len(t, crons, 27) + + assert.NotEmpty(t, crons) }) t.Run("Execute", func(t *testing.T) { From 2b39e973be4e72b20f26a40aed894b67371f563f Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Wed, 30 Aug 2023 15:21:18 +0200 Subject: [PATCH 13/30] [GITEA] [picture].*AVATAR_UPLOAD_PATH is legacy (cherry picked from commit cb4cc01825458752efe01628f705b4f8676e49a2) (cherry picked from commit bef11d61318a462e34202f78fad7f883b0756a88) (cherry picked from commit 077b1c52b6e330a66aa55c4e29562278e94026d1) (cherry picked from commit aff7aa08587855b71495fa52c301d653a42da38f) (cherry picked from commit d2f8f6eacbc669a3ae800304f0f8b3f5a11e1a11) (cherry picked from commit 476bd3c4910d15fe7e5f68abc304fd4e3166edaa) --- custom/conf/app.example.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index ec49e42915..566e7f56f3 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1770,9 +1770,6 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;AVATAR_UPLOAD_PATH = data/avatars -;REPOSITORY_AVATAR_UPLOAD_PATH = data/repo-avatars -;; ;; How Gitea deals with missing repository avatars ;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used ;REPOSITORY_AVATAR_FALLBACK = none From e0d51303129fe8763d87ed5f859eeae8f0cc6188 Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Thu, 7 Sep 2023 07:11:29 +0000 Subject: [PATCH 14/30] [GITEA] notifies admins on new user registration Sends email with information on the new user (time of creation and time of last sign-in) and a link to manage the new user from the admin panel closes: https://codeberg.org/forgejo/forgejo/issues/480 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1371 Co-authored-by: Aravinth Manivannan Co-committed-by: Aravinth Manivannan (cherry picked from commit c721aa828ba6aec5ef95459cfc632a0a1f7463e9) (cherry picked from commit 6487efcb9da61be1f802f1cd8007330153322770) Conflicts: modules/notification/base/notifier.go modules/notification/base/null.go modules/notification/notification.go https://codeberg.org/forgejo/forgejo/pulls/1422 (cherry picked from commit 7ea66ee1c5dd21d9e6a43f961e8adc71ec79b806) Conflicts: services/notify/notifier.go services/notify/notify.go services/notify/null.go https://codeberg.org/forgejo/forgejo/pulls/1469 (cherry picked from commit 7d2d9970115c94954dacb45684f9e3c16117ebfe) (cherry picked from commit 435a54f14039408b315c99063bdce28c7ef6fe2f) (cherry picked from commit 8ec7b3e4484383445fa2622a28bb4f5c990dd4f2) [GITEA] notifies admins on new user registration (squash) performance bottleneck Refs: https://codeberg.org/forgejo/forgejo/issues/1479 (cherry picked from commit 97ac9147ff3643cca0a059688c6b3c53479e28a7) (cherry picked from commit 19f295c16bd392aa438477fa3c42038d63d1a06a) (cherry picked from commit 3367dcb2cf5328e2afc89f7d5a008b64ede1c987) [GITEA] notifies admins on new user registration (squash) cosmetic changes Co-authored-by: delvh (cherry picked from commit 9f1670e040b469ed4346aa2689a75088e4e71c8b) (cherry picked from commit de5bb2a224ab2ae9be891de1ee88a7454a07f7e9) (cherry picked from commit 8f8e52f31a4da080465521747a2c5c0c51ed65e3) --- custom/conf/app.example.ini | 2 + .../config-cheat-sheet.en-us.md | 1 + models/user/user.go | 6 ++ models/user/user_test.go | 10 +++ modules/setting/admin.go | 5 +- options/locale/locale_en-US.ini | 4 + routers/web/auth/auth.go | 3 +- services/mailer/mail_admin_new_user.go | 80 +++++++++++++++++ services/mailer/mail_admin_new_user_test.go | 88 +++++++++++++++++++ services/mailer/notify.go | 4 + services/notify/notifier.go | 2 + services/notify/notify.go | 7 ++ services/notify/null.go | 3 + templates/mail/notify/admin_new_user.tmpl | 22 +++++ 14 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 services/mailer/mail_admin_new_user.go create mode 100644 services/mailer/mail_admin_new_user_test.go create mode 100644 templates/mail/notify/admin_new_user.tmpl diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 566e7f56f3..5ad1427d56 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1456,6 +1456,8 @@ LEVEL = Info ;; ;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled ;DEFAULT_EMAIL_NOTIFICATIONS = enabled +;; Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false +;SEND_NOTIFICATION_EMAIL_ON_NEW_USER = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 3c864582ce..32de11c9c5 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -510,6 +510,7 @@ And the following unique queues: - `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled - `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations. +- `SEND_NOTIFICATION_EMAIL_ON_NEW_USER`: **false**: Send an email to all admins when a new user signs up to inform the admins about this act. ## Security (`security`) diff --git a/models/user/user.go b/models/user/user.go index 60aa6b9a6f..95786a5a63 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -223,6 +223,12 @@ func GetAllUsers(ctx context.Context) ([]*User, error) { return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users) } +// GetAllAdmins returns a slice of all adminusers found in DB. +func GetAllAdmins(ctx context.Context) ([]*User, error) { + users := make([]*User, 0) + return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).And("is_admin = ?", true).Find(&users) +} + // IsLocal returns true if user login type is LoginPlain. func (u *User) IsLocal() bool { return u.LoginType <= auth.Plain diff --git a/models/user/user_test.go b/models/user/user_test.go index 971117482c..fed2cb2c48 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -544,3 +544,13 @@ func Test_ValidateUser(t *testing.T) { assert.EqualValues(t, expected, err == nil, fmt.Sprintf("case: %+v", kase)) } } + +func TestGetAllAdmins(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + admins, err := user_model.GetAllAdmins(db.DefaultContext) + assert.NoError(t, err) + + assert.Len(t, admins, 1) + assert.Equal(t, int64(1), admins[0].ID) +} diff --git a/modules/setting/admin.go b/modules/setting/admin.go index 2d2dd26de9..d7f0ee827d 100644 --- a/modules/setting/admin.go +++ b/modules/setting/admin.go @@ -5,8 +5,9 @@ package setting // Admin settings var Admin struct { - DisableRegularOrgCreation bool - DefaultEmailNotification string + DisableRegularOrgCreation bool + DefaultEmailNotification string + SendNotificationEmailOnNewUser bool } func loadAdminFrom(rootCfg ConfigProvider) { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 1071a61988..9f3124ac87 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -441,6 +441,10 @@ activate_email = Verify your email address activate_email.title = %s, please verify your email address activate_email.text = Please click the following link to verify your email address within %s: +admin.new_user.subject = New user %s just signed up +admin.new_user.user_info = User Information +admin.new_user.text = Please click here to manage the user from the admin panel. + register_notify = Welcome to Gitea register_notify.title = %[1]s, welcome to %[2]s register_notify.text_1 = this is your registration confirmation email for %s! diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 8017602d99..465077a909 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -30,6 +30,7 @@ import ( "code.gitea.io/gitea/services/externalaccount" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/mailer" + notify_service "code.gitea.io/gitea/services/notify" "github.com/markbates/goth" ) @@ -586,6 +587,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth. } } + notify_service.NewUserSignUp(ctx, u) // update external user information if gothUser != nil { if err := externalaccount.UpdateExternalUser(u, *gothUser); err != nil { @@ -609,7 +611,6 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth. ctx.Data["Email"] = u.Email ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale) ctx.HTML(http.StatusOK, TplActivate) - if setting.CacheService.Enabled { if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { log.Error("Set cache(MailResendLimit) fail: %v", err) diff --git a/services/mailer/mail_admin_new_user.go b/services/mailer/mail_admin_new_user.go new file mode 100644 index 0000000000..47b110412c --- /dev/null +++ b/services/mailer/mail_admin_new_user.go @@ -0,0 +1,80 @@ +// Copyright 2023 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT +package mailer + +import ( + "bytes" + "context" + "strconv" + + 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/setting" + "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/translation" +) + +const ( + tplNewUserMail base.TplName = "notify/admin_new_user" +) + +var sa = SendAsyncs + +// MailNewUser sends notification emails on new user registrations to all admins +func MailNewUser(ctx context.Context, u *user_model.User) { + if !setting.Admin.SendNotificationEmailOnNewUser { + return + } + + if setting.MailService == nil { + // No mail service configured + return + } + + recipients, err := user_model.GetAllAdmins(ctx) + if err != nil { + log.Error("user_model.GetAllAdmins: %v", err) + return + } + + langMap := make(map[string][]string) + for _, r := range recipients { + langMap[r.Language] = append(langMap[r.Language], r.Email) + } + + for lang, tos := range langMap { + mailNewUser(ctx, u, lang, tos) + } +} + +func mailNewUser(ctx context.Context, u *user_model.User, lang string, tos []string) { + locale := translation.NewLocale(lang) + + subject := locale.Tr("mail.admin.new_user.subject", u.Name) + manageUserURL := setting.AppSubURL + "/admin/users/" + strconv.FormatInt(u.ID, 10) + body := locale.Tr("mail.admin.new_user.text", manageUserURL) + mailMeta := map[string]any{ + "NewUser": u, + "Subject": subject, + "Body": body, + "Language": locale.Language(), + "locale": locale, + "Str2html": templates.Str2html, + } + + var mailBody bytes.Buffer + + if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplNewUserMail), mailMeta); err != nil { + log.Error("ExecuteTemplate [%s]: %v", string(tplNewUserMail)+"/body", err) + return + } + + msgs := make([]*Message, 0, len(tos)) + for _, to := range tos { + msg := NewMessage(to, subject, mailBody.String()) + msg.Info = subject + msgs = append(msgs, msg) + } + sa(msgs) +} diff --git a/services/mailer/mail_admin_new_user_test.go b/services/mailer/mail_admin_new_user_test.go new file mode 100644 index 0000000000..4bb8d6a7a0 --- /dev/null +++ b/services/mailer/mail_admin_new_user_test.go @@ -0,0 +1,88 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package mailer + +import ( + "context" + "strconv" + "strings" + "testing" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" +) + +func getTestUsers() []*user_model.User { + admin := new(user_model.User) + admin.Name = "admin" + admin.IsAdmin = true + admin.Language = "en_US" + admin.Email = "admin@example.com" + + newUser := new(user_model.User) + newUser.Name = "new_user" + newUser.Language = "en_US" + newUser.IsAdmin = false + newUser.Email = "new_user@example.com" + newUser.LastLoginUnix = 1693648327 + newUser.CreatedUnix = 1693648027 + + user_model.CreateUser(db.DefaultContext, admin) + user_model.CreateUser(db.DefaultContext, newUser) + + users := make([]*user_model.User, 0) + users = append(users, admin) + users = append(users, newUser) + + return users +} + +func cleanUpUsers(ctx context.Context, users []*user_model.User) { + for _, u := range users { + db.DeleteByID(ctx, u.ID, new(user_model.User)) + } +} + +func TestAdminNotificationMail_test(t *testing.T) { + mailService := setting.Mailer{ + From: "test@example.com", + Protocol: "dummy", + } + + setting.MailService = &mailService + setting.Domain = "localhost" + setting.AppSubURL = "http://localhost" + + // test with SEND_NOTIFICATION_EMAIL_ON_NEW_USER enabled + setting.Admin.SendNotificationEmailOnNewUser = true + + ctx := context.Background() + NewContext(ctx) + + users := getTestUsers() + oldSendAsyncs := sa + defer func() { + sa = oldSendAsyncs + cleanUpUsers(ctx, users) + }() + + sa = func(msgs []*Message) { + assert.Equal(t, len(msgs), 1, "Test provides only one admin user, so only one email must be sent") + assert.Equal(t, msgs[0].To, users[0].Email, "checks if the recipient is the admin of the instance") + manageUserURL := "/admin/users/" + strconv.FormatInt(users[1].ID, 10) + assert.True(t, strings.ContainsAny(msgs[0].Body, manageUserURL), "checks if the message contains the link to manage the newly created user from the admin panel") + } + MailNewUser(ctx, users[1]) + + // test with SEND_NOTIFICATION_EMAIL_ON_NEW_USER disabled; emails shouldn't be sent + setting.Admin.SendNotificationEmailOnNewUser = false + sa = func(msgs []*Message) { + assert.Equal(t, 1, 0, "this shouldn't execute. MailNewUser must exit early since SEND_NOTIFICATION_EMAIL_ON_NEW_USER is disabled") + } + + MailNewUser(ctx, users[1]) +} diff --git a/services/mailer/notify.go b/services/mailer/notify.go index 9eaf268d0a..1577e824a5 100644 --- a/services/mailer/notify.go +++ b/services/mailer/notify.go @@ -202,3 +202,7 @@ func (m *mailNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner * log.Error("SendRepoTransferNotifyMail: %v", err) } } + +func (m *mailNotifier) NewUserSignUp(ctx context.Context, newUser *user_model.User) { + MailNewUser(ctx, newUser) +} diff --git a/services/notify/notifier.go b/services/notify/notifier.go index ed053a812a..3230a5e5f5 100644 --- a/services/notify/notifier.go +++ b/services/notify/notifier.go @@ -59,6 +59,8 @@ type Notifier interface { EditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page string) + NewUserSignUp(ctx context.Context, newUser *user_model.User) + NewRelease(ctx context.Context, rel *repo_model.Release) UpdateRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release) DeleteRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release) diff --git a/services/notify/notify.go b/services/notify/notify.go index 16fbb6325d..9cb329d302 100644 --- a/services/notify/notify.go +++ b/services/notify/notify.go @@ -347,6 +347,13 @@ func RepoPendingTransfer(ctx context.Context, doer, newOwner *user_model.User, r } } +// NewUserSignUp notifies about a newly signed up user to notifiers +func NewUserSignUp(ctx context.Context, newUser *user_model.User) { + for _, notifier := range notifiers { + notifier.NewUserSignUp(ctx, newUser) + } +} + // PackageCreate notifies creation of a package to notifiers func PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) { for _, notifier := range notifiers { diff --git a/services/notify/null.go b/services/notify/null.go index dddd421bef..894d118eac 100644 --- a/services/notify/null.go +++ b/services/notify/null.go @@ -197,6 +197,9 @@ func (*NullNotifier) SyncDeleteRef(ctx context.Context, doer *user_model.User, r func (*NullNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) { } +func (*NullNotifier) NewUserSignUp(ctx context.Context, newUser *user_model.User) { +} + // PackageCreate places a place holder function func (*NullNotifier) PackageCreate(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) { } diff --git a/templates/mail/notify/admin_new_user.tmpl b/templates/mail/notify/admin_new_user.tmpl new file mode 100644 index 0000000000..58b5c264e7 --- /dev/null +++ b/templates/mail/notify/admin_new_user.tmpl @@ -0,0 +1,22 @@ + + + + + {{.Subject}} + + + + + + +
    +

    {{.locale.Tr "mail.admin.new_user.user_info"}}

    +
  • {{.locale.Tr "admin.users.created"}}: {{DateTime "full" .NewUser.LastLoginUnix}}
  • +
  • {{.locale.Tr "admin.users.last_login"}}: {{DateTime "full" .NewUser.CreatedUnix}}
  • +
+

{{.Body | Str2html}}

+ + From f7f723628c76c5c2a0678139fbc4264feea352ea Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Mon, 11 Sep 2023 18:46:59 +0200 Subject: [PATCH 15/30] [GITEA] Improve HTML title on repositories (squash) do not double escape (cherry picked from commit 22882fe25cde57837a31738a10c71c9478e16662) (cherry picked from commit 63e99df3d1ecb50da3b723848ca85d56b831a8d7) (cherry picked from commit b65d777bc78fabf7e3d1bf8c50aff4eb5395d783) (cherry picked from commit 2961f4f6320b4b38c33f33e7133e7f3d3f86bd0f) --- routers/web/repo/view.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index a1a15c2919..353d511c55 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -165,7 +165,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { if ctx.Repo.TreePath != "" { ctx.Data["HideRepoInfo"] = true - ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+util.PathEscapeSegments(ctx.Repo.TreePath), ctx.Repo.RefName) + ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+ctx.Repo.TreePath, ctx.Repo.RefName) } subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true) @@ -344,7 +344,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } defer dataRc.Close() - ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+util.PathEscapeSegments(ctx.Repo.TreePath), ctx.Repo.RefName) + ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+ctx.Repo.TreePath, ctx.Repo.RefName) ctx.Data["FileIsSymlink"] = entry.IsLink() ctx.Data["FileName"] = blob.Name() ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) From 56b229f22e55f6e2f6cca8e596cc6d749026eceb Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Sun, 10 Sep 2023 16:28:52 +0200 Subject: [PATCH 16/30] [GITEA] enable system users search via the API Refs: https://codeberg.org/forgejo/forgejo/issues/1403 (cherry picked from commit 87bd40411e3af7eefce55e2a05475a8b366caa6f) Conflicts: routers/api/v1/user/user.go https://codeberg.org/forgejo/forgejo/pulls/1469 (cherry picked from commit 74f70ca873fa516e19eeb379dccd0f1fc8fba73b) (cherry picked from commit 673a75bb43fb2808e49df9cd1dc51c075ed1463c) (cherry picked from commit fcd4535ac66609d6e76079af4f97300d31554bee) --- models/user/user_system.go | 4 ++- routers/api/v1/user/user.go | 38 ++++++++++++++++------- tests/integration/api_user_search_test.go | 22 +++++++++++++ 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/models/user/user_system.go b/models/user/user_system.go index f54f4e3ffb..ecf235abc3 100644 --- a/models/user/user_system.go +++ b/models/user/user_system.go @@ -9,10 +9,12 @@ import ( "code.gitea.io/gitea/modules/structs" ) +const GhostUserID = -1 + // NewGhostUser creates and returns a fake user for someone has deleted their account. func NewGhostUser() *User { return &User{ - ID: -1, + ID: GhostUserID, Name: "Ghost", LowerName: "ghost", } diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index 6359138369..fb8f67d072 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -54,19 +54,33 @@ func Search(ctx *context.APIContext) { listOptions := utils.GetListOptions(ctx) - users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ - Actor: ctx.Doer, - Keyword: ctx.FormTrim("q"), - UID: ctx.FormInt64("uid"), - Type: user_model.UserTypeIndividual, - ListOptions: listOptions, - }) - if err != nil { - ctx.JSON(http.StatusInternalServerError, map[string]any{ - "ok": false, - "error": err.Error(), + uid := ctx.FormInt64("uid") + var users []*user_model.User + var maxResults int64 + var err error + + switch uid { + case user_model.GhostUserID: + maxResults = 1 + users = []*user_model.User{user_model.NewGhostUser()} + case user_model.ActionsUserID: + maxResults = 1 + users = []*user_model.User{user_model.NewActionsUser()} + default: + users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ + Actor: ctx.Doer, + Keyword: ctx.FormTrim("q"), + UID: uid, + Type: user_model.UserTypeIndividual, + ListOptions: listOptions, }) - return + if err != nil { + ctx.JSON(http.StatusInternalServerError, map[string]any{ + "ok": false, + "error": err.Error(), + }) + return + } } ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) diff --git a/tests/integration/api_user_search_test.go b/tests/integration/api_user_search_test.go index c5b202b319..ddfeb25234 100644 --- a/tests/integration/api_user_search_test.go +++ b/tests/integration/api_user_search_test.go @@ -56,6 +56,28 @@ func TestAPIUserSearchNotLoggedIn(t *testing.T) { } } +func TestAPIUserSearchSystemUsers(t *testing.T) { + defer tests.PrepareTestEnv(t)() + for _, systemUser := range []*user_model.User{ + user_model.NewGhostUser(), + user_model.NewActionsUser(), + } { + t.Run(systemUser.Name, func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/users/search?uid=%d", systemUser.ID) + resp := MakeRequest(t, req, http.StatusOK) + + var results SearchResults + DecodeJSON(t, resp, &results) + assert.NotEmpty(t, results.Data) + if assert.EqualValues(t, 1, len(results.Data)) { + user := results.Data[0] + assert.EqualValues(t, user.UserName, systemUser.Name) + assert.EqualValues(t, user.ID, systemUser.ID) + } + }) + } +} + func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" From f9a59b940a2ab2c9abac7c346da24b22d15017f1 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Tue, 12 Sep 2023 09:04:46 +0200 Subject: [PATCH 17/30] [GITEA] enable system users for comment.LoadPoster System users (Ghost, ActionsUser, etc) have a negative id and may be the author of a comment, either because it was created by a now deleted user or via an action using a transient token. The GetPossibleUserByID function has special cases related to system users and will not fail if given a negative id. Refs: https://codeberg.org/forgejo/forgejo/issues/1425 (cherry picked from commit 97667e06b384d834a04eaa05e8f91563481709b1) (cherry picked from commit 8ef73a09c9cb08e536aff7df8fc715c8d48c7c4f) (cherry picked from commit fa8a00d26420ef56497203a5c607b3d336feb813) (cherry picked from commit 2ada2074b56f3cd981dbacac0a7b1033ff486ffd) --- models/issues/comment.go | 2 +- tests/integration/api_comment_test.go | 37 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/models/issues/comment.go b/models/issues/comment.go index 1f0b28a050..043a263fe8 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -342,7 +342,7 @@ func (c *Comment) AfterLoad(session *xorm.Session) { // LoadPoster loads comment poster func (c *Comment) LoadPoster(ctx context.Context) (err error) { - if c.PosterID <= 0 || c.Poster != nil { + if c.Poster != nil { return nil } diff --git a/tests/integration/api_comment_test.go b/tests/integration/api_comment_test.go index 339ffdbe0e..eed496a9bf 100644 --- a/tests/integration/api_comment_test.go +++ b/tests/integration/api_comment_test.go @@ -189,6 +189,43 @@ func TestAPIGetComment(t *testing.T) { assert.Equal(t, expect.Created.Unix(), apiComment.Created.Unix()) } +func TestAPIGetSystemUserComment(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + for _, systemUser := range []*user_model.User{ + user_model.NewGhostUser(), + user_model.NewActionsUser(), + } { + body := fmt.Sprintf("Hello %s", systemUser.Name) + comment, err := issues_model.CreateComment(db.DefaultContext, &issues_model.CreateCommentOptions{ + Type: issues_model.CommentTypeComment, + Doer: systemUser, + Repo: repo, + Issue: issue, + Content: body, + }) + assert.NoError(t, err) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID) + resp := MakeRequest(t, req, http.StatusOK) + + var apiComment api.Comment + DecodeJSON(t, resp, &apiComment) + + if assert.NotNil(t, apiComment.Poster) { + if assert.Equal(t, systemUser.ID, apiComment.Poster.ID) { + assert.NoError(t, comment.LoadPoster(db.DefaultContext)) + assert.Equal(t, systemUser.Name, apiComment.Poster.UserName) + } + } + assert.Equal(t, body, apiComment.Body) + } +} + func TestAPIEditComment(t *testing.T) { defer tests.PrepareTestEnv(t)() const newCommentBody = "This is the new comment body" From c726aa5a5ca76a29431bef78b562c5daff87a255 Mon Sep 17 00:00:00 2001 From: Caesar Schinas Date: Tue, 12 Sep 2023 21:05:38 +0100 Subject: [PATCH 18/30] [GITEA] Add anchor to review types (squash) remove duplicate id Fixes https://codeberg.org/forgejo/forgejo/issues/1414 (cherry picked from commit 1f033198a68bd8bc93e5bb16013c387f447ea741) (cherry picked from commit 6243914c588e8ed3e824a6a83b1cfe85650f8898) (cherry picked from commit a714ef9d6d6564392bf97f71bec55e23e785092a) (cherry picked from commit 9b60ae026725cc7e317ec9efbbdb51808c9f4715) --- templates/repo/issue/view_content/comments.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 98e25529a7..baa3380c65 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -403,7 +403,7 @@
{{if or .Content .Attachments}} -
+
From 6b5e728f0aaa87e2711c1c2d2111446fc412e0ca Mon Sep 17 00:00:00 2001 From: Gusted Date: Wed, 13 Sep 2023 01:08:37 +0200 Subject: [PATCH 19/30] [GITEA] Tidy up archive modal - Make it consistent with the other modals of the dangerous actions. (cherry picked from commit 576d7ec759baefd2382d565212c3168e38bbdd75) (cherry picked from commit 8b1225f9742cc0d3942824895923cbc8e9d49d04) (cherry picked from commit c2c47972ee492686842b1623f9fe941a0e599f0a) (cherry picked from commit eec301806b925388585546edc6407e3f6d644f44) --- templates/repo/settings/options.tmpl | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 2a989d3bda..8d1e1bcaa4 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -976,20 +976,23 @@ {{end}}
-

+

{{if .Repository.IsArchived}} {{ctx.Locale.Tr "repo.settings.unarchive.text"}} {{else}} {{ctx.Locale.Tr "repo.settings.archive.text"}} {{end}} -

-
-
- {{.CsrfTokenHtml}} - - - {{template "base/modal_actions_confirm" .}} +
+ + {{.CsrfTokenHtml}} + + +
+ + +
+
{{end}} {{end}} From f5af5050b34891ff16a4ef1f8e3d805fe135238d Mon Sep 17 00:00:00 2001 From: Gusted Date: Wed, 13 Sep 2023 12:04:10 +0200 Subject: [PATCH 20/30] [GITEA] Use restricted sanitizer for repository description - Currently the repository description uses the same sanitizer as a normal markdown document. This means that element such as heading and images are allowed and can be abused. - Create a minimal restricted sanitizer for the repository description, which only allows what the postprocessor currently allows, which are links and emojis. - Added unit testing. - Resolves https://codeberg.org/forgejo/forgejo/issues/1202 - Resolves https://codeberg.org/Codeberg/Community/issues/1122 (cherry picked from commit a8afa4cd181d7c31f73d6a8fae4c6a4b9622a425) (cherry picked from commit 0238587c51e2c749413ca5a63e47590399fe5a2b) (cherry picked from commit a8c7bbf728326b992e000a3d19c8833610f960c9) (cherry picked from commit 80e05a8245092b4158c6c970ca0563181b40f2eb) --- models/repo/repo.go | 4 ++-- modules/markup/sanitizer.go | 35 +++++++++++++++++++++++++++++--- modules/markup/sanitizer_test.go | 22 ++++++++++++++++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/models/repo/repo.go b/models/repo/repo.go index 0b0c029993..8caf1e8cc0 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -584,9 +584,9 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML { }, repo.Description) if err != nil { log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err) - return template.HTML(markup.Sanitize(repo.Description)) + return template.HTML(markup.SanitizeDescription(repo.Description)) } - return template.HTML(markup.Sanitize(desc)) + return template.HTML(markup.SanitizeDescription(desc)) } // CloneLink represents different types of clone URLs of repository. diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go index 48c08831f1..992e85b989 100644 --- a/modules/markup/sanitizer.go +++ b/modules/markup/sanitizer.go @@ -18,9 +18,10 @@ import ( // Sanitizer is a protection wrapper of *bluemonday.Policy which does not allow // any modification to the underlying policies once it's been created. type Sanitizer struct { - defaultPolicy *bluemonday.Policy - rendererPolicies map[string]*bluemonday.Policy - init sync.Once + defaultPolicy *bluemonday.Policy + descriptionPolicy *bluemonday.Policy + rendererPolicies map[string]*bluemonday.Policy + init sync.Once } var ( @@ -41,6 +42,7 @@ func NewSanitizer() { func InitializeSanitizer() { sanitizer.rendererPolicies = map[string]*bluemonday.Policy{} sanitizer.defaultPolicy = createDefaultPolicy() + sanitizer.descriptionPolicy = createRepoDescriptionPolicy() for name, renderer := range renderers { sanitizerRules := renderer.SanitizerRules() @@ -161,6 +163,27 @@ func createDefaultPolicy() *bluemonday.Policy { return policy } +// createRepoDescriptionPolicy returns a minimal more strict policy that is used for +// repository descriptions. +func createRepoDescriptionPolicy() *bluemonday.Policy { + policy := bluemonday.NewPolicy() + + // Allow italics and bold. + policy.AllowElements("i", "b", "em", "strong") + + // Allow code. + policy.AllowElements("code") + + // Allow links + policy.AllowAttrs("href", "target", "rel").OnElements("a") + + // Allow classes for emojis + policy.AllowAttrs("class").Matching(regexp.MustCompile(`^emoji$`)).OnElements("img", "span") + policy.AllowAttrs("aria-label").OnElements("span") + + return policy +} + func addSanitizerRules(policy *bluemonday.Policy, rules []setting.MarkupSanitizerRule) { for _, rule := range rules { if rule.AllowDataURIImages { @@ -176,6 +199,12 @@ func addSanitizerRules(policy *bluemonday.Policy, rules []setting.MarkupSanitize } } +// SanitizeDescription sanitizes the HTML generated for a repository description. +func SanitizeDescription(s string) string { + NewSanitizer() + return sanitizer.descriptionPolicy.Sanitize(s) +} + // Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist. func Sanitize(s string) string { NewSanitizer() diff --git a/modules/markup/sanitizer_test.go b/modules/markup/sanitizer_test.go index 0bc63ff0a7..b7b8792bd7 100644 --- a/modules/markup/sanitizer_test.go +++ b/modules/markup/sanitizer_test.go @@ -73,6 +73,28 @@ func Test_Sanitizer(t *testing.T) { } } +func TestDescriptionSanitizer(t *testing.T) { + NewSanitizer() + + testCases := []string{ + `

Title

`, `Title`, + `image`, ``, + `THUMBS UP`, `THUMBS UP`, + `Hello World`, `Hello World`, + `
`, ``, + `https://example.com`, `https://example.com`, + `Important!`, `Important!`, + `
Click me! Nothing to see here.
`, `Click me! Nothing to see here.`, + ``, ``, + `I have a strong opinion about this.`, `I have a strong opinion about this.`, + `Provides alternative wg(8) tool`, `Provides alternative wg(8) tool`, + } + + for i := 0; i < len(testCases); i += 2 { + assert.Equal(t, testCases[i+1], SanitizeDescription(testCases[i])) + } +} + func TestSanitizeNonEscape(t *testing.T) { descStr := "<script>alert(document.domain)</script>" From 9d40b409d715d336f68ec6306d8a928c171c85fc Mon Sep 17 00:00:00 2001 From: Gusted Date: Wed, 13 Sep 2023 21:36:21 +0200 Subject: [PATCH 21/30] [GITEA] Fix issue card links on projects - Don't expect that rendering is done on a repository, use the given issue to figure out the repository link. - Resolves https://codeberg.org/forgejo/forgejo/issues/1321 (cherry picked from commit 63f16652ca21809d2e088c46ddb88b84c08a5ad6) (cherry picked from commit 821785d0af2fc2a41997675fc2809e1e45c0cdd8) (cherry picked from commit 345742a0dc2b505a5bdc0747d4e2cda7136e735d) (cherry picked from commit 2a37b91d7ced112b06d04950a9970cbd2d52f0f2) --- templates/repo/issue/card.tmpl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/repo/issue/card.tmpl b/templates/repo/issue/card.tmpl index 05b7dbaabc..8e46b2da0e 100644 --- a/templates/repo/issue/card.tmpl +++ b/templates/repo/issue/card.tmpl @@ -33,7 +33,7 @@
{{if .MilestoneID}}
- + {{svg "octicon-milestone" 16 "gt-mr-2 gt-vm"}} {{.Milestone.Name}} @@ -42,7 +42,7 @@ {{if $.Page.LinkedPRs}} {{range index $.Page.LinkedPRs .ID}}
- + {{svg "octicon-git-merge" 16 "gt-mr-2 gt-vm"}} {{.Title}} #{{.Index}} @@ -54,7 +54,7 @@ {{if or .Labels .Assignees}}
{{range .Labels}} - {{RenderLabel ctx .}} + {{RenderLabel ctx .}} {{end}}
{{range .Assignees}} From 6427fa65b641a32ead53779e3e7bda97704567df Mon Sep 17 00:00:00 2001 From: Gusted Date: Sun, 17 Sep 2023 20:25:30 +0200 Subject: [PATCH 22/30] [GITEA] Skip unsupported code comment - If there's a code comment that's received during the migration that contains no diffhunk, skip it. This either means it was commenting on old diffhunk or it's just a general codecomment. Forgejo supports neither of such type of code comment. - Resolves https://codeberg.org/forgejo/forgejo/issues/1407 (cherry picked from commit ae463c7c559e02975ce5e758d8780def978eebee) (cherry picked from commit bf48f02a86d6a193417f13a77031b8207a173dca) (cherry picked from commit 10c3f102fa9135de37e9f73137ae5a9cf7072635) (cherry picked from commit 828b4cc10cd0fc7e2540fe75e88b6ebf978c5c84) --- services/migrations/gitea_uploader.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index ebf22e0e40..de262d431c 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -859,6 +859,11 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { } for _, comment := range review.Comments { + // Skip code comment if it doesn't have a diff it is commeting on. + if comment.DiffHunk == "" { + continue + } + line := comment.Line if line != 0 { comment.Position = 1 From f3be6eb269526a9f4ea7861189f07977f2d4a32f Mon Sep 17 00:00:00 2001 From: Gusted Date: Thu, 14 Sep 2023 21:53:57 +0200 Subject: [PATCH 23/30] [GITEA] Detect file rename and show in history - Add a indication to the file history if the file has been renamed, this indication contains a link to browse the history of the file further. - Added unit testing. - Added integration testing. - Resolves https://codeberg.org/forgejo/forgejo/issues/1279 (cherry picked from commit 72c297521b1830360aab4b50e37efcc7e67e0d5d) (cherry picked from commit 283f9648947f8dd2f315ecca19566ccca2b49c18) Conflicts: options/locale/locale_en-US.ini https://codeberg.org/forgejo/forgejo/pulls/1550 (cherry picked from commit 7c30af7fdee08efd02041c01abca47394a69bb8b) --- modules/git/commit.go | 56 ++++++++++++++++++ modules/git/commit_test.go | 27 +++++++++ options/locale/locale_en-US.ini | 2 + routers/web/repo/commit.go | 16 +++++ templates/repo/commits.tmpl | 5 ++ .../40/8bbd3bd1f96950f8cf2f98c479557f6b18817a | Bin 63 -> 0 bytes .../5d/5c87a90af64cc67f22d60a942d5efaef8bc96b | Bin 50 -> 0 bytes .../88/3e2970ed6937cbb63311e941adb97df0ae3a52 | Bin 49 -> 0 bytes .../8c/ac7a8f434451410cc91ab9c04d07baff974ad8 | Bin 120 -> 0 bytes .../a0/ccafed39086ef520be6886d9395eb2100d317e | Bin 102 -> 0 bytes .../ab/e2a9ddfd7f542ff89bc13960a929dc8ca86c99 | Bin 36 -> 0 bytes .../cd/879fb6cf5b7bbe0fbc3a0ef44c8695fde89a56 | Bin 108 -> 0 bytes .../d8/f53dfb33f6ccf4169c34970b5e747511c18beb | Bin 784 -> 0 bytes .../f3/c1ec36c0e7605be54e71f24035caa675b7ba41 | Bin 48 -> 0 bytes .../repo59.git/objects/info/commit-graph | Bin 0 -> 1292 bytes .../user2/repo59.git/objects/info/packs | 2 + ...d3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.idx | Bin 0 -> 1660 bytes ...3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.pack | Bin 0 -> 6316 bytes ...d3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.rev | Bin 0 -> 136 bytes .../user2/repo59.git/packed-refs | 3 +- .../user2/repo59.git/refs/heads/cake-recipe | 1 - tests/integration/repo_test.go | 30 ++++++++++ 22 files changed, 140 insertions(+), 2 deletions(-) delete mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/40/8bbd3bd1f96950f8cf2f98c479557f6b18817a delete mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/5d/5c87a90af64cc67f22d60a942d5efaef8bc96b delete mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/88/3e2970ed6937cbb63311e941adb97df0ae3a52 delete mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/8c/ac7a8f434451410cc91ab9c04d07baff974ad8 delete mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/a0/ccafed39086ef520be6886d9395eb2100d317e delete mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/ab/e2a9ddfd7f542ff89bc13960a929dc8ca86c99 delete mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/cd/879fb6cf5b7bbe0fbc3a0ef44c8695fde89a56 delete mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/d8/f53dfb33f6ccf4169c34970b5e747511c18beb delete mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/f3/c1ec36c0e7605be54e71f24035caa675b7ba41 create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/info/commit-graph create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/info/packs create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.idx create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.pack create mode 100644 tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.rev delete mode 100644 tests/gitea-repositories-meta/user2/repo59.git/refs/heads/cake-recipe diff --git a/modules/git/commit.go b/modules/git/commit.go index b09be25ba0..bc22d52b45 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -509,6 +509,62 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi return fileStatus, nil } +func parseCommitRenames(renames *[][2]string, stdout io.Reader) { + rd := bufio.NewReader(stdout) + for { + // Skip (R || three digits || NULL byte) + _, err := rd.Discard(5) + if err != nil { + if err != io.EOF { + log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err) + } + return + } + oldFileName, err := rd.ReadString('\x00') + if err != nil { + if err != io.EOF { + log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err) + } + return + } + newFileName, err := rd.ReadString('\x00') + if err != nil { + if err != io.EOF { + log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err) + } + return + } + oldFileName = strings.TrimSuffix(oldFileName, "\x00") + newFileName = strings.TrimSuffix(newFileName, "\x00") + *renames = append(*renames, [2]string{oldFileName, newFileName}) + } +} + +// GetCommitFileRenames returns the renames that the commit contains. +func GetCommitFileRenames(ctx context.Context, repoPath, commitID string) ([][2]string, error) { + renames := [][2]string{} + stdout, w := io.Pipe() + done := make(chan struct{}) + go func() { + parseCommitRenames(&renames, stdout) + close(done) + }() + + stderr := new(bytes.Buffer) + err := NewCommand(ctx, "show", "--name-status", "--pretty=format:", "-z", "--diff-filter=R").AddDynamicArguments(commitID).Run(&RunOpts{ + Dir: repoPath, + Stdout: w, + Stderr: stderr, + }) + w.Close() // Close writer to exit parsing goroutine + if err != nil { + return nil, ConcatenateError(err, stderr.String()) + } + + <-done + return renames, nil +} + // GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository. func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) { commitID, _, err := NewCommand(ctx, "rev-parse").AddDynamicArguments(shortID).RunStdString(&RunOpts{Dir: repoPath}) diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go index ac586fdf09..a095ac9844 100644 --- a/modules/git/commit_test.go +++ b/modules/git/commit_test.go @@ -278,3 +278,30 @@ func TestGetCommitFileStatusMerges(t *testing.T) { assert.Equal(t, commitFileStatus.Removed, expected.Removed) assert.Equal(t, commitFileStatus.Modified, expected.Modified) } + +func TestParseCommitRenames(t *testing.T) { + testcases := []struct { + output string + renames [][2]string + }{ + { + output: "R090\x00renamed.txt\x00history.txt\x00", + renames: [][2]string{{"renamed.txt", "history.txt"}}, + }, + { + output: "R090\x00renamed.txt\x00history.txt\x00R000\x00corruptedstdouthere", + renames: [][2]string{{"renamed.txt", "history.txt"}}, + }, + { + output: "R100\x00renamed.txt\x00history.txt\x00R001\x00readme.md\x00README.md\x00", + renames: [][2]string{{"renamed.txt", "history.txt"}, {"readme.md", "README.md"}}, + }, + } + + for _, testcase := range testcases { + renames := [][2]string{} + parseCommitRenames(&renames, strings.NewReader(testcase.output)) + + assert.Equal(t, testcase.renames, renames) + } +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 9f3124ac87..15ea2ffed3 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1283,6 +1283,8 @@ commits.find = Search commits.search_all = All Branches commits.author = Author commits.message = Message +commits.browse_further = Browse further +commits.renamed_from = Renamed from %s commits.date = Date commits.older = Older commits.newer = Newer diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 3587d287fc..f8508dbf68 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -239,6 +239,22 @@ func FileHistory(ctx *context.Context) { ctx.ServerError("CommitsByFileAndRange", err) return } + oldestCommit := commits[len(commits)-1] + + renamedFiles, err := git.GetCommitFileRenames(ctx, ctx.Repo.GitRepo.Path, oldestCommit.ID.String()) + if err != nil { + ctx.ServerError("GetCommitFileRenames", err) + return + } + + for _, renames := range renamedFiles { + if renames[1] == fileName { + ctx.Data["OldFilename"] = renames[0] + ctx.Data["OldFilenameHistory"] = fmt.Sprintf("%s/commits/commit/%s/%s", ctx.Repo.RepoLink, oldestCommit.ID.String(), renames[0]) + break + } + } + ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository) ctx.Data["Username"] = ctx.Repo.Owner.Name diff --git a/templates/repo/commits.tmpl b/templates/repo/commits.tmpl index 42004c2610..b3d983029b 100644 --- a/templates/repo/commits.tmpl +++ b/templates/repo/commits.tmpl @@ -13,6 +13,11 @@
{{template "repo/commits_table" .}} + {{if .OldFilename}} +
+ {{.locale.Tr "repo.commits.renamed_from" .OldFilename}} ({{.locale.Tr "repo.commits.browse_further"}}) +
+ {{end}}
{{template "base/footer" .}} diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/40/8bbd3bd1f96950f8cf2f98c479557f6b18817a b/tests/gitea-repositories-meta/user2/repo59.git/objects/40/8bbd3bd1f96950f8cf2f98c479557f6b18817a deleted file mode 100644 index 567284ef1c21f472841f3d729cbfe024d78c448c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63 zcmV-F0Korv0ZYosPf{>3XHZrMN-fAQ&Me6)6NNXyJgE!N}W0ssfX5``$?8bAO5 diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/5d/5c87a90af64cc67f22d60a942d5efaef8bc96b b/tests/gitea-repositories-meta/user2/repo59.git/objects/5d/5c87a90af64cc67f22d60a942d5efaef8bc96b deleted file mode 100644 index f23960f4cc8c09af29fc4765f683b697a4547d74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50 zcmV-20L}k+0V^p=O;s>9VK6ZO0)@QP;*!j~bcW9d-8WL<+jltv I07;Az9utceGXMYp diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/88/3e2970ed6937cbb63311e941adb97df0ae3a52 b/tests/gitea-repositories-meta/user2/repo59.git/objects/88/3e2970ed6937cbb63311e941adb97df0ae3a52 deleted file mode 100644 index 46cc9e3e5ec14999b5424f35c1c548db9e9e33c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49 zcmb7FlR6{FfcPQQ3!H%bn$i7%S~Z$=-z96@n>ehkMsI7j#P%$ zXG=6znHT_pLP~0C0Yhv|`%12FKF8{nu5nG#jr;Y!`(!rMjI`9mlG377y^@L&h7LQ; ag14FGr?(jkzI0r>v-ZO}s~`Z9QY~(zy*Y~j diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/a0/ccafed39086ef520be6886d9395eb2100d317e b/tests/gitea-repositories-meta/user2/repo59.git/objects/a0/ccafed39086ef520be6886d9395eb2100d317e deleted file mode 100644 index 3b71228a7ec7b91f1066cda387ef6efa28c2ae68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102 zcmV-s0Ga=I0V^p=O;xb4U@$Z=Ff%bx2y%6F@paY9O<`F5Xyx6%^&$E{W*@XnSgCoZ zXGP9TsG{Q3M6>2AdT%A&7_dRgi_+j`HiK;pmmM?MU`7Mx>)%bH?6OHM zk5sBnouqA=Y63LcbH7YOmH|GAl4Hc@EW@%K&C)1I1Uia^1hFYP#!;RNM>a}%Dtb?4 zJAn6?4K(;YO4A`5NBYlfjhe2`eoNZs4?rJ;J;Mr|fWQvz5u(27_uQ2I-(JxbV^x4( z|B6Ty%>s_%fUBlh_~u>6-<$#zs9bFmF%~6^Q@J9f=}|(LabtH3q_NxV$YbR02+h-Wy>8SN zL-n>NnOHxD1_ktBKMTFMF~Oeo44c$Nx1zjPg-n1^s{~0y49o8@?{IqT_1l9FDBH+tz~X}{gyB7!qm$KrCW*^)AW)NzRtpG4}}%%=|SNs z@Nz?3mnX@zAyYKZV*tH#nR{bxS}d9i?M(85HRz`|j6_MUVShjg2W|Ppe8h&7*MdL7JHfOBCJr|RNl?>o$nk$Jg%Kk7Ay z>ilZ^!I!SZjq{9}32tH%t54`QEQTUns*)fL4hA0LId@WGsHUmmoZfJs-`Nc O!MB&yMEwL3??r>^yMtc< diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/f3/c1ec36c0e7605be54e71f24035caa675b7ba41 b/tests/gitea-repositories-meta/user2/repo59.git/objects/f3/c1ec36c0e7605be54e71f24035caa675b7ba41 deleted file mode 100644 index b6803eb5a271b9d33c981d1ba24fa4126ae409db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48 zcmV-00MGw;0V^p=O;s>9W-u`T0)@2voRrieh6QKVzqRDZ`>L=nqwS_;+$I5D!#V&X Gkq<4~#T1zU diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/info/commit-graph b/tests/gitea-repositories-meta/user2/repo59.git/objects/info/commit-graph new file mode 100644 index 0000000000000000000000000000000000000000..d151dc87e63b6d2485029740163c1cd02c7d41d0 GIT binary patch literal 1292 zcmZ>E5Aa}QWMT04ba7*V02d(J2f}1=advSGfwCLiT^x;|>^Be^M6&!quu)iyK;9@G z8DYQ#jO$TMyD(q|hVP)otZOq~hxjx+n&edca;Jo0TTt~>qlO(eF-;1GS6Q+>X#B*q zXnC03iOL&aZGRhoJM%?sj>&ZHxRO%AgWaz~!hS7&%G9F9;IU;%)q2U6wRV$c763g9 z!a%^1%EG==%(L8v{Wo8~;ety&&fTp1sip^yfh3`7IDl#{^{lDtcXkPMdd1A3JP%SxF1gMWxEDF>^5@ubS z@jAq(;n5_g;+H!m4BLXLry7Yw@3bh^P7CGT=PJLCyJX2r^^lKZp5->|zxnzN7hLLb z?q=mrH9dIDp?k0O#h;l0KhEpVI8qr}pDoc?6%zJq@l&Q2H3p9@ORCmOwyd?AG&44) zeI?g7pJVk(*SMzW#{GKVeKNaYhfPeA!r@hxY!4bgF)dmiW_O~p!%nl{ZKnC@ZN`Ex z9oO!x{jkm|sPmShlc&wf&DW9@7c-SjT=qEAS+QqLRll=Kpd-&oshtOW*?0Y)?sa3z z){=*9A_nY|rc-k_tj+IZ`MzT3?9&@1I_wNg%64-KT|0ErZ=PcE;{|8dzqRDZ`>L=n zqwS_;+$I5D!@31$WhcDQ-+Jo{Z&GE)mY-AX4Z3O;t$QeOsNH3$wQk+RLfb;;fROw< zs~@er`?o$s|HtfumI*60@ARz5nR%4w?APQj#nq;J zp5Td}556%w@H`>Oj{w5~;Pn@?d+qGzQ z=#$(_u|4p16j!cPuI0Wu*(=7fk+Mr)dWTGX;HzJL)F&)0 zuZ%(S$D#N-VDW4Y%!)u-ES-Ttd>RnD0onb)^aISF5`I8j2^4DsCZ~(Qthp2@_Y}x4 z0v22DK>9qeNC*MaZ-Lq1Bv36-pV;o)%gg==_sf<)^wG=i%W-_VPDShf{Yw4SqES8S R@AzV-R-~x4va}Sh1OP;7B9#CD literal 0 HcmV?d00001 diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.pack b/tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.pack new file mode 100644 index 0000000000000000000000000000000000000000..ddb8c16caf1f0600bf22af94b7f41c3799679617 GIT binary patch literal 6316 zcmZu$RZtvYlUzK&;vRwqcXtWy?yz`Zad!w7++iVPaa|mO2Z!LYkl;>m4-UaE|6ScZ z-NW}VU-eW?Pu0|P*Jw(~r~m)}#D9+{N7^qZ1EN${B~KV}_k?lFXAlW|LxZ~U02~_Md>gA>-Ih>#u8Yz9iy<`p*MwOlPzs2TJS4S}$`heU< zcWE^S!=GCVycD1Q&FJ#UV!9BWJ0Fq|pd81!(w**g!N6-!ljzzt#IcSXrq&Uq zf(El>-5gwa-6T}|l>v4Mlv}EbOQT>hbv2{5QCStD=&=^8SsY|gUkG1tyf5G*`IKxW z$DX$yl2de(b#cL1G-Y1@&Fr&06S-9hC*hvN8B;6cdFIfmlGX)Y*%d@kdd}bZG_Ba8{?TVbDcsYs3?X;#Opo|`K7?;xlRTrynb<0*;BCT zX=z{I+9bzYrW0uY=C7N}zBg?f9c?$1YHuaj8u!!L^C^*c#+`qDHMw1yHAeCxbwYYJ*%rN$?ku7X!1I-Zv)X-4SeBDL6UQf zbja_Mo@OL(30U8_I7Fz+%E%p55UqfY*J&dfz`V8ic1KiZ%Y0Bp=?u$WCQ0AP^_RWN zCDBPRgY~ucI|E^0_olk~v7AK9i2YV zMzi~N;-bwFOeOnrV|HA+X$o-Q0e;L38JfVuJX|vAeh1cF%~_dpu6TJ-%iTp@Tn|7& zAWGASR{Sk@Rf57PIeNk}kG#t3EqCcfp>MX7ejr4*(7qi(YB9U6%fo^PB?1%$g)A5T zumxsAcP)c61UoVl4)y8%Q+{QPs_}D2qwZpj{vRy(EG3;bZsYn-4XjH3l45jnw0%Bl8?;^aj5iUvwHJd8RoGJ!jNC%+9o-6CA$9j2!H#(SNg9hW<4vf79H4#EWm5BcO{=LC(S(rOfAN)o-`ja&*tNars8&a%btD?Fs)f+kzEMe2$bz0t??pUdya(Ck05p#e!RaW(zdPKw0 zb9*K^nqf-c?%t*4P|I4CvT0|VzY7Uq(;a2rry+)S%z%xM$g-Ymp`tjdCfPj(W=cfCbsL=g0#)`2@{~{@vsI2)_cFj z0Q!)@JD-G)nu{~rv}faLpcb%;e$@-eX)>Le0~2;iTr;-l&|wQ>ZWic_>_QC@OZdUH zK$>lsK-`=8NACAVdY`P_=*`mJnAqt+>pd2_V1s5@j3iiFBr?5TPtV4j~X5T7m%nxI)XmO(hl2#TUxF~;Ft{9?t>n@l&gZ)Fs-5$`*l z=zQTQ;MIc=PNWz|Op1WUq`oC|sV4fpcJTGpE{Pr`?eD%p;v6FD`FK7=SFJUySh#nF z-{ksp#{-E10I{GdKaEMbfoYGVymBo9ESu}on))L~r{Y^#o^5`dw#f^vPZ&%6XC{Nh zM^1&%WsE$X_$@=Moq*g;h1CNck(NOJ3)<81`jb1+pR9znr{pu-y3Cr8cfjFD?caX2 z(V^5{(bppFY?hsOZ;3$37kU~d5`isZMVt+IP$eAmtKTTF(*0b^@}IRC!}gDH02Ois z%DRMn&ZnWCJ@v>=NNz3B_>7baE=KwNVTSlbQdzAoNS8kO)B3_}<7Cw5s$nWP5`seA zdS+dmU$o)72J9=KHbj^sjwlD+zVuvf>?KBR^mJTi2A;)OX~C<#^g~vaE?^&5Q;qo{ zAQTA}X^DjP30zP#kZ)<>?scIiA3`iE`8I@Ib2(0QwpWjhqP!<lk8R^VV!y#7)qYXvJoHRx3u4#Jq zEc8EODZAy777*7+&l`Zc`V`lBTH|BqWZzTHd>=a9phw3{{m#B2uzz>6bgcT^UYEv_ z5OEJPd|fHg@za2DQ8Zo8@N3?0rd>S)W6LiIp3_`ig8YvhV*v|J$&J|Jh-N!ckOwS$ zmR*{V*PxaN1|g%ymOti__y~RWOMp2RiwzbZjnlu-Bq7iFVV6M zmk-i@ZB?XOQ8pm|#)crR7Y7POAC{< z-mro+rT(z~Rzxz?9p|0A3PYiGtA0i@aL~M_aqF5B3}9L63-EN)K{pKau`9_w*>soB zKTUxLPMRf8>9C9pc^CcsoRgTi{N#tqe7O1cOF9CQD>mLWRco!;q>1Ol^)Ml$gAPZ2 zkWOuKiPW^NG7j>t3;K;q`LXJ-`$7t8oGOj`N6-Xe7UtC$s z;g4UY=%_yE=XhBvjE~bB-_5b~ER)tvA(^YdKyb`&7Co}PWt>(0*~wQ6ue^UN3Dh-| zV-shS9OrBuIJMLlNqUC|j6>@P-ko3MwNNeo7~lYw9Wy9x7SccF3X3mY7VxLAWc?;{ z+~L)ZK78V!ufDAGCpLf)@3hX8jScv1lQ3pkPu%ZXs7f5^iW2N2GU%^aq5-Nr7M@kxEMa` z?-mlgNs(TNcDtBBVDW^la8occJC+Q^*z77808z3g*{rF-V`jtX!0fd*iQF z`}S2b387T{;u#Y>co>vTql{HUZQyO(^`V2f#g#a*GpG1USis|gM|x?zTWP4d>P&>0 zq)eK0r1jf)x=Ii)lo{qWm6Xmw13m1PWD}d^!_NDvQ_UN79v)F@|Cq=#g|CWr0QZdp zare&u+3SLf`Ka?Ab-0m5$5t(XmnN)@`TG-2@J-3aHxy0dz~+r!9LjWQMlCMQ3fM^R zfw$l`hxxZ^tKID&=ikAm-|UU zUgAtr17$zjR+c$45E{22zZTQ+?I=;S2=t(=^OaKt1YTu}2x@TGP01xgLP}9rzNQ^P zUrL?fwMwAkyt4dyG?<8C~W+V%89FXP@#*j?b*KN>oGg z4Qyp;bapH*TFY%6mNb9sgT57A93W^eY-WY-3^5jwq4?5L`u4t&$=Cd&c-uhDB885n z?cqBvqw`O$b013=bguP_ZfJle{RDWGFn@#5BRrG2G>mN_mqy3-f}_6oEWSFhbL^fNWWWL-T3<}`Bdl~DmF78 zvgv4rMvQpyki<$CjHSaNX*3>5TXIs0P;El8_7ZfOO(Jnd{8z#Unx&xPz=)Z zPhcgRUc4uNkVF`bC4|RRD~2LT;UVA31%786ME2t2l*0~A>hHWBHOLP(*fj~|hHDyi+y2-W%ApPX^HA?#^m*#<0>F`R+)1dx% zrMSHlTz%dF{inM0MWrAQiWve5+LiN<$<`lE-3r&-#Q10_Z`%t+4~yv(_S}HX)?l8% zj9Q-Agfeg3Ybd4n=H>(${2z4m00PiBN`J1+S?T4QW_7T zvLrLplLkxv8!}W-()K$zRth8M0sgF_L@GNJS>aZ<#@2fe^61oCa9ZckTNfDPMKCzD zxw9cWuHTP})V9E!!D3|OWZdFZ<$v*ZjoDFTOqow_PI3!R+%Nl@q5n?WEIB zoGU>CRRYy+&M0+ku^F7_XuE~lE(AjrgYExy(qD4;?pyUkQp-_+q|HT$(x!*&95diJ z{CPX!i1Kxc6ezKY2*D15qLZ9(V(Q33|KmXZiLU8Nd4T+#nA}-S2{}S!<*JGOH>iy! zUa$`kUQnP)dwG2i(e?3#xcXOiuL;k5V8(5vTVb^7^AzL!CKlhIY6a09!_6=LltmcX zv=0G&Ok2~Ie*c&dHCPsB2)DSXFZ{HF1k|(~BAG)Swl9Y*z9)`6{I}kL}lE?56 z5@KVJSMS;wuv}VOU#T1AB ziD&vm$D;&KZ`BNA`00nT|K000kQz%&L?WH<_}U}EFdmr*)iceiJL6=);G0KZ^cKZ3 z4*sn=kyr6W%e6%pBo;@ZQ@P?;W3=&w#|Un2j^tiw;-Jz0F;Ixk$J*>^$@A~B8JMsb zAxRA2HJAS@6f7%PiHzDAKWMVAcnXhErMRnKNvvS7tDYTq>S1JcDu0)%W1x5#p67i) zl^g(Ew|}mwrceu%65L;7_m;r=-=c<&_M(cu-u1$+GQSQ4kytydov=V_3_0 zwa}}GUDS>ii844L`>0@%V`uEPJK4gmI?lq$DO$nP{+*K6C2=55RI1&Q@E>0$f`Tf_ z@5!qTwiAHY3Q1z-+M`6NIf=yIL&lqy9zmkUPnSC!Ih(PCgB+iqW87$JE_L(89@?eb zHBM#@SIURq9bauP;S_psa7tV7gS=83btS7Dn|@!|;}Wc;pIQjxY@{+RJpU+j*C<1TYS6jsvFSW}C3hoG%0TWcpm7CzFG+g-{)3&xi*uAv zv^i1#Y;r&)1U1OOKx4w3Q&1Ye0Mjprmye5!ms{(Nhqu>-_uROerQ5-}$It$Xd4<>b+M z*T@>OOns$@0J;hlqGNoNUQ?xa^Q#70QcWYpJXZCN=?9N2^|8$`7^r|&CxpsZHXE^RGUSnmphtoC^0f6;| ze)S9?%=^FTjSCg(($)!O6#oAd``!Ry_Te=D3-#Ipq66^ykfLnD`Tz6xRh)(#fGUSb z+VhW^j{X~E_7ToWwo%SXU8X6vQB6f%#Zgri6?SDftJcWmgbK6%h=z{h_=FPs{Pq?i zAY&WXqzU1_6Gs*GnAFucl-aeI)Y;V3^>z>e(tD`jPE@}finwvCP-$0&HRn z3PZqFxhzV5ErSushMp=!1xvBqxe7MsH!@gRb6jnLS=j(5kT`BIg86L2kGA_0)XH=1A8GzrtsZV P<#0@LlG?4MW7+sGiQYRB literal 0 HcmV?d00001 diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.rev b/tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.rev new file mode 100644 index 0000000000000000000000000000000000000000..81554dba7405b86a5e8db6c52a81b9c6590b0f0e GIT binary patch literal 136 zcmWIYbctYKU|@t|HXscGd_XJ!#2|5QAQl2*E+A$CVpbp)0b(8?W&vVeAm#*OW+3JV zVnHBg2VxE&7S6rA?4NMIZ23bUz3jdm$EWL5wC;O}2sbdA%~jT1wJNl|W?lK0SNV4U DT$U8_ literal 0 HcmV?d00001 diff --git a/tests/gitea-repositories-meta/user2/repo59.git/packed-refs b/tests/gitea-repositories-meta/user2/repo59.git/packed-refs index 114c84d2aa..77fedbf67d 100644 --- a/tests/gitea-repositories-meta/user2/repo59.git/packed-refs +++ b/tests/gitea-repositories-meta/user2/repo59.git/packed-refs @@ -1,3 +1,4 @@ # pack-refs with: peeled fully-peeled sorted -d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/heads/master +d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/heads/cake-recipe +80b83c5c8220c3aa3906e081f202a2a7563ec879 refs/heads/master d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/tags/v1.0 diff --git a/tests/gitea-repositories-meta/user2/repo59.git/refs/heads/cake-recipe b/tests/gitea-repositories-meta/user2/repo59.git/refs/heads/cake-recipe deleted file mode 100644 index 63bbea6692..0000000000 --- a/tests/gitea-repositories-meta/user2/repo59.git/refs/heads/cake-recipe +++ /dev/null @@ -1 +0,0 @@ -d8f53dfb33f6ccf4169c34970b5e747511c18beb diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index d044174df1..2ac1632188 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -690,3 +690,33 @@ func TestDangerZoneConfirmation(t *testing.T) { }) }) } + +func TestRenamedFileHistory(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + t.Run("Renamed file", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo59/commits/branch/master/license") + resp := MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + renameNotice := htmlDoc.doc.Find(".ui.bottom.attached.header") + assert.Equal(t, 1, renameNotice.Length()) + assert.Contains(t, renameNotice.Text(), "Renamed from licnse (Browse further)") + + oldFileHistoryLink, ok := renameNotice.Find("a").Attr("href") + assert.True(t, ok) + assert.Equal(t, "/user2/repo59/commits/commit/80b83c5c8220c3aa3906e081f202a2a7563ec879/licnse", oldFileHistoryLink) + }) + + t.Run("Non renamed file", func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo59/commits/branch/master/README.md") + resp := MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + htmlDoc.AssertElement(t, ".ui.bottom.attached.header", false) + }) +} From 4e58ab82b77a8f4e6f994fc21b42fb70f0629778 Mon Sep 17 00:00:00 2001 From: rome-user Date: Sat, 30 Sep 2023 18:03:22 -0700 Subject: [PATCH 24/30] [GITEA] fix indentation in Maven package install instructions The installation instructions of a Maven package places the `url` child of the `repository` node in an extra indentation level. This indentation is unnecesary since both the `id` and `url` nodes are direct children of the `repository` node. This commit removes the unnecessary indentation. Refs: https://codeberg.org/forgejo/forgejo/pulls/1534 (cherry picked from commit 82f0ddad7bfcb40595d0f79220834377b04382d8) (cherry picked from commit 905e546549bc69460d93f6e30bbe93124e924e57) --- templates/package/content/maven.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/package/content/maven.tmpl b/templates/package/content/maven.tmpl index b2cd567e16..100c12e180 100644 --- a/templates/package/content/maven.tmpl +++ b/templates/package/content/maven.tmpl @@ -7,7 +7,7 @@
<repositories>
 	<repository>
 		<id>gitea</id>
-			<url></url>
+		<url></url>
 	</repository>
 </repositories>
 

From 325d92917f655c999b81b08832ee623d6b669f0f Mon Sep 17 00:00:00 2001
From: Gusted 
Date: Sat, 30 Sep 2023 00:45:31 +0200
Subject: [PATCH 25/30] [GITEA] Drop sha256-simd in favor of stdlib
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- In Go 1.21 the crypto/sha256 [got a massive
improvement](https://go.dev/doc/go1.21#crypto/sha256) by utilizing the
SHA instructions for AMD64 CPUs, which sha256-simd already was doing.
The performance is now on par and I think it's preferable to use the
standard library rather than a package when possible.

```
cpu: AMD Ryzen 5 3600X 6-Core Processor
                │  simd.txt   │               go.txt                │
                │   sec/op    │    sec/op     vs base               │
Hash/8Bytes-12    63.25n ± 1%    73.38n ± 1%  +16.02% (p=0.002 n=6)
Hash/64Bytes-12   98.73n ± 1%   105.30n ± 1%   +6.65% (p=0.002 n=6)
Hash/1K-12        567.2n ± 1%    572.8n ± 1%   +0.99% (p=0.002 n=6)
Hash/8K-12        4.062µ ± 1%    4.062µ ± 1%        ~ (p=0.396 n=6)
Hash/1M-12        512.1µ ± 0%    510.6µ ± 1%        ~ (p=0.485 n=6)
Hash/5M-12        2.556m ± 1%    2.564m ± 0%        ~ (p=0.093 n=6)
Hash/10M-12       5.112m ± 0%    5.127m ± 0%        ~ (p=0.093 n=6)
geomean           13.82µ         14.27µ        +3.28%

                │   simd.txt   │               go.txt                │
                │     B/s      │     B/s       vs base               │
Hash/8Bytes-12    120.6Mi ± 1%   104.0Mi ± 1%  -13.81% (p=0.002 n=6)
Hash/64Bytes-12   618.2Mi ± 1%   579.8Mi ± 1%   -6.22% (p=0.002 n=6)
Hash/1K-12        1.682Gi ± 1%   1.665Gi ± 1%   -0.98% (p=0.002 n=6)
Hash/8K-12        1.878Gi ± 1%   1.878Gi ± 1%        ~ (p=0.310 n=6)
Hash/1M-12        1.907Gi ± 0%   1.913Gi ± 1%        ~ (p=0.485 n=6)
Hash/5M-12        1.911Gi ± 1%   1.904Gi ± 0%        ~ (p=0.093 n=6)
Hash/10M-12       1.910Gi ± 0%   1.905Gi ± 0%        ~ (p=0.093 n=6)
geomean           1.066Gi        1.032Gi        -3.18%
```

(cherry picked from commit abd94ff5b59c86e793fd9bf12187ea6cfd1f3fa1)
(cherry picked from commit 15e81637abf70576a564cf9eecaa9640228afb5b)

Conflicts:
	go.mod
	https://codeberg.org/forgejo/forgejo/pulls/1581
---
 go.mod                                           | 2 +-
 models/auth/oauth2.go                            | 2 +-
 models/auth/twofactor.go                         | 2 +-
 models/migrations/base/hash.go                   | 2 +-
 models/migrations/v1_14/v166.go                  | 2 +-
 modules/auth/password/hash/pbkdf2.go             | 2 +-
 modules/avatar/hash.go                           | 3 +--
 modules/avatar/identicon/identicon.go            | 3 +--
 modules/base/tool.go                             | 2 +-
 modules/context/context_cookie.go                | 2 +-
 modules/git/last_commit_cache.go                 | 3 +--
 modules/lfs/content_store.go                     | 3 +--
 modules/lfs/pointer.go                           | 3 +--
 modules/secret/secret.go                         | 3 +--
 modules/util/keypair.go                          | 3 +--
 modules/util/keypair_test.go                     | 2 +-
 routers/api/packages/chef/auth.go                | 3 +--
 routers/api/packages/maven/maven.go              | 3 +--
 services/lfs/server.go                           | 2 +-
 services/mailer/token/token.go                   | 3 +--
 services/webhook/deliver.go                      | 2 +-
 tests/integration/api_packages_chef_test.go      | 2 +-
 tests/integration/api_packages_container_test.go | 2 +-
 tests/integration/api_packages_test.go           | 2 +-
 24 files changed, 24 insertions(+), 34 deletions(-)

diff --git a/go.mod b/go.mod
index 38afc33b36..b993d228d2 100644
--- a/go.mod
+++ b/go.mod
@@ -78,7 +78,6 @@ require (
 	github.com/mholt/archiver/v3 v3.5.1
 	github.com/microcosm-cc/bluemonday v1.0.25
 	github.com/minio/minio-go/v7 v7.0.63
-	github.com/minio/sha256-simd v1.0.1
 	github.com/msteinert/pam v1.2.0
 	github.com/nektos/act v0.2.52
 	github.com/niklasfasching/go-org v1.7.0
@@ -231,6 +230,7 @@ require (
 	github.com/mholt/acmez v1.2.0 // indirect
 	github.com/miekg/dns v1.1.56 // indirect
 	github.com/minio/md5-simd v1.1.2 // indirect
+	github.com/minio/sha256-simd v1.0.1 // indirect
 	github.com/mitchellh/copystructure v1.2.0 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/mitchellh/reflectwalk v1.0.2 // indirect
diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go
index 7ac1d3a5fc..b962959d8c 100644
--- a/models/auth/oauth2.go
+++ b/models/auth/oauth2.go
@@ -5,6 +5,7 @@ package auth
 
 import (
 	"context"
+	"crypto/sha256"
 	"encoding/base32"
 	"encoding/base64"
 	"fmt"
@@ -19,7 +20,6 @@ import (
 	"code.gitea.io/gitea/modules/util"
 
 	uuid "github.com/google/uuid"
-	"github.com/minio/sha256-simd"
 	"golang.org/x/crypto/bcrypt"
 	"xorm.io/builder"
 	"xorm.io/xorm"
diff --git a/models/auth/twofactor.go b/models/auth/twofactor.go
index 51061e5205..d0c341a192 100644
--- a/models/auth/twofactor.go
+++ b/models/auth/twofactor.go
@@ -6,6 +6,7 @@ package auth
 import (
 	"context"
 	"crypto/md5"
+	"crypto/sha256"
 	"crypto/subtle"
 	"encoding/base32"
 	"encoding/base64"
@@ -18,7 +19,6 @@ import (
 	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
 
-	"github.com/minio/sha256-simd"
 	"github.com/pquerna/otp/totp"
 	"golang.org/x/crypto/pbkdf2"
 )
diff --git a/models/migrations/base/hash.go b/models/migrations/base/hash.go
index 0debec272b..00fd1efd4a 100644
--- a/models/migrations/base/hash.go
+++ b/models/migrations/base/hash.go
@@ -4,9 +4,9 @@
 package base
 
 import (
+	"crypto/sha256"
 	"encoding/hex"
 
-	"github.com/minio/sha256-simd"
 	"golang.org/x/crypto/pbkdf2"
 )
 
diff --git a/models/migrations/v1_14/v166.go b/models/migrations/v1_14/v166.go
index 78f33e8f9b..e5731582fd 100644
--- a/models/migrations/v1_14/v166.go
+++ b/models/migrations/v1_14/v166.go
@@ -4,9 +4,9 @@
 package v1_14 //nolint
 
 import (
+	"crypto/sha256"
 	"encoding/hex"
 
-	"github.com/minio/sha256-simd"
 	"golang.org/x/crypto/argon2"
 	"golang.org/x/crypto/bcrypt"
 	"golang.org/x/crypto/pbkdf2"
diff --git a/modules/auth/password/hash/pbkdf2.go b/modules/auth/password/hash/pbkdf2.go
index 9ff6d162fc..27382fedb8 100644
--- a/modules/auth/password/hash/pbkdf2.go
+++ b/modules/auth/password/hash/pbkdf2.go
@@ -4,12 +4,12 @@
 package hash
 
 import (
+	"crypto/sha256"
 	"encoding/hex"
 	"strings"
 
 	"code.gitea.io/gitea/modules/log"
 
-	"github.com/minio/sha256-simd"
 	"golang.org/x/crypto/pbkdf2"
 )
 
diff --git a/modules/avatar/hash.go b/modules/avatar/hash.go
index 4fc28a7739..50db9c1943 100644
--- a/modules/avatar/hash.go
+++ b/modules/avatar/hash.go
@@ -4,10 +4,9 @@
 package avatar
 
 import (
+	"crypto/sha256"
 	"encoding/hex"
 	"strconv"
-
-	"github.com/minio/sha256-simd"
 )
 
 // HashAvatar will generate a unique string, which ensures that when there's a
diff --git a/modules/avatar/identicon/identicon.go b/modules/avatar/identicon/identicon.go
index 9b7a2faf05..63926d5f19 100644
--- a/modules/avatar/identicon/identicon.go
+++ b/modules/avatar/identicon/identicon.go
@@ -7,11 +7,10 @@
 package identicon
 
 import (
+	"crypto/sha256"
 	"fmt"
 	"image"
 	"image/color"
-
-	"github.com/minio/sha256-simd"
 )
 
 const minImageSize = 16
diff --git a/modules/base/tool.go b/modules/base/tool.go
index 71dcb83fb4..038138d647 100644
--- a/modules/base/tool.go
+++ b/modules/base/tool.go
@@ -6,6 +6,7 @@ package base
 import (
 	"crypto/md5"
 	"crypto/sha1"
+	"crypto/sha256"
 	"encoding/base64"
 	"encoding/hex"
 	"errors"
@@ -24,7 +25,6 @@ import (
 	"code.gitea.io/gitea/modules/setting"
 
 	"github.com/dustin/go-humanize"
-	"github.com/minio/sha256-simd"
 )
 
 // EncodeMD5 encodes string to md5 hex value.
diff --git a/modules/context/context_cookie.go b/modules/context/context_cookie.go
index 9ce67a5298..1fd9719d31 100644
--- a/modules/context/context_cookie.go
+++ b/modules/context/context_cookie.go
@@ -4,6 +4,7 @@
 package context
 
 import (
+	"crypto/sha256"
 	"encoding/hex"
 	"net/http"
 	"strings"
@@ -12,7 +13,6 @@ import (
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web/middleware"
 
-	"github.com/minio/sha256-simd"
 	"golang.org/x/crypto/pbkdf2"
 )
 
diff --git a/modules/git/last_commit_cache.go b/modules/git/last_commit_cache.go
index 20bc796085..6fb910fcae 100644
--- a/modules/git/last_commit_cache.go
+++ b/modules/git/last_commit_cache.go
@@ -4,12 +4,11 @@
 package git
 
 import (
+	"crypto/sha256"
 	"fmt"
 
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
-
-	"github.com/minio/sha256-simd"
 )
 
 // Cache represents a caching interface
diff --git a/modules/lfs/content_store.go b/modules/lfs/content_store.go
index daf8c6cfdd..0d9c0c98ac 100644
--- a/modules/lfs/content_store.go
+++ b/modules/lfs/content_store.go
@@ -4,6 +4,7 @@
 package lfs
 
 import (
+	"crypto/sha256"
 	"encoding/hex"
 	"errors"
 	"hash"
@@ -12,8 +13,6 @@ import (
 
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/storage"
-
-	"github.com/minio/sha256-simd"
 )
 
 var (
diff --git a/modules/lfs/pointer.go b/modules/lfs/pointer.go
index 3e5bb8f91d..ebde20f826 100644
--- a/modules/lfs/pointer.go
+++ b/modules/lfs/pointer.go
@@ -4,6 +4,7 @@
 package lfs
 
 import (
+	"crypto/sha256"
 	"encoding/hex"
 	"errors"
 	"fmt"
@@ -12,8 +13,6 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
-
-	"github.com/minio/sha256-simd"
 )
 
 const (
diff --git a/modules/secret/secret.go b/modules/secret/secret.go
index 9c2ecd181d..e70ae1839c 100644
--- a/modules/secret/secret.go
+++ b/modules/secret/secret.go
@@ -7,13 +7,12 @@ import (
 	"crypto/aes"
 	"crypto/cipher"
 	"crypto/rand"
+	"crypto/sha256"
 	"encoding/base64"
 	"encoding/hex"
 	"errors"
 	"fmt"
 	"io"
-
-	"github.com/minio/sha256-simd"
 )
 
 // AesEncrypt encrypts text and given key with AES.
diff --git a/modules/util/keypair.go b/modules/util/keypair.go
index 97f2d9ebca..8b86c142af 100644
--- a/modules/util/keypair.go
+++ b/modules/util/keypair.go
@@ -7,10 +7,9 @@ import (
 	"crypto"
 	"crypto/rand"
 	"crypto/rsa"
+	"crypto/sha256"
 	"crypto/x509"
 	"encoding/pem"
-
-	"github.com/minio/sha256-simd"
 )
 
 // GenerateKeyPair generates a public and private keypair
diff --git a/modules/util/keypair_test.go b/modules/util/keypair_test.go
index c9925f7988..c6f68c845a 100644
--- a/modules/util/keypair_test.go
+++ b/modules/util/keypair_test.go
@@ -7,12 +7,12 @@ import (
 	"crypto"
 	"crypto/rand"
 	"crypto/rsa"
+	"crypto/sha256"
 	"crypto/x509"
 	"encoding/pem"
 	"regexp"
 	"testing"
 
-	"github.com/minio/sha256-simd"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/routers/api/packages/chef/auth.go b/routers/api/packages/chef/auth.go
index 3aef8281a4..a790e9a363 100644
--- a/routers/api/packages/chef/auth.go
+++ b/routers/api/packages/chef/auth.go
@@ -8,6 +8,7 @@ import (
 	"crypto"
 	"crypto/rsa"
 	"crypto/sha1"
+	"crypto/sha256"
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/pem"
@@ -26,8 +27,6 @@ import (
 	chef_module "code.gitea.io/gitea/modules/packages/chef"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/services/auth"
-
-	"github.com/minio/sha256-simd"
 )
 
 const (
diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go
index 52e31e8033..6cd5804645 100644
--- a/routers/api/packages/maven/maven.go
+++ b/routers/api/packages/maven/maven.go
@@ -6,6 +6,7 @@ package maven
 import (
 	"crypto/md5"
 	"crypto/sha1"
+	"crypto/sha256"
 	"crypto/sha512"
 	"encoding/hex"
 	"encoding/xml"
@@ -26,8 +27,6 @@ import (
 	maven_module "code.gitea.io/gitea/modules/packages/maven"
 	"code.gitea.io/gitea/routers/api/packages/helper"
 	packages_service "code.gitea.io/gitea/services/packages"
-
-	"github.com/minio/sha256-simd"
 )
 
 const (
diff --git a/services/lfs/server.go b/services/lfs/server.go
index 58b4663345..68e3cf98a0 100644
--- a/services/lfs/server.go
+++ b/services/lfs/server.go
@@ -5,6 +5,7 @@ package lfs
 
 import (
 	stdCtx "context"
+	"crypto/sha256"
 	"encoding/base64"
 	"encoding/hex"
 	"errors"
@@ -33,7 +34,6 @@ import (
 	"code.gitea.io/gitea/modules/storage"
 
 	"github.com/golang-jwt/jwt/v5"
-	"github.com/minio/sha256-simd"
 )
 
 // requestContext contain variables from the HTTP request.
diff --git a/services/mailer/token/token.go b/services/mailer/token/token.go
index aa7b567188..8a5a762d6b 100644
--- a/services/mailer/token/token.go
+++ b/services/mailer/token/token.go
@@ -6,14 +6,13 @@ package token
 import (
 	"context"
 	crypto_hmac "crypto/hmac"
+	"crypto/sha256"
 	"encoding/base32"
 	"fmt"
 	"time"
 
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/util"
-
-	"github.com/minio/sha256-simd"
 )
 
 // A token is a verifiable container describing an action.
diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go
index fd7a3d7fba..cf82aab2de 100644
--- a/services/webhook/deliver.go
+++ b/services/webhook/deliver.go
@@ -7,6 +7,7 @@ import (
 	"context"
 	"crypto/hmac"
 	"crypto/sha1"
+	"crypto/sha256"
 	"crypto/tls"
 	"encoding/hex"
 	"fmt"
@@ -29,7 +30,6 @@ import (
 	webhook_module "code.gitea.io/gitea/modules/webhook"
 
 	"github.com/gobwas/glob"
-	"github.com/minio/sha256-simd"
 )
 
 // Deliver deliver hook task
diff --git a/tests/integration/api_packages_chef_test.go b/tests/integration/api_packages_chef_test.go
index 4a1fe2607e..7a9a8f30b8 100644
--- a/tests/integration/api_packages_chef_test.go
+++ b/tests/integration/api_packages_chef_test.go
@@ -11,6 +11,7 @@ import (
 	"crypto/rand"
 	"crypto/rsa"
 	"crypto/sha1"
+	"crypto/sha256"
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/pem"
@@ -33,7 +34,6 @@ import (
 	chef_router "code.gitea.io/gitea/routers/api/packages/chef"
 	"code.gitea.io/gitea/tests"
 
-	"github.com/minio/sha256-simd"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go
index 01002a4413..7d2215e7f8 100644
--- a/tests/integration/api_packages_container_test.go
+++ b/tests/integration/api_packages_container_test.go
@@ -5,6 +5,7 @@ package integration
 
 import (
 	"bytes"
+	"crypto/sha256"
 	"encoding/base64"
 	"fmt"
 	"net/http"
@@ -23,7 +24,6 @@ import (
 	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/tests"
 
-	"github.com/minio/sha256-simd"
 	oci "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/stretchr/testify/assert"
 )
diff --git a/tests/integration/api_packages_test.go b/tests/integration/api_packages_test.go
index e530b2c1ad..59d8dcb88a 100644
--- a/tests/integration/api_packages_test.go
+++ b/tests/integration/api_packages_test.go
@@ -5,6 +5,7 @@ package integration
 
 import (
 	"bytes"
+	"crypto/sha256"
 	"fmt"
 	"net/http"
 	"strings"
@@ -24,7 +25,6 @@ import (
 	packages_cleanup_service "code.gitea.io/gitea/services/packages/cleanup"
 	"code.gitea.io/gitea/tests"
 
-	"github.com/minio/sha256-simd"
 	"github.com/stretchr/testify/assert"
 )
 

From ff60267b91232c133de577e7fcb3436c29b98725 Mon Sep 17 00:00:00 2001
From: Gusted 
Date: Sat, 30 Sep 2023 00:07:45 +0200
Subject: [PATCH 26/30] [GITEA] Remove SSH workaround

- Update github.com/gliderlabs/ssh to include https://github.com/gliderlabs/ssh/commit/02f9d573009f8c13755b6b90fa14a4f549b17b22.
- Resolves https://codeberg.org/forgejo/forgejo/issues/1230

(cherry picked from commit 05a0fd8c9f4ba8876e8845dfad49b449681a1341)
(cherry picked from commit fcd68716a39333d5c00c74e44da27b913a3da20c)

Conflicts:
	go.mod
	https://codeberg.org/forgejo/forgejo/pulls/1581
---
 go.mod             | 2 +-
 go.sum             | 8 ++------
 modules/ssh/ssh.go | 9 ++-------
 3 files changed, 5 insertions(+), 14 deletions(-)

diff --git a/go.mod b/go.mod
index b993d228d2..552121c754 100644
--- a/go.mod
+++ b/go.mod
@@ -36,7 +36,7 @@ require (
 	github.com/ethantkoenig/rupture v1.0.1
 	github.com/felixge/fgprof v0.9.3
 	github.com/fsnotify/fsnotify v1.6.0
-	github.com/gliderlabs/ssh v0.3.5
+	github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46
 	github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b
 	github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
 	github.com/go-chi/chi/v5 v5.0.10
diff --git a/go.sum b/go.sum
index ae3bb5541b..c70fa6c372 100644
--- a/go.sum
+++ b/go.sum
@@ -327,8 +327,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
 github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
 github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
-github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
-github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
+github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46 h1:fYiA820jw7wmAvdXrHwMItxjJkra7dT9y8yiXhtzb94=
+github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46/go.mod h1:i/TCLcdiX9Up/vs+Rp8c3yMbqp2Y4Y7Nh9uzGFCa5pM=
 github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
 github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
 github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b h1:VLD6IPBDkqEsOZ+EfLO6MayuHycZ0cv4BStTlRoZduo=
@@ -1225,7 +1225,6 @@ golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@@ -1325,9 +1324,7 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1341,7 +1338,6 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go
index 37624ab679..f8e4f569b8 100644
--- a/modules/ssh/ssh.go
+++ b/modules/ssh/ssh.go
@@ -17,7 +17,6 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
-	"reflect"
 	"strconv"
 	"strings"
 	"sync"
@@ -165,10 +164,6 @@ func sessionHandler(session ssh.Session) {
 }
 
 func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
-	// FIXME: the "ssh.Context" is not thread-safe, so db operations should use the immutable parent "Context"
-	// TODO: Remove after https://github.com/gliderlabs/ssh/pull/211
-	parentCtx := reflect.ValueOf(ctx).Elem().FieldByName("Context").Interface().(context.Context)
-
 	if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
 		log.Debug("Handle Public Key: Fingerprint: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())
 	}
@@ -200,7 +195,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
 		// look for the exact principal
 	principalLoop:
 		for _, principal := range cert.ValidPrincipals {
-			pkey, err := asymkey_model.SearchPublicKeyByContentExact(parentCtx, principal)
+			pkey, err := asymkey_model.SearchPublicKeyByContentExact(ctx, principal)
 			if err != nil {
 				if asymkey_model.IsErrKeyNotExist(err) {
 					log.Debug("Principal Rejected: %s Unknown Principal: %s", ctx.RemoteAddr(), principal)
@@ -257,7 +252,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
 		log.Debug("Handle Public Key: %s Fingerprint: %s is not a certificate", ctx.RemoteAddr(), gossh.FingerprintSHA256(key))
 	}
 
-	pkey, err := asymkey_model.SearchPublicKeyByContent(parentCtx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key))))
+	pkey, err := asymkey_model.SearchPublicKeyByContent(ctx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key))))
 	if err != nil {
 		if asymkey_model.IsErrKeyNotExist(err) {
 			log.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())

From 85f2065c9bc6ded9c21909ec76a9e8fc2d22f462 Mon Sep 17 00:00:00 2001
From: Gusted 
Date: Sat, 30 Sep 2023 22:16:47 +0200
Subject: [PATCH 27/30] [GITEA] Make atomic ssh keys replacement robust

- After stumbling upon https://github.com/golang/go/issues/22397 and
reading the implementations I realized that Forgejo code doesn't have
`Sync()` and it doesn't properly error handle the `Close` function.
- (likely) Resolves https://codeberg.org/forgejo/forgejo/issues/1446

(cherry picked from commit 0efcb334c2f123d0869a30d684189eb31e8b983f)
(cherry picked from commit 04ef02c0dd98c7437acb39383d311c0901366508)
---
 models/asymkey/ssh_key_authorized_keys.go       | 7 ++++++-
 models/asymkey/ssh_key_authorized_principals.go | 7 ++++++-
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/models/asymkey/ssh_key_authorized_keys.go b/models/asymkey/ssh_key_authorized_keys.go
index f0a3a77eaf..4c9cc1deab 100644
--- a/models/asymkey/ssh_key_authorized_keys.go
+++ b/models/asymkey/ssh_key_authorized_keys.go
@@ -169,7 +169,12 @@ func RewriteAllPublicKeys(ctx context.Context) error {
 		return err
 	}
 
-	t.Close()
+	if err := t.Sync(); err != nil {
+		return err
+	}
+	if err := t.Close(); err != nil {
+		return err
+	}
 	return util.Rename(tmpPath, fPath)
 }
 
diff --git a/models/asymkey/ssh_key_authorized_principals.go b/models/asymkey/ssh_key_authorized_principals.go
index 592196c255..79915df7b5 100644
--- a/models/asymkey/ssh_key_authorized_principals.go
+++ b/models/asymkey/ssh_key_authorized_principals.go
@@ -92,7 +92,12 @@ func RewriteAllPrincipalKeys(ctx context.Context) error {
 		return err
 	}
 
-	t.Close()
+	if err := t.Sync(); err != nil {
+		return err
+	}
+	if err := t.Close(); err != nil {
+		return err
+	}
 	return util.Rename(tmpPath, fPath)
 }
 

From 2b4ab46d8eacd2e6b2318f26e327ec59b804ea23 Mon Sep 17 00:00:00 2001
From: Gusted 
Date: Sun, 1 Oct 2023 12:58:58 +0200
Subject: [PATCH 28/30] [GITEA] Use existing jsonschema library

- Use the 'existing' jsonschema library for the nodeinfo integration test.

(cherry picked from commit 73864840f27274d4cdaef23d47a6a71fc60529c3)
(cherry picked from commit da36df306b7a75434c75ed5f63608e06266ca480)

Conflicts:
	go.mod
	https://codeberg.org/forgejo/forgejo/pulls/1581
---
 go.mod                                |  3 ---
 go.sum                                |  7 -------
 tests/integration/integration_test.go | 19 +++++++++----------
 3 files changed, 9 insertions(+), 20 deletions(-)

diff --git a/go.mod b/go.mod
index 552121c754..4e8bcbfeb2 100644
--- a/go.mod
+++ b/go.mod
@@ -100,7 +100,6 @@ require (
 	github.com/ulikunitz/xz v0.5.11
 	github.com/urfave/cli/v2 v2.25.7
 	github.com/xanzy/go-gitlab v0.93.0
-	github.com/xeipuuv/gojsonschema v1.2.0
 	github.com/yohcop/openid-go v1.0.1
 	github.com/yuin/goldmark v1.5.6
 	github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
@@ -274,8 +273,6 @@ require (
 	github.com/valyala/fastjson v1.6.4 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
 	github.com/xanzy/ssh-agent v0.3.3 // indirect
-	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
-	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
 	github.com/zeebo/blake3 v0.2.3 // indirect
diff --git a/go.sum b/go.sum
index c70fa6c372..c7279f5397 100644
--- a/go.sum
+++ b/go.sum
@@ -1039,13 +1039,6 @@ github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3k
 github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
 github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
 github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
-github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
-github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
-github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
-github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
-github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
-github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
-github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go
index a3d0eb1019..6b00f3575b 100644
--- a/tests/integration/integration_test.go
+++ b/tests/integration/integration_test.go
@@ -42,8 +42,8 @@ import (
 	"github.com/markbates/goth"
 	"github.com/markbates/goth/gothic"
 	goth_gitlab "github.com/markbates/goth/providers/gitlab"
+	"github.com/santhosh-tekuri/jsonschema/v5"
 	"github.com/stretchr/testify/assert"
-	"github.com/xeipuuv/gojsonschema"
 )
 
 var testWebRoutes *web.Route
@@ -512,16 +512,15 @@ func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile
 	_, schemaFileErr := os.Stat(schemaFilePath)
 	assert.Nil(t, schemaFileErr)
 
-	schema, schemaFileReadErr := os.ReadFile(schemaFilePath)
-	assert.Nil(t, schemaFileReadErr)
-	assert.True(t, len(schema) > 0)
+	schema, err := jsonschema.Compile(schemaFilePath)
+	assert.NoError(t, err)
 
-	nodeinfoSchema := gojsonschema.NewStringLoader(string(schema))
-	nodeinfoString := gojsonschema.NewStringLoader(resp.Body.String())
-	result, schemaValidationErr := gojsonschema.Validate(nodeinfoSchema, nodeinfoString)
-	assert.Nil(t, schemaValidationErr)
-	assert.Empty(t, result.Errors())
-	assert.True(t, result.Valid())
+	var data interface{}
+	err = json.Unmarshal(resp.Body.Bytes(), &data)
+	assert.NoError(t, err)
+
+	schemaValidation := schema.Validate(data)
+	assert.Nil(t, schemaValidation)
 }
 
 func GetCSRF(t testing.TB, session *TestSession, urlStr string) string {

From 711638193daa2311e2ead6249a47dcec47b4e335 Mon Sep 17 00:00:00 2001
From: Gusted 
Date: Sun, 1 Oct 2023 00:09:25 +0200
Subject: [PATCH 29/30] [GITEA] Use maintained gziphandler

- https://github.com/NYTimes/gziphandler doesn't seems to be maintained
anymore and Forgejo already includes
https://github.com/klauspost/compress which provides a maintained and
faster gzip handler fork.
- Enables Jitter to prevent BREACH attacks, as this *seems* to be
possible in the context of Forgejo.

(cherry picked from commit cc2847241d82001babd8d40c87d03169f21c14cd)
(cherry picked from commit 99ba56a8761dd08e08d9499cab2ded1a6b7b970f)

Conflicts:
	go.sum
	https://codeberg.org/forgejo/forgejo/pulls/1581
---
 assets/go-licenses.json                 | 10 +++++-----
 go.mod                                  |  1 -
 go.sum                                  |  2 --
 modules/web/handler.go                  | 10 ++++++++++
 routers/web/web.go                      | 13 ++++---------
 tests/integration/lfs_getobject_test.go |  8 ++++----
 6 files changed, 23 insertions(+), 21 deletions(-)

diff --git a/assets/go-licenses.json b/assets/go-licenses.json
index ee1e503f2a..159f335a17 100644
--- a/assets/go-licenses.json
+++ b/assets/go-licenses.json
@@ -89,11 +89,6 @@
     "path": "github.com/DataDog/zstd/LICENSE",
     "licenseText": "Simplified BSD License\n\nCopyright (c) 2016, Datadog \u003cinfo@datadoghq.com\u003e\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright notice,\n      this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright notice,\n      this list of conditions and the following disclaimer in the documentation\n      and/or other materials provided with the distribution.\n    * Neither the name of the copyright holder nor the names of its contributors\n      may be used to endorse or promote products derived from this software\n      without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
   },
-  {
-    "name": "github.com/NYTimes/gziphandler",
-    "path": "github.com/NYTimes/gziphandler/LICENSE",
-    "licenseText": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2016-2017 The New York Times Company\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
-  },
   {
     "name": "github.com/ProtonMail/go-crypto",
     "path": "github.com/ProtonMail/go-crypto/LICENSE",
@@ -669,6 +664,11 @@
     "path": "github.com/klauspost/compress/LICENSE",
     "licenseText": "Copyright (c) 2012 The Go Authors. All rights reserved.\nCopyright (c) 2019 Klaus Post. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n------------------\n\nFiles: gzhttp/*\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2016-2017 The New York Times Company\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n------------------\n\nFiles: s2/cmd/internal/readahead/*\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Klaus Post\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n---------------------\nFiles: snappy/*\nFiles: internal/snapref/*\n\nCopyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-----------------\n\nFiles: s2/cmd/internal/filepathx/*\n\nCopyright 2016 The filepathx Authors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
   },
+  {
+    "name": "github.com/klauspost/compress/gzhttp",
+    "path": "github.com/klauspost/compress/gzhttp/LICENSE",
+    "licenseText": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2016-2017 The New York Times Company\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
+  },
   {
     "name": "github.com/klauspost/compress/internal/snapref",
     "path": "github.com/klauspost/compress/internal/snapref/LICENSE",
diff --git a/go.mod b/go.mod
index 4e8bcbfeb2..c59ef850e6 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,6 @@ require (
 	gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
 	github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
 	github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
-	github.com/NYTimes/gziphandler v1.1.1
 	github.com/PuerkitoBio/goquery v1.8.1
 	github.com/alecthomas/chroma/v2 v2.9.1
 	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
diff --git a/go.sum b/go.sum
index c7279f5397..b7347c0c52 100644
--- a/go.sum
+++ b/go.sum
@@ -102,8 +102,6 @@ github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBa
 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
 github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
 github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
-github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
-github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
 github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
 github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
 github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
diff --git a/modules/web/handler.go b/modules/web/handler.go
index 26b7428016..728cc5a160 100644
--- a/modules/web/handler.go
+++ b/modules/web/handler.go
@@ -147,6 +147,16 @@ func toHandlerProvider(handler any) func(next http.Handler) http.Handler {
 		}
 	}
 
+	if hp, ok := handler.(func(next http.Handler) http.HandlerFunc); ok {
+		return func(next http.Handler) http.Handler {
+			h := hp(next) // this handle could be dynamically generated, so we can't use it for debug info
+			return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+				routing.UpdateFuncInfo(req.Context(), funcInfo)
+				h.ServeHTTP(resp, req)
+			})
+		}
+	}
+
 	provider := func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) {
 			// wrap the response writer to check whether the response has been written
diff --git a/routers/web/web.go b/routers/web/web.go
index a793cfb542..bb10d86be7 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -48,17 +48,12 @@ import (
 	_ "code.gitea.io/gitea/modules/session" // to registers all internal adapters
 
 	"gitea.com/go-chi/captcha"
-	"github.com/NYTimes/gziphandler"
 	chi_middleware "github.com/go-chi/chi/v5/middleware"
 	"github.com/go-chi/cors"
+	"github.com/klauspost/compress/gzhttp"
 	"github.com/prometheus/client_golang/prometheus"
 )
 
-const (
-	// GzipMinSize represents min size to compress for the body size of response
-	GzipMinSize = 1400
-)
-
 // CorsHandler return a http handler who set CORS options if enabled by config
 func CorsHandler() func(next http.Handler) http.Handler {
 	if setting.CORSConfig.Enabled {
@@ -229,11 +224,11 @@ func Routes() *web.Route {
 	var mid []any
 
 	if setting.EnableGzip {
-		h, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(GzipMinSize))
+		wrapper, err := gzhttp.NewWrapper(gzhttp.RandomJitter(32, 0, false))
 		if err != nil {
-			log.Fatal("GzipHandlerWithOpts failed: %v", err)
+			log.Fatal("gzhttp.NewWrapper failed: %v", err)
 		}
-		mid = append(mid, h)
+		mid = append(mid, wrapper)
 	}
 
 	if setting.Service.EnableCaptcha {
diff --git a/tests/integration/lfs_getobject_test.go b/tests/integration/lfs_getobject_test.go
index fe070c62d5..c36e0ef06c 100644
--- a/tests/integration/lfs_getobject_test.go
+++ b/tests/integration/lfs_getobject_test.go
@@ -18,9 +18,9 @@ import (
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/lfs"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/routers/web"
 	"code.gitea.io/gitea/tests"
 
+	"github.com/klauspost/compress/gzhttp"
 	gzipp "github.com/klauspost/compress/gzip"
 	"github.com/stretchr/testify/assert"
 )
@@ -132,7 +132,7 @@ func TestGetLFSSmallTokenFail(t *testing.T) {
 
 func TestGetLFSLarge(t *testing.T) {
 	defer tests.PrepareTestEnv(t)()
-	content := make([]byte, web.GzipMinSize*10)
+	content := make([]byte, gzhttp.DefaultMinSize*10)
 	for i := range content {
 		content[i] = byte(i % 256)
 	}
@@ -143,7 +143,7 @@ func TestGetLFSLarge(t *testing.T) {
 
 func TestGetLFSGzip(t *testing.T) {
 	defer tests.PrepareTestEnv(t)()
-	b := make([]byte, web.GzipMinSize*10)
+	b := make([]byte, gzhttp.DefaultMinSize*10)
 	for i := range b {
 		b[i] = byte(i % 256)
 	}
@@ -159,7 +159,7 @@ func TestGetLFSGzip(t *testing.T) {
 
 func TestGetLFSZip(t *testing.T) {
 	defer tests.PrepareTestEnv(t)()
-	b := make([]byte, web.GzipMinSize*10)
+	b := make([]byte, gzhttp.DefaultMinSize*10)
 	for i := range b {
 		b[i] = byte(i % 256)
 	}

From a14be197eb0b6eddef0162aea2ba639873463f19 Mon Sep 17 00:00:00 2001
From: Gusted 
Date: Sat, 3 Jun 2023 15:21:40 +0200
Subject: [PATCH 30/30] [GITEA] rework long-term authentication

- The current architecture is inherently insecure, because you can
construct the 'secret' cookie value with values that are available in
the database. Thus provides zero protection when a database is
dumped/leaked.
- This patch implements a new architecture that's inspired from: [Paragonie Initiative](https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence#secure-remember-me-cookies).
- Integration testing is added to ensure the new mechanism works.
- Removes a setting, because it's not used anymore.

(cherry picked from commit eff097448b1ebd2a280fcdd55d10b1f6081e9ccd)

[GITEA] rework long-term authentication (squash) add migration

Reminder: the migration is run via integration tests as explained
in the commit "[DB] run all Forgejo migrations in integration tests"

(cherry picked from commit 4accf7443c1c59b4d2e7787d6a6c602d725da403)
---
 models/auth/auth_token.go             |  96 +++++++++++++++
 models/forgejo_migrations/migrate.go  |   2 +
 models/forgejo_migrations/v1_20/v3.go |  25 ++++
 models/user/user.go                   |   5 +
 modules/context/context_cookie.go     |  50 ++------
 modules/setting/security.go           |   2 -
 modules/util/legacy.go                |  53 ---------
 modules/util/legacy_test.go           |  20 ----
 routers/install/install.go            |  13 +-
 routers/web/auth/auth.go              |  61 ++++++----
 routers/web/auth/oauth.go             |   3 +-
 routers/web/home.go                   |   3 +-
 routers/web/user/setting/account.go   |   9 ++
 routers/web/web.go                    |   2 +-
 services/auth/auth.go                 |   4 -
 tests/integration/auth_token_test.go  | 163 ++++++++++++++++++++++++++
 tests/integration/integration_test.go |   8 ++
 17 files changed, 365 insertions(+), 154 deletions(-)
 create mode 100644 models/auth/auth_token.go
 create mode 100644 models/forgejo_migrations/v1_20/v3.go
 create mode 100644 tests/integration/auth_token_test.go

diff --git a/models/auth/auth_token.go b/models/auth/auth_token.go
new file mode 100644
index 0000000000..2c3ca90734
--- /dev/null
+++ b/models/auth/auth_token.go
@@ -0,0 +1,96 @@
+// Copyright 2023 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package auth
+
+import (
+	"context"
+	"crypto/sha256"
+	"encoding/hex"
+	"fmt"
+	"time"
+
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
+)
+
+// AuthorizationToken represents a authorization token to a user.
+type AuthorizationToken struct {
+	ID              int64  `xorm:"pk autoincr"`
+	UID             int64  `xorm:"INDEX"`
+	LookupKey       string `xorm:"INDEX UNIQUE"`
+	HashedValidator string
+	Expiry          timeutil.TimeStamp
+}
+
+// TableName provides the real table name.
+func (AuthorizationToken) TableName() string {
+	return "forgejo_auth_token"
+}
+
+func init() {
+	db.RegisterModel(new(AuthorizationToken))
+}
+
+// IsExpired returns if the authorization token is expired.
+func (authToken *AuthorizationToken) IsExpired() bool {
+	return authToken.Expiry.AsLocalTime().Before(time.Now())
+}
+
+// GenerateAuthToken generates a new authentication token for the given user.
+// It returns the lookup key and validator values that should be passed to the
+// user via a long-term cookie.
+func GenerateAuthToken(ctx context.Context, userID int64, expiry timeutil.TimeStamp) (lookupKey, validator string, err error) {
+	// Request 64 random bytes. The first 32 bytes will be used for the lookupKey
+	// and the other 32 bytes will be used for the validator.
+	rBytes, err := util.CryptoRandomBytes(64)
+	if err != nil {
+		return "", "", err
+	}
+	hexEncoded := hex.EncodeToString(rBytes)
+	validator, lookupKey = hexEncoded[64:], hexEncoded[:64]
+
+	_, err = db.GetEngine(ctx).Insert(&AuthorizationToken{
+		UID:             userID,
+		Expiry:          expiry,
+		LookupKey:       lookupKey,
+		HashedValidator: HashValidator(rBytes[32:]),
+	})
+	return lookupKey, validator, err
+}
+
+// FindAuthToken will find a authorization token via the lookup key.
+func FindAuthToken(ctx context.Context, lookupKey string) (*AuthorizationToken, error) {
+	var authToken AuthorizationToken
+	has, err := db.GetEngine(ctx).Where("lookup_key = ?", lookupKey).Get(&authToken)
+	if err != nil {
+		return nil, err
+	} else if !has {
+		return nil, fmt.Errorf("lookup key %q: %w", lookupKey, util.ErrNotExist)
+	}
+	return &authToken, nil
+}
+
+// DeleteAuthToken will delete the authorization token.
+func DeleteAuthToken(ctx context.Context, authToken *AuthorizationToken) error {
+	_, err := db.DeleteByBean(ctx, authToken)
+	return err
+}
+
+// DeleteAuthTokenByUser will delete all authorization tokens for the user.
+func DeleteAuthTokenByUser(ctx context.Context, userID int64) error {
+	if userID == 0 {
+		return nil
+	}
+
+	_, err := db.DeleteByBean(ctx, &AuthorizationToken{UID: userID})
+	return err
+}
+
+// HashValidator will return a hexified hashed version of the validator.
+func HashValidator(validator []byte) string {
+	h := sha256.New()
+	h.Write(validator)
+	return hex.EncodeToString(h.Sum(nil))
+}
diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go
index 2becf1b713..58f158bd17 100644
--- a/models/forgejo_migrations/migrate.go
+++ b/models/forgejo_migrations/migrate.go
@@ -41,6 +41,8 @@ var migrations = []*Migration{
 	NewMigration("Add Forgejo Blocked Users table", forgejo_v1_20.AddForgejoBlockedUser),
 	// v1 -> v2
 	NewMigration("create the forgejo_sem_ver table", forgejo_v1_20.CreateSemVerTable),
+	// v2 -> v3
+	NewMigration("create the forgejo_auth_token table", forgejo_v1_20.CreateAuthorizationTokenTable),
 }
 
 // GetCurrentDBVersion returns the current Forgejo database version.
diff --git a/models/forgejo_migrations/v1_20/v3.go b/models/forgejo_migrations/v1_20/v3.go
new file mode 100644
index 0000000000..38c29bed03
--- /dev/null
+++ b/models/forgejo_migrations/v1_20/v3.go
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: MIT
+
+package forgejo_v1_20 //nolint:revive
+
+import (
+	"code.gitea.io/gitea/modules/timeutil"
+
+	"xorm.io/xorm"
+)
+
+type AuthorizationToken struct {
+	ID              int64  `xorm:"pk autoincr"`
+	UID             int64  `xorm:"INDEX"`
+	LookupKey       string `xorm:"INDEX UNIQUE"`
+	HashedValidator string
+	Expiry          timeutil.TimeStamp
+}
+
+func (AuthorizationToken) TableName() string {
+	return "forgejo_auth_token"
+}
+
+func CreateAuthorizationTokenTable(x *xorm.Engine) error {
+	return x.Sync(new(AuthorizationToken))
+}
diff --git a/models/user/user.go b/models/user/user.go
index 95786a5a63..abf9f213f7 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -386,6 +386,11 @@ func (u *User) SetPassword(passwd string) (err error) {
 		return nil
 	}
 
+	// Invalidate all authentication tokens for this user.
+	if err := auth.DeleteAuthTokenByUser(db.DefaultContext, u.ID); err != nil {
+		return err
+	}
+
 	if u.Salt, err = GetUserSalt(); err != nil {
 		return err
 	}
diff --git a/modules/context/context_cookie.go b/modules/context/context_cookie.go
index 1fd9719d31..39e3218d1b 100644
--- a/modules/context/context_cookie.go
+++ b/modules/context/context_cookie.go
@@ -4,16 +4,14 @@
 package context
 
 import (
-	"crypto/sha256"
-	"encoding/hex"
 	"net/http"
 	"strings"
 
+	auth_model "code.gitea.io/gitea/models/auth"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/web/middleware"
-
-	"golang.org/x/crypto/pbkdf2"
 )
 
 const CookieNameFlash = "gitea_flash"
@@ -46,41 +44,13 @@ func (ctx *Context) GetSiteCookie(name string) string {
 	return middleware.GetSiteCookie(ctx.Req, name)
 }
 
-// GetSuperSecureCookie returns given cookie value from request header with secret string.
-func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) {
-	val := ctx.GetSiteCookie(name)
-	return ctx.CookieDecrypt(secret, val)
-}
-
-// CookieDecrypt returns given value from with secret string.
-func (ctx *Context) CookieDecrypt(secret, val string) (string, bool) {
-	if val == "" {
-		return "", false
-	}
-
-	text, err := hex.DecodeString(val)
+// SetLTACookie will generate a LTA token and add it as an cookie.
+func (ctx *Context) SetLTACookie(u *user_model.User) error {
+	days := 86400 * setting.LogInRememberDays
+	lookup, validator, err := auth_model.GenerateAuthToken(ctx, u.ID, timeutil.TimeStampNow().Add(int64(days)))
 	if err != nil {
-		return "", false
+		return err
 	}
-
-	key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
-	text, err = util.AESGCMDecrypt(key, text)
-	return string(text), err == nil
-}
-
-// SetSuperSecureCookie sets given cookie value to response header with secret string.
-func (ctx *Context) SetSuperSecureCookie(secret, name, value string, maxAge int) {
-	text := ctx.CookieEncrypt(secret, value)
-	ctx.SetSiteCookie(name, text, maxAge)
-}
-
-// CookieEncrypt encrypts a given value using the provided secret
-func (ctx *Context) CookieEncrypt(secret, value string) string {
-	key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
-	text, err := util.AESGCMEncrypt(key, []byte(value))
-	if err != nil {
-		panic("error encrypting cookie: " + err.Error())
-	}
-
-	return hex.EncodeToString(text)
+	ctx.SetSiteCookie(setting.CookieRememberName, lookup+":"+validator, days)
+	return nil
 }
diff --git a/modules/setting/security.go b/modules/setting/security.go
index 90f614d4cd..92caa05fad 100644
--- a/modules/setting/security.go
+++ b/modules/setting/security.go
@@ -19,7 +19,6 @@ var (
 	SecretKey                          string
 	InternalToken                      string // internal access token
 	LogInRememberDays                  int
-	CookieUserName                     string
 	CookieRememberName                 string
 	ReverseProxyAuthUser               string
 	ReverseProxyAuthEmail              string
@@ -104,7 +103,6 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
 	sec := rootCfg.Section("security")
 	InstallLock = HasInstallLock(rootCfg)
 	LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7)
-	CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome")
 	SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY")
 	if SecretKey == "" {
 		// FIXME: https://github.com/go-gitea/gitea/issues/16832
diff --git a/modules/util/legacy.go b/modules/util/legacy.go
index 2ea293a2be..2d4de01949 100644
--- a/modules/util/legacy.go
+++ b/modules/util/legacy.go
@@ -4,10 +4,6 @@
 package util
 
 import (
-	"crypto/aes"
-	"crypto/cipher"
-	"crypto/rand"
-	"errors"
 	"io"
 	"os"
 )
@@ -40,52 +36,3 @@ func CopyFile(src, dest string) error {
 	}
 	return os.Chmod(dest, si.Mode())
 }
-
-// AESGCMEncrypt (from legacy package): encrypts plaintext with the given key using AES in GCM mode. should be replaced.
-func AESGCMEncrypt(key, plaintext []byte) ([]byte, error) {
-	block, err := aes.NewCipher(key)
-	if err != nil {
-		return nil, err
-	}
-
-	gcm, err := cipher.NewGCM(block)
-	if err != nil {
-		return nil, err
-	}
-
-	nonce := make([]byte, gcm.NonceSize())
-	if _, err := rand.Read(nonce); err != nil {
-		return nil, err
-	}
-
-	ciphertext := gcm.Seal(nil, nonce, plaintext, nil)
-	return append(nonce, ciphertext...), nil
-}
-
-// AESGCMDecrypt (from legacy package): decrypts ciphertext with the given key using AES in GCM mode. should be replaced.
-func AESGCMDecrypt(key, ciphertext []byte) ([]byte, error) {
-	block, err := aes.NewCipher(key)
-	if err != nil {
-		return nil, err
-	}
-
-	gcm, err := cipher.NewGCM(block)
-	if err != nil {
-		return nil, err
-	}
-
-	size := gcm.NonceSize()
-	if len(ciphertext)-size <= 0 {
-		return nil, errors.New("ciphertext is empty")
-	}
-
-	nonce := ciphertext[:size]
-	ciphertext = ciphertext[size:]
-
-	plainText, err := gcm.Open(nil, nonce, ciphertext, nil)
-	if err != nil {
-		return nil, err
-	}
-
-	return plainText, nil
-}
diff --git a/modules/util/legacy_test.go b/modules/util/legacy_test.go
index e732094c29..b7991bd365 100644
--- a/modules/util/legacy_test.go
+++ b/modules/util/legacy_test.go
@@ -4,8 +4,6 @@
 package util
 
 import (
-	"crypto/aes"
-	"crypto/rand"
 	"fmt"
 	"os"
 	"testing"
@@ -37,21 +35,3 @@ func TestCopyFile(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, testContent, dstContent)
 }
-
-func TestAESGCM(t *testing.T) {
-	t.Parallel()
-
-	key := make([]byte, aes.BlockSize)
-	_, err := rand.Read(key)
-	assert.NoError(t, err)
-
-	plaintext := []byte("this will be encrypted")
-
-	ciphertext, err := AESGCMEncrypt(key, plaintext)
-	assert.NoError(t, err)
-
-	decrypted, err := AESGCMDecrypt(key, ciphertext)
-	assert.NoError(t, err)
-
-	assert.Equal(t, plaintext, decrypted)
-}
diff --git a/routers/install/install.go b/routers/install/install.go
index 1c44e1f5fd..cb7818bd33 100644
--- a/routers/install/install.go
+++ b/routers/install/install.go
@@ -553,18 +553,13 @@ func SubmitInstall(ctx *context.Context) {
 			u, _ = user_model.GetUserByName(ctx, u.Name)
 		}
 
-		days := 86400 * setting.LogInRememberDays
-		ctx.SetSiteCookie(setting.CookieUserName, u.Name, days)
-
-		ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd),
-			setting.CookieRememberName, u.Name, days)
-
-		// Auto-login for admin
-		if err = ctx.Session.Set("uid", u.ID); err != nil {
+		if err := ctx.SetLTACookie(u); err != nil {
 			ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
 			return
 		}
-		if err = ctx.Session.Set("uname", u.Name); err != nil {
+
+		// Auto-login for admin
+		if err = ctx.Session.Set("uid", u.ID); err != nil {
 			ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
 			return
 		}
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index 465077a909..884c6fe266 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -5,6 +5,8 @@
 package auth
 
 import (
+	"crypto/subtle"
+	"encoding/hex"
 	"errors"
 	"fmt"
 	"net/http"
@@ -50,21 +52,47 @@ func AutoSignIn(ctx *context.Context) (bool, error) {
 		return false, nil
 	}
 
-	uname := ctx.GetSiteCookie(setting.CookieUserName)
-	if len(uname) == 0 {
+	authCookie := ctx.GetSiteCookie(setting.CookieRememberName)
+	if len(authCookie) == 0 {
 		return false, nil
 	}
 
 	isSucceed := false
 	defer func() {
 		if !isSucceed {
-			log.Trace("auto-login cookie cleared: %s", uname)
-			ctx.DeleteSiteCookie(setting.CookieUserName)
+			log.Trace("Auto login cookie is cleared: %s", authCookie)
 			ctx.DeleteSiteCookie(setting.CookieRememberName)
 		}
 	}()
 
-	u, err := user_model.GetUserByName(ctx, uname)
+	lookupKey, validator, found := strings.Cut(authCookie, ":")
+	if !found {
+		return false, nil
+	}
+
+	authToken, err := auth.FindAuthToken(ctx, lookupKey)
+	if err != nil {
+		if errors.Is(err, util.ErrNotExist) {
+			return false, nil
+		}
+		return false, err
+	}
+
+	if authToken.IsExpired() {
+		err = auth.DeleteAuthToken(ctx, authToken)
+		return false, err
+	}
+
+	rawValidator, err := hex.DecodeString(validator)
+	if err != nil {
+		return false, err
+	}
+
+	if subtle.ConstantTimeCompare([]byte(authToken.HashedValidator), []byte(auth.HashValidator(rawValidator))) == 0 {
+		return false, nil
+	}
+
+	u, err := user_model.GetUserByID(ctx, authToken.UID)
 	if err != nil {
 		if !user_model.IsErrUserNotExist(err) {
 			return false, fmt.Errorf("GetUserByName: %w", err)
@@ -72,17 +100,11 @@ func AutoSignIn(ctx *context.Context) (bool, error) {
 		return false, nil
 	}
 
-	if val, ok := ctx.GetSuperSecureCookie(
-		base.EncodeMD5(u.Rands+u.Passwd), setting.CookieRememberName); !ok || val != u.Name {
-		return false, nil
-	}
-
 	isSucceed = true
 
 	if err := updateSession(ctx, nil, map[string]any{
 		// Set session IDs
-		"uid":   u.ID,
-		"uname": u.Name,
+		"uid": authToken.UID,
 	}); err != nil {
 		return false, fmt.Errorf("unable to updateSession: %w", err)
 	}
@@ -291,10 +313,10 @@ func handleSignIn(ctx *context.Context, u *user_model.User, remember bool) {
 
 func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRedirect bool) string {
 	if remember {
-		days := 86400 * setting.LogInRememberDays
-		ctx.SetSiteCookie(setting.CookieUserName, u.Name, days)
-		ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd),
-			setting.CookieRememberName, u.Name, days)
+		if err := ctx.SetLTACookie(u); err != nil {
+			ctx.ServerError("GenerateAuthToken", err)
+			return setting.AppSubURL + "/"
+		}
 	}
 
 	if err := updateSession(ctx, []string{
@@ -307,8 +329,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
 		"twofaRemember",
 		"linkAccount",
 	}, map[string]any{
-		"uid":   u.ID,
-		"uname": u.Name,
+		"uid": u.ID,
 	}); err != nil {
 		ctx.ServerError("RegenerateSession", err)
 		return setting.AppSubURL + "/"
@@ -369,7 +390,6 @@ func getUserName(gothUser *goth.User) string {
 func HandleSignOut(ctx *context.Context) {
 	_ = ctx.Session.Flush()
 	_ = ctx.Session.Destroy(ctx.Resp, ctx.Req)
-	ctx.DeleteSiteCookie(setting.CookieUserName)
 	ctx.DeleteSiteCookie(setting.CookieRememberName)
 	ctx.Csrf.DeleteCookie(ctx)
 	middleware.DeleteRedirectToCookie(ctx.Resp)
@@ -732,8 +752,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
 	log.Trace("User activated: %s", user.Name)
 
 	if err := updateSession(ctx, nil, map[string]any{
-		"uid":   user.ID,
-		"uname": user.Name,
+		"uid": user.ID,
 	}); err != nil {
 		log.Error("Unable to regenerate session for user: %-v with email: %s: %v", user, user.Email, err)
 		ctx.ServerError("ActivateUserEmail", err)
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index ce58cbdef9..2aba6e1224 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -1118,8 +1118,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
 	// we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page.
 	if !needs2FA {
 		if err := updateSession(ctx, nil, map[string]any{
-			"uid":   u.ID,
-			"uname": u.Name,
+			"uid": u.ID,
 		}); err != nil {
 			ctx.ServerError("updateSession", err)
 			return
diff --git a/routers/web/home.go b/routers/web/home.go
index ab3fbde2c9..4bcc4adcbf 100644
--- a/routers/web/home.go
+++ b/routers/web/home.go
@@ -54,8 +54,7 @@ func Home(ctx *context.Context) {
 	}
 
 	// Check auto-login.
-	uname := ctx.GetSiteCookie(setting.CookieUserName)
-	if len(uname) != 0 {
+	if len(ctx.GetSiteCookie(setting.CookieRememberName)) != 0 {
 		ctx.Redirect(setting.AppSubURL + "/user/login")
 		return
 	}
diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go
index 5c14f3ad4b..f50c19a923 100644
--- a/routers/web/user/setting/account.go
+++ b/routers/web/user/setting/account.go
@@ -78,6 +78,15 @@ func AccountPost(ctx *context.Context) {
 			ctx.ServerError("UpdateUser", err)
 			return
 		}
+
+		// Re-generate LTA cookie.
+		if len(ctx.GetSiteCookie(setting.CookieRememberName)) != 0 {
+			if err := ctx.SetLTACookie(ctx.Doer); err != nil {
+				ctx.ServerError("SetLTACookie", err)
+				return
+			}
+		}
+
 		log.Trace("User password updated: %s", ctx.Doer.Name)
 		ctx.Flash.Success(ctx.Tr("settings.change_password_success"))
 	}
diff --git a/routers/web/web.go b/routers/web/web.go
index bb10d86be7..593e56f586 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -181,7 +181,7 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont
 
 		// Redirect to log in page if auto-signin info is provided and has not signed in.
 		if !options.SignOutRequired && !ctx.IsSigned &&
-			len(ctx.GetSiteCookie(setting.CookieUserName)) > 0 {
+			len(ctx.GetSiteCookie(setting.CookieRememberName)) > 0 {
 			if ctx.Req.URL.Path != "/user/events" {
 				middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
 			}
diff --git a/services/auth/auth.go b/services/auth/auth.go
index 0c8acac61f..338e07005d 100644
--- a/services/auth/auth.go
+++ b/services/auth/auth.go
@@ -72,10 +72,6 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore
 	if err != nil {
 		log.Error(fmt.Sprintf("Error setting session: %v", err))
 	}
-	err = sess.Set("uname", user.Name)
-	if err != nil {
-		log.Error(fmt.Sprintf("Error setting session: %v", err))
-	}
 
 	// Language setting of the user overwrites the one previously set
 	// If the user does not have a locale set, we save the current one.
diff --git a/tests/integration/auth_token_test.go b/tests/integration/auth_token_test.go
new file mode 100644
index 0000000000..24c66ee261
--- /dev/null
+++ b/tests/integration/auth_token_test.go
@@ -0,0 +1,163 @@
+// Copyright 2023 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+	"encoding/hex"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+
+	"code.gitea.io/gitea/models/auth"
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/models/unittest"
+	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/tests"
+
+	"github.com/stretchr/testify/assert"
+)
+
+// GetSessionForLTACookie returns a new session with only the LTA cookie being set.
+func GetSessionForLTACookie(t *testing.T, ltaCookie *http.Cookie) *TestSession {
+	t.Helper()
+
+	ch := http.Header{}
+	ch.Add("Cookie", ltaCookie.String())
+	cr := http.Request{Header: ch}
+
+	session := emptyTestSession(t)
+	baseURL, err := url.Parse(setting.AppURL)
+	assert.NoError(t, err)
+	session.jar.SetCookies(baseURL, cr.Cookies())
+
+	return session
+}
+
+// GetLTACookieValue returns the value of the LTA cookie.
+func GetLTACookieValue(t *testing.T, sess *TestSession) string {
+	t.Helper()
+
+	rememberCookie := sess.GetCookie(setting.CookieRememberName)
+	assert.NotNil(t, rememberCookie)
+
+	cookieValue, err := url.QueryUnescape(rememberCookie.Value)
+	assert.NoError(t, err)
+
+	return cookieValue
+}
+
+// TestSessionCookie checks if the session cookie provides authentication.
+func TestSessionCookie(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	sess := loginUser(t, "user1")
+	assert.NotNil(t, sess.GetCookie(setting.SessionConfig.CookieName))
+
+	req := NewRequest(t, "GET", "/user/settings")
+	sess.MakeRequest(t, req, http.StatusOK)
+}
+
+// TestLTACookie checks if the LTA cookie that's returned is valid, exists in the database
+// and provides authentication of no session cookie is present.
+func TestLTACookie(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+	sess := emptyTestSession(t)
+
+	req := NewRequestWithValues(t, "POST", "/user/login", map[string]string{
+		"_csrf":     GetCSRF(t, sess, "/user/login"),
+		"user_name": user.Name,
+		"password":  userPassword,
+		"remember":  "true",
+	})
+	sess.MakeRequest(t, req, http.StatusSeeOther)
+
+	// Checks if the database entry exist for the user.
+	ltaCookieValue := GetLTACookieValue(t, sess)
+	lookupKey, validator, found := strings.Cut(ltaCookieValue, ":")
+	assert.True(t, found)
+	rawValidator, err := hex.DecodeString(validator)
+	assert.NoError(t, err)
+	unittest.AssertExistsAndLoadBean(t, &auth.AuthorizationToken{LookupKey: lookupKey, HashedValidator: auth.HashValidator(rawValidator), UID: user.ID})
+
+	// Check if the LTA cookie it provides authentication.
+	// If LTA cookie provides authentication /user/login shouldn't return status 200.
+	session := GetSessionForLTACookie(t, sess.GetCookie(setting.CookieRememberName))
+	req = NewRequest(t, "GET", "/user/login")
+	session.MakeRequest(t, req, http.StatusSeeOther)
+}
+
+// TestLTAPasswordChange checks that LTA doesn't provide authentication when a
+// password change has happened and that the new LTA does provide authentication.
+func TestLTAPasswordChange(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+
+	sess := loginUserWithPasswordRemember(t, user.Name, userPassword, true)
+	oldRememberCookie := sess.GetCookie(setting.CookieRememberName)
+	assert.NotNil(t, oldRememberCookie)
+
+	// Make a simple password change.
+	req := NewRequestWithValues(t, "POST", "/user/settings/account", map[string]string{
+		"_csrf":        GetCSRF(t, sess, "/user/settings/account"),
+		"old_password": userPassword,
+		"password":     "password2",
+		"retype":       "password2",
+	})
+	sess.MakeRequest(t, req, http.StatusSeeOther)
+	rememberCookie := sess.GetCookie(setting.CookieRememberName)
+	assert.NotNil(t, rememberCookie)
+
+	// Check if the password really changed.
+	assert.NotEqualValues(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).Passwd, user.Passwd)
+
+	// /user/settings/account should provide with a new LTA cookie, so check for that.
+	// If LTA cookie provides authentication /user/login shouldn't return status 200.
+	session := GetSessionForLTACookie(t, rememberCookie)
+	req = NewRequest(t, "GET", "/user/login")
+	session.MakeRequest(t, req, http.StatusSeeOther)
+
+	// Check if the old LTA token is invalidated.
+	session = GetSessionForLTACookie(t, oldRememberCookie)
+	req = NewRequest(t, "GET", "/user/login")
+	session.MakeRequest(t, req, http.StatusOK)
+}
+
+// TestLTAExpiry tests that the LTA expiry works.
+func TestLTAExpiry(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+
+	sess := loginUserWithPasswordRemember(t, user.Name, userPassword, true)
+
+	ltaCookieValie := GetLTACookieValue(t, sess)
+	lookupKey, _, found := strings.Cut(ltaCookieValie, ":")
+	assert.True(t, found)
+
+	// Ensure it's not expired.
+	lta := unittest.AssertExistsAndLoadBean(t, &auth.AuthorizationToken{UID: user.ID, LookupKey: lookupKey})
+	assert.False(t, lta.IsExpired())
+
+	// Manually stub LTA's expiry.
+	_, err := db.GetEngine(db.DefaultContext).ID(lta.ID).Table("forgejo_auth_token").Cols("expiry").Update(&auth.AuthorizationToken{Expiry: timeutil.TimeStampNow()})
+	assert.NoError(t, err)
+
+	// Ensure it's expired.
+	lta = unittest.AssertExistsAndLoadBean(t, &auth.AuthorizationToken{UID: user.ID, LookupKey: lookupKey})
+	assert.True(t, lta.IsExpired())
+
+	// Should return 200 OK, because LTA doesn't provide authorization anymore.
+	session := GetSessionForLTACookie(t, sess.GetCookie(setting.CookieRememberName))
+	req := NewRequest(t, "GET", "/user/login")
+	session.MakeRequest(t, req, http.StatusOK)
+
+	// Ensure it's deleted.
+	unittest.AssertNotExistsBean(t, &auth.AuthorizationToken{UID: user.ID, LookupKey: lookupKey})
+}
diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go
index 6b00f3575b..cb6269cea9 100644
--- a/tests/integration/integration_test.go
+++ b/tests/integration/integration_test.go
@@ -17,6 +17,7 @@ import (
 	"net/url"
 	"os"
 	"path/filepath"
+	"strconv"
 	"strings"
 	"sync/atomic"
 	"testing"
@@ -299,6 +300,12 @@ func loginUser(t testing.TB, userName string) *TestSession {
 
 func loginUserWithPassword(t testing.TB, userName, password string) *TestSession {
 	t.Helper()
+
+	return loginUserWithPasswordRemember(t, userName, password, false)
+}
+
+func loginUserWithPasswordRemember(t testing.TB, userName, password string, rememberMe bool) *TestSession {
+	t.Helper()
 	req := NewRequest(t, "GET", "/user/login")
 	resp := MakeRequest(t, req, http.StatusOK)
 
@@ -307,6 +314,7 @@ func loginUserWithPassword(t testing.TB, userName, password string) *TestSession
 		"_csrf":     doc.GetCSRF(),
 		"user_name": userName,
 		"password":  password,
+		"remember":  strconv.FormatBool(rememberMe),
 	})
 	resp = MakeRequest(t, req, http.StatusSeeOther)