From 80f8620d0d746c7ce5e88eeef3ec62431c399ec8 Mon Sep 17 00:00:00 2001
From: Gusted <postmaster@gusted.xyz>
Date: Fri, 18 Aug 2023 11:21:24 +0200
Subject: [PATCH] [GITEA] Improve HTML title on repositories

- The `<title>` 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)
(cherry picked from commit d706d9e222469c689eb069ec609968296657dfdc)
(cherry picked from commit 6d46261a3f81d3642b313e76ad93c5f72fbd6bf8)
(cherry picked from commit f864d18ad30760bd1e2fb1925b87b19e3208ad97)
---
 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 4dfa01d8e2..78517a6914 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}}</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/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<s#GY?EXhzvR7lB8OG_<E%_~vJ&df_G
VR>)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-<TbEo)G=iuke$D>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
zcmb<m^geacKghr=?SqG|SEzoN#<}ypp&s78I+{SPho`Qe-YFf8lV{Ffe$2!mca2X0
E0Le2FOaK4?

literal 0
HcmV?d00001

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 0000000000000000000000000000000000000000..5a1e79326f2c2ee1a4c451167389f5096eabdf72
GIT binary patch
literal 120
zcmV-;0Ehp00V^p=O;s>7FlR6{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{Q3<f7D)_~OLU<ka}0)a1;9RK1dl5{9$w^S7OkuHMJL$BOTZPutYLFJ^@S
I0N_g~9D6=E761SM

literal 0
HcmV?d00001

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 0000000000000000000000000000000000000000..dcbd3b3eb9f03f5d60d082bd174cf3e8d07c6e8a
GIT binary patch
literal 36
rcmb<m^geacKghr+As~$7q(`WRpRb;mZlH#rj;~+n6DEer296E@-c1XM

literal 0
HcmV?d00001

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 0000000000000000000000000000000000000000..cd9cea6797da4bacfae03d6da3b053b81d969db2
GIT binary patch
literal 108
zcmV-y0F(cC0VRwv4udcd1XKGJ+abA#LWg(<d4O#eumBtRE|K_sPD(jDtBFUA=wHKQ
z+6ZB4R5Yf1adggmyi14&p%t^q`_d8rXPMbLvGto1u?0b?YK5Zs2-IpjZ^2TVK^B}8
O2`iuQ-u(dQ948US(J)2;

literal 0
HcmV?d00001

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 0000000000000000000000000000000000000000..bea28c9f69b13925696567043b55ee3a544ce94c
GIT binary patch
literal 784
zcmV+r1MmEJ0hN=<uB%1>M6>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_<N6zqB~{6bz|(
z88^4w(!a1&Jl%ge1xO9gmJ0>NxV$YbR02+h-W<i4Z3HuNB`n?WDPMf^Mf~0>y>8SN
zL-n>NnOHxD1_ktBKMTFMF~Oeo44c$Nx1zjPg-n1^s{~0y49o8@?{IqT_1l9F<ml+Q
z9C}RBeGn<|zST=%w;6q?N4)b-XXc8BYB)(@zga5$><Ig{cl7%sA$kIRHm~J}{?`20
z`NP{m$x&d*S7)08xz__Sb<D?XcDfv)v6OGKxlD#%XJSAuT`t5cYAfG8x2j*Vewi12
zTJ2^Ea1neQx#)5*l>DBH+t<aF%ENli#a$-YU1bW@izqP7T$`cw$TekB##S1PNpyTh
z+k*mF^zUgl%VLdhnmP+=@!?Ac>z~X}{gyB7!qm$KrCW*^)AW)NzRtpG4}}%%=|SNs
z@Nz?3mnX@zAyYKZV*tH#nR{bxS}d9i?M(85HRz`|j6_MUVShjg2W|Ppe8h&<LkhU9
z`dWpstae%^_i+Gs#s*K5<(k3j78iSEG>7*MdL7JHfOBCJr|RNl?>o$nk$Jg%Kk7Ay
z>ilZ^!I!SZjq{9}32tH%t5<moRy?*E(a}TN4~8WWew8kHGc1@^G5^rQS*HLDtNP<q
zH4^z!KGR{%CV7@+SIOuqcS&zzJ_#CxU$9z^2UsDM%&P1dVXr;XOwv;d$eaD_+~sl9
z`_bldN7Pg|IhDS5$9pE_7Ok3Zva>4`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 fa159c6c5b..d5be5039f2 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 f6d028df2c..11f6332d61 100644
--- a/tests/integration/integration_test.go
+++ b/tests/integration/integration_test.go
@@ -530,3 +530,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)()