diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index d3e1920a10..7431630b13 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -806,6 +806,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
@@ -1762,9 +1767,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
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},
 	}
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/models/fixtures/release.yml b/models/fixtures/release.yml
index 4ed7df440d..844deb3a7b 100644
--- a/models/fixtures/release.yml
+++ b/models/fixtures/release.yml
@@ -136,3 +136,17 @@
   is_prerelease: false
   is_tag: false
   created_unix: 946684803
+
+- id: 11
+  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 c22eb8c2a2..6afef2a432 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 7faba424b6..a7ef741ac1 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 f24d098a7e..c0d3051079 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 7097b6ea14..3be1ebb3c9 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/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go
index a6dac12039..7a95ab518c 100644
--- a/modules/markup/orgmode/orgmode.go
+++ b/modules/markup/orgmode/orgmode.go
@@ -153,18 +153,30 @@ func (r *Writer) WriteRegularLink(l org.RegularLink) {
 		link = []byte(util.URLJoin(r.URLPrefix, lnk))
 	}
 
-	description := string(link)
-	if l.Description != nil {
-		description = r.WriteNodesAsString(l.Description...)
-	}
 	switch l.Kind() {
 	case "image":
-		imageSrc := getMediaURL(link)
-		fmt.Fprintf(r, `<img src="%s" alt="%s" title="%s" />`, imageSrc, description, description)
+		if l.Description == nil {
+			imageSrc := getMediaURL(link)
+			fmt.Fprintf(r, `<img src="%s" alt="%s" title="%s" />`, imageSrc, link, link)
+		} else {
+			description := strings.TrimPrefix(org.String(l.Description...), "file:")
+			imageSrc := getMediaURL([]byte(description))
+			fmt.Fprintf(r, `<a href="%s"><img src="%s" alt="%s" /></a>`, link, imageSrc, imageSrc)
+		}
 	case "video":
-		videoSrc := getMediaURL(link)
-		fmt.Fprintf(r, `<video src="%s" title="%s">%s</video>`, videoSrc, description, description)
+		if l.Description == nil {
+			imageSrc := getMediaURL(link)
+			fmt.Fprintf(r, `<video src="%s" title="%s">%s</video>`, imageSrc, link, link)
+		} else {
+			description := strings.TrimPrefix(org.String(l.Description...), "file:")
+			videoSrc := getMediaURL([]byte(description))
+			fmt.Fprintf(r, `<a href="%s"><video src="%s" title="%s"></video></a>`, link, videoSrc, videoSrc)
+		}
 	default:
+		description := string(link)
+		if l.Description != nil {
+			description = r.WriteNodesAsString(l.Description...)
+		}
 		fmt.Fprintf(r, `<a href="%s" title="%s">%s</a>`, link, description, description)
 	}
 }
diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go
index d6467c36f7..8f454e9955 100644
--- a/modules/markup/orgmode/orgmode_test.go
+++ b/modules/markup/orgmode/orgmode_test.go
@@ -42,7 +42,7 @@ func TestRender_StandardLinks(t *testing.T) {
 		"<p><a href=\""+lnk+"\" title=\"WikiPage\">WikiPage</a></p>")
 }
 
-func TestRender_Images(t *testing.T) {
+func TestRender_Media(t *testing.T) {
 	setting.AppURL = AppURL
 	setting.AppSubURL = AppSubURL
 
@@ -60,6 +60,18 @@ func TestRender_Images(t *testing.T) {
 
 	test("[[file:"+url+"]]",
 		"<p><img src=\""+result+"\" alt=\""+result+"\" title=\""+result+"\" /></p>")
+
+	// With description.
+	test("[[https://example.com][https://example.com/example.svg]]",
+		`<p><a href="https://example.com"><img src="https://example.com/example.svg" alt="https://example.com/example.svg" /></a></p>`)
+	test("[[https://example.com][https://example.com/example.mp4]]",
+		`<p><a href="https://example.com"><video src="https://example.com/example.mp4" title="https://example.com/example.mp4"></video></a></p>`)
+
+	// Without description.
+	test("[[https://example.com/example.svg]]",
+		`<p><img src="https://example.com/example.svg" alt="https://example.com/example.svg" title="https://example.com/example.svg" /></p>`)
+	test("[[https://example.com/example.mp4]]",
+		`<p><video src="https://example.com/example.mp4" title="https://example.com/example.mp4">https://example.com/example.mp4</video></p>`)
 }
 
 func TestRender_Source(t *testing.T) {
diff --git a/modules/setting/database.go b/modules/setting/database.go
index d3e6afe1f8..fc7444e87b 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
diff --git a/modules/setting/service.go b/modules/setting/service.go
index 9099f74e52..ba8bf4b3c5 100644
--- a/modules/setting/service.go
+++ b/modules/setting/service.go
@@ -67,6 +67,7 @@ var Service = struct {
 	DefaultKeepEmailPrivate                 bool
 	DefaultAllowCreateOrganization          bool
 	DefaultUserIsRestricted                 bool
+	AllowDotsInUsernames                    bool
 	EnableTimetracking                      bool
 	DefaultEnableTimetracking               bool
 	DefaultEnableDependencies               bool
@@ -178,6 +179,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/locales/gitea_en-US.ini b/options/locales/gitea_en-US.ini
index 48b5ca9aa6..c5d11ffbd4 100644
--- a/options/locales/gitea_en-US.ini
+++ b/options/locales/gitea_en-US.ini
@@ -292,6 +292,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
@@ -532,6 +533,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/routers/install/install.go b/routers/install/install.go
index cacd74479f..65af5db0bf 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
diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go
index 138df45857..a658d7e4cd 100644
--- a/routers/web/repo/release.go
+++ b/routers/web/repo/release.go
@@ -387,7 +387,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/routers/web/repo/view.go b/routers/web/repo/view.go
index 15c85f6427..5278f5b55d 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -164,7 +164,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)
@@ -343,7 +343,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/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/templates/admin/config.tmpl b/templates/admin/config.tmpl
index 36d9bcb8a5..6947ecfe8e 100644
--- a/templates/admin/config.tmpl
+++ b/templates/admin/config.tmpl
@@ -159,6 +159,8 @@
 				<dd>{{if .Service.DefaultKeepEmailPrivate}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
 				<dt>{{.locale.Tr "admin.config.default_allow_create_organization"}}</dt>
 				<dd>{{if .Service.DefaultAllowCreateOrganization}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
+				<dt>{{.locale.Tr "admin.config.allow_dots_in_usernames"}}</dt>
+				<dd>{{if .Service.AllowDotsInUsernames}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
 				<dt>{{.locale.Tr "admin.config.enable_timetracking"}}</dt>
 				<dd>{{if .Service.EnableTimetracking}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
 				{{if .Service.EnableTimetracking}}
diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl
index c3645209cd..08c68752e2 100644
--- a/templates/base/head.tmpl
+++ b/templates/base/head.tmpl
@@ -2,7 +2,8 @@
 <html lang="{{ctx.Locale.Lang}}" class="theme-{{if .SignedUser.Theme}}{{.SignedUser.Theme}}{{else}}{{DefaultTheme}}{{end}}">
 <head>
 	<meta name="viewport" content="width=device-width, initial-scale=1">
-	<title>{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}}</title>
+	{{/* Display `- .Repsository.FullName` only if `.Title` does not already start with that. */}}
+	<title>{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if and (.Repository.Name) (not (StringUtils.HasPrefix .Title .Repository.FullName))}}{{.Repository.FullName}} - {{end}}{{AppName}}</title>
 	{{if .ManifestData}}<link rel="manifest" href="data:{{.ManifestData}}">{{end}}
 	<meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}">
 	<meta name="description" content="{{if .Repository}}{{.Repository.Name}}{{if .Repository.Description}} - {{.Repository.Description}}{{end}}{{else}}{{MetaDescription}}{{end}}">
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index d43979ff59..0f77e0254a 100644
--- a/templates/repo/issue/view_content/comments.tmpl
+++ b/templates/repo/issue/view_content/comments.tmpl
@@ -364,7 +364,7 @@
 				{{end}}
 			</div>
 		{{else if eq .Type 22}}
-			<div class="timeline-item-group">
+			<div class="timeline-item-group" id="{{.HashTag}}">
 				<div class="timeline-item event">
 					{{if .OriginalAuthor}}
 					{{else}}
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 0000000000..567284ef1c
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo59.git/objects/40/8bbd3bd1f96950f8cf2f98c479557f6b18817a differ
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 0000000000..f23960f4cc
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo59.git/objects/5d/5c87a90af64cc67f22d60a942d5efaef8bc96b differ
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 0000000000..46cc9e3e5e
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo59.git/objects/88/3e2970ed6937cbb63311e941adb97df0ae3a52 differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/8c/ac7a8f434451410cc91ab9c04d07baff974ad8 b/tests/gitea-repositories-meta/user2/repo59.git/objects/8c/ac7a8f434451410cc91ab9c04d07baff974ad8
new file mode 100644
index 0000000000..5a1e79326f
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo59.git/objects/8c/ac7a8f434451410cc91ab9c04d07baff974ad8 differ
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 0000000000..3b71228a7e
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo59.git/objects/a0/ccafed39086ef520be6886d9395eb2100d317e differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/ab/e2a9ddfd7f542ff89bc13960a929dc8ca86c99 b/tests/gitea-repositories-meta/user2/repo59.git/objects/ab/e2a9ddfd7f542ff89bc13960a929dc8ca86c99
new file mode 100644
index 0000000000..dcbd3b3eb9
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo59.git/objects/ab/e2a9ddfd7f542ff89bc13960a929dc8ca86c99 differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/cd/879fb6cf5b7bbe0fbc3a0ef44c8695fde89a56 b/tests/gitea-repositories-meta/user2/repo59.git/objects/cd/879fb6cf5b7bbe0fbc3a0ef44c8695fde89a56
new file mode 100644
index 0000000000..cd9cea6797
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo59.git/objects/cd/879fb6cf5b7bbe0fbc3a0ef44c8695fde89a56 differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/d8/f53dfb33f6ccf4169c34970b5e747511c18beb b/tests/gitea-repositories-meta/user2/repo59.git/objects/d8/f53dfb33f6ccf4169c34970b5e747511c18beb
new file mode 100644
index 0000000000..bea28c9f69
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo59.git/objects/d8/f53dfb33f6ccf4169c34970b5e747511c18beb differ
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 0000000000..b6803eb5a2
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo59.git/objects/f3/c1ec36c0e7605be54e71f24035caa675b7ba41 differ
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_admin_test.go b/tests/integration/api_admin_test.go
index 6613d4b715..423a27eb52 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,54 @@ 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.NotEmpty(t, resp.Header().Get("X-Total-Count"))
+
+		var crons []api.Cron
+		DecodeJSON(t, resp, &crons)
+
+		assert.NotEmpty(t, crons)
+	})
+
+	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))
+			}
+		}
+	})
+}
diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go
index 3933298f23..43fab43f5b 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 670272ee4d..d5ff89e703 100644
--- a/tests/integration/integration_test.go
+++ b/tests/integration/integration_test.go
@@ -547,3 +547,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/release_test.go b/tests/integration/release_test.go
index 8de761ea6c..1c4be6a927 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":    "",
 	}
@@ -239,3 +243,12 @@ func TestViewTagsList(t *testing.T) {
 
 	assert.EqualValues(t, []string{"v1.0", "delete-tag", "v1.1"}, tagNames)
 }
+
+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)
+}
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)
+		})
+	}
 }
diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go
index 9ace3ca30c..cccfd4b7bb 100644
--- a/tests/integration/repo_test.go
+++ b/tests/integration/repo_test.go
@@ -444,3 +444,107 @@ func TestGeneratedSourceLink(t *testing.T) {
 		assert.Equal(t, "/user27/repo49/src/commit/aacbdfe9e1c4b47f60abe81849045fa4e96f1d75/test/test.txt", dataURL)
 	})
 }
+
+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)
+		})
+	})
+}
diff --git a/web_src/css/repo/issue-list.css b/web_src/css/repo/issue-list.css
index f76b690cb1..c458bf525b 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-body .branches {