From ca9c039ba6117460cdf39d30ecb521d7569c8770 Mon Sep 17 00:00:00 2001
From: Renovate Bot <forgejo-renovate-action@forgejo.org>
Date: Fri, 26 Apr 2024 02:05:51 +0000
Subject: [PATCH 001/107] Update module github.com/minio/minio-go/v7 to v7.0.70

---
 go.mod | 3 +--
 go.sum | 6 ++----
 2 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/go.mod b/go.mod
index 2f4c64b2da..4f9028fe68 100644
--- a/go.mod
+++ b/go.mod
@@ -73,7 +73,7 @@ require (
 	github.com/meilisearch/meilisearch-go v0.26.1
 	github.com/mholt/archiver/v3 v3.5.1
 	github.com/microcosm-cc/bluemonday v1.0.26
-	github.com/minio/minio-go/v7 v7.0.69
+	github.com/minio/minio-go/v7 v7.0.70
 	github.com/msteinert/pam v1.2.0
 	github.com/nektos/act v0.2.52
 	github.com/niklasfasching/go-org v1.7.0
@@ -222,7 +222,6 @@ require (
 	github.com/mholt/acmez v1.2.0 // indirect
 	github.com/miekg/dns v1.1.58 // 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/go.sum b/go.sum
index ce55e89399..9897914ab2 100644
--- a/go.sum
+++ b/go.sum
@@ -618,10 +618,8 @@ github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
 github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
 github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
 github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
-github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
-github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
-github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
-github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
+github.com/minio/minio-go/v7 v7.0.70 h1:1u9NtMgfK1U42kUxcsl5v0yj6TEOPR497OAQxpJnn2g=
+github.com/minio/minio-go/v7 v7.0.70/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo=
 github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
 github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=

From 9035b400a6b07b0206e3b90903236b6999a4bfae Mon Sep 17 00:00:00 2001
From: Beowulf <beowulf@beocode.eu>
Date: Thu, 25 Apr 2024 00:19:28 +0200
Subject: [PATCH 002/107] UI: use full screen height for displaying pdf files

---
 release-notes/8.0.0/feat/3434.md | 1 +
 web_src/css/repo.css             | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 release-notes/8.0.0/feat/3434.md

diff --git a/release-notes/8.0.0/feat/3434.md b/release-notes/8.0.0/feat/3434.md
new file mode 100644
index 0000000000..a8b28eb1c3
--- /dev/null
+++ b/release-notes/8.0.0/feat/3434.md
@@ -0,0 +1 @@
+When PDFs are displayed in the repository, the [full height of the screen](https://codeberg.org/forgejo/forgejo/pulls/3434) is now used instead of a predefined fixed height
diff --git a/web_src/css/repo.css b/web_src/css/repo.css
index 87dbeb5bba..d443ee0575 100644
--- a/web_src/css/repo.css
+++ b/web_src/css/repo.css
@@ -402,7 +402,7 @@ td .commit-summary {
 
 .pdf-content {
   width: 100%;
-  height: 600px;
+  height: 100vh;
   border: none !important;
   display: flex;
   align-items: center;

From dad16cd5895a945c067ec6a2dacdac67afc8aa04 Mon Sep 17 00:00:00 2001
From: Gerard Salvatella <codeberg@wish.yt>
Date: Mon, 22 Apr 2024 04:53:04 +0200
Subject: [PATCH 003/107] fix(Dockerfile.rootless): revert to default path for
 `app.ini`

The current path of the `$GITEA_APP_INI` configuration file makes the
forgejo application reset every time the container is restarted, unless
a specific volume for this file is created. Consider the following:

* This quirk is not documented
* All configuration data resides in `/var/lib/gitea`
* The custom configuration path defaults to `/var/lib/gitea/custom/conf`
  (see `forgejo -h`)
* Containers mounting the volume `-v /foo/bar:/var/lib/gitea` already
  have this file available to modify. Another volume shouldn't be
  required
* Containers using named volumes can use `docker cp` to modify the file
  inside the volume, if desired

For these reasons, it makes more sense to use the default path for
`$GITEA_APP_INI` rather than require users to create a dedicated volume
for the file. Revert it back to its default while maintaining backwards
compatibility (users can update by simply moving the file to the new
path).
---
 Dockerfile.rootless                                |  7 +++++--
 docker/rootless/usr/local/bin/docker-entrypoint.sh |  5 +++++
 docker/rootless/usr/local/bin/docker-setup.sh      | 12 ++++++++++++
 release-notes/8.0.0/fix/3363.md                    |  6 ++++++
 4 files changed, 28 insertions(+), 2 deletions(-)
 create mode 100644 release-notes/8.0.0/fix/3363.md

diff --git a/Dockerfile.rootless b/Dockerfile.rootless
index 3f4cba955a..6d1503f034 100644
--- a/Dockerfile.rootless
+++ b/Dockerfile.rootless
@@ -100,8 +100,11 @@ ENV GITEA_CUSTOM /var/lib/gitea/custom
 ENV GITEA_TEMP /tmp/gitea
 ENV TMPDIR /tmp/gitea
 
-#TODO add to docs the ability to define the ini to load (useful to test and revert a config)
-ENV GITEA_APP_INI /etc/gitea/app.ini
+# Legacy config file for backwards compatibility
+# TODO: remove on next major version release
+ENV GITEA_APP_INI_LEGACY /etc/gitea/app.ini
+
+ENV GITEA_APP_INI ${GITEA_CUSTOM}/conf/app.ini
 ENV HOME "/var/lib/gitea/git"
 VOLUME ["/var/lib/gitea", "/etc/gitea"]
 WORKDIR /var/lib/gitea
diff --git a/docker/rootless/usr/local/bin/docker-entrypoint.sh b/docker/rootless/usr/local/bin/docker-entrypoint.sh
index ca509214bf..e5fa41cc78 100755
--- a/docker/rootless/usr/local/bin/docker-entrypoint.sh
+++ b/docker/rootless/usr/local/bin/docker-entrypoint.sh
@@ -13,5 +13,10 @@ fi
 if [ $# -gt 0 ]; then
     exec "$@"
 else
+    # TODO: remove on next major version release
+    # Honour legacy config file if existing
+    if [ -f ${GITEA_APP_INI_LEGACY} ]; then
+        GITEA_APP_INI=${GITEA_APP_INI_LEGACY}
+    fi
     exec /usr/local/bin/gitea -c ${GITEA_APP_INI} web
 fi
diff --git a/docker/rootless/usr/local/bin/docker-setup.sh b/docker/rootless/usr/local/bin/docker-setup.sh
index b480685863..09bbeabc63 100755
--- a/docker/rootless/usr/local/bin/docker-setup.sh
+++ b/docker/rootless/usr/local/bin/docker-setup.sh
@@ -11,6 +11,18 @@ mkdir -p ${GITEA_CUSTOM} && chmod 0700 ${GITEA_CUSTOM}
 mkdir -p ${GITEA_TEMP} && chmod 0700 ${GITEA_TEMP}
 if [ ! -w ${GITEA_TEMP} ]; then echo "${GITEA_TEMP} is not writable"; exit 1; fi
 
+# TODO: remove on next major version release
+# Honour legacy config file if existing, but inform the user
+if [ -f ${GITEA_APP_INI_LEGACY} ] && [ ${GITEA_APP_INI} != ${GITEA_APP_INI_LEGACY} ]; then
+    GITEA_APP_INI_DEFAULT=/var/lib/gitea/custom/conf/app.ini
+    echo -e \
+      "\033[33mWARNING\033[0m: detected configuration file in deprecated default path ${GITEA_APP_INI_LEGACY}." \
+      "The new default is ${GITEA_APP_INI_DEFAULT}. To remove this warning, choose one of the options:\n" \
+      "* Move ${GITEA_APP_INI_LEGACY} to ${GITEA_APP_INI_DEFAULT} (or to \$GITEA_APP_INI if you want to override this variable)\n" \
+      "* Explicitly override GITEA_APP_INI=${GITEA_APP_INI_LEGACY} in the container environment"
+    GITEA_APP_INI=${GITEA_APP_INI_LEGACY}
+fi
+
 #Prepare config file
 if [ ! -f ${GITEA_APP_INI} ]; then
 
diff --git a/release-notes/8.0.0/fix/3363.md b/release-notes/8.0.0/fix/3363.md
new file mode 100644
index 0000000000..65b516cabc
--- /dev/null
+++ b/release-notes/8.0.0/fix/3363.md
@@ -0,0 +1,6 @@
+Reverted the rootless container image path in `GITEA_APP_INI` from
+`/etc/gitea/app.ini` to its default value of
+`/var/lib/gitea/custom/conf/app.ini`. This allows container users to not have
+to mount two separate volumes (one for the configuration data and one for the
+configuration `.ini` file). A warning is issued for users with the legacy
+configuration on how to update to the new path.

From f9628f883df485af1c208345d55a956db741eb76 Mon Sep 17 00:00:00 2001
From: Beowulf <beowulf@beocode.eu>
Date: Thu, 25 Apr 2024 22:19:24 +0200
Subject: [PATCH 004/107] Move settings button back to the right in repo and
 org header This will move the settings button back to the right, like known
 from older versions. For this, the overflow-menu was changed when a setting
 button is available. If no settings button is available, the behavior will
 not change.

Fixes #3301
---
 templates/org/menu.tmpl                     |   2 +-
 templates/repo/header.tmpl                  |   6 +-
 tests/e2e/right-settings-button.test.e2e.js | 114 ++++++++++++++++++++
 web_src/js/webcomponents/overflow-menu.js   |  24 ++++-
 4 files changed, 141 insertions(+), 5 deletions(-)
 create mode 100644 tests/e2e/right-settings-button.test.e2e.js

diff --git a/templates/org/menu.tmpl b/templates/org/menu.tmpl
index c519606d1f..1860a3765a 100644
--- a/templates/org/menu.tmpl
+++ b/templates/org/menu.tmpl
@@ -40,7 +40,7 @@
 			</a>
 			{{end}}
 			{{if .IsOrganizationOwner}}
-			<a class="{{if .PageIsOrgSettings}}active {{end}}item" href="{{.OrgLink}}/settings">
+			<a id="settings-btn" class="{{if .PageIsOrgSettings}}active {{end}}right item" href="{{.OrgLink}}/settings">
 			{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
 			</a>
 			{{end}}
diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl
index bbc3b9c008..95c2e059d5 100644
--- a/templates/repo/header.tmpl
+++ b/templates/repo/header.tmpl
@@ -177,18 +177,18 @@
 					{{$highlightSettings := true}}
 					{{if and .SignedUser.EnableRepoUnitHints (not (.Repository.AllUnitsEnabled ctx))}}
 						{{$highlightSettings = false}}
-						<a class="{{if .PageIsRepoSettingsUnits}}active {{end}}item" href="{{.RepoLink}}/settings/units">
+						<a id="settings-btn" class="{{if .PageIsRepoSettingsUnits}}active {{end}}right item" href="{{.RepoLink}}/settings/units">
 							{{svg "octicon-diff-added"}} {{ctx.Locale.Tr "repo.settings.units.add_more"}}
 						</a>
 					{{end}}
-					<a class="{{if and .PageIsRepoSettings (or $highlightSettings (not .PageIsRepoSettingsUnits))}}active {{end}} item" href="{{.RepoLink}}/settings">
+					<a id="settings-btn" class="{{if and .PageIsRepoSettings (or $highlightSettings (not .PageIsRepoSettingsUnits))}}active {{end}}right item" href="{{.RepoLink}}/settings">
 						{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
 					</a>
 				{{end}}
 			</div>
 		{{else if .Permission.IsAdmin}}
 			<div class="overflow-menu-items">
-				<a class="{{if .PageIsRepoSettings}}active {{end}} item" href="{{.RepoLink}}/settings">
+				<a id="settings-btn" class="{{if .PageIsRepoSettings}}active {{end}}right item" href="{{.RepoLink}}/settings">
 					{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
 				</a>
 			</div>
diff --git a/tests/e2e/right-settings-button.test.e2e.js b/tests/e2e/right-settings-button.test.e2e.js
new file mode 100644
index 0000000000..7f457dec4a
--- /dev/null
+++ b/tests/e2e/right-settings-button.test.e2e.js
@@ -0,0 +1,114 @@
+// @ts-check
+import {test, expect} from '@playwright/test';
+import {login_user, load_logged_in_context} from './utils_e2e.js';
+
+test.beforeAll(async ({browser}, workerInfo) => {
+  await login_user(browser, workerInfo, 'user2');
+});
+
+test.describe('desktop viewport', () => {
+  test.use({viewport: {width: 1920, height: 300}});
+
+  test('Settings button on right of repo header', async ({browser}, workerInfo) => {
+    const context = await load_logged_in_context(browser, workerInfo, 'user2');
+    const page = await context.newPage();
+
+    await page.goto('/user2/repo1');
+
+    const settingsBtn = page.locator('.overflow-menu-items>#settings-btn');
+    await expect(settingsBtn).toBeVisible();
+    await expect(settingsBtn).toHaveClass(/right/);
+
+    await expect(page.locator('.overflow-menu-button')).toHaveCount(0);
+  });
+
+  test('Settings button on right of org header', async ({browser}, workerInfo) => {
+    const context = await load_logged_in_context(browser, workerInfo, 'user2');
+    const page = await context.newPage();
+
+    await page.goto('/org3');
+
+    const settingsBtn = page.locator('.overflow-menu-items>#settings-btn');
+    await expect(settingsBtn).toBeVisible();
+    await expect(settingsBtn).toHaveClass(/right/);
+
+    await expect(page.locator('.overflow-menu-button')).toHaveCount(0);
+  });
+
+  test('User overview overflow menu should not be influenced', async ({page}) => {
+    await page.goto('/user2');
+
+    await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0);
+
+    await expect(page.locator('.overflow-menu-button')).toHaveCount(0);
+  });
+});
+
+test.describe('small viewport', () => {
+  test.use({viewport: {width: 800, height: 300}});
+
+  test('Settings button in overflow menu of repo header', async ({browser}, workerInfo) => {
+    const context = await load_logged_in_context(browser, workerInfo, 'user2');
+    const page = await context.newPage();
+
+    await page.goto('/user2/repo1');
+
+    await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0);
+
+    await expect(page.locator('.overflow-menu-button')).toBeVisible();
+
+    await page.click('.overflow-menu-button');
+    await expect(page.locator('.tippy-target>#settings-btn')).toBeVisible();
+
+    // Verify that we have no duplicated items
+    const shownItems = await page.locator('.overflow-menu-items>a').all();
+    expect(shownItems).not.toHaveLength(0);
+    const overflowItems = await page.locator('.tippy-target>a').all();
+    expect(overflowItems).not.toHaveLength(0);
+
+    const items = shownItems.concat(overflowItems);
+    expect(Array.from(new Set(items))).toHaveLength(items.length);
+  });
+
+  test('Settings button in overflow menu of org header', async ({browser}, workerInfo) => {
+    const context = await load_logged_in_context(browser, workerInfo, 'user2');
+    const page = await context.newPage();
+
+    await page.goto('/org3');
+
+    await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0);
+
+    await expect(page.locator('.overflow-menu-button')).toBeVisible();
+
+    await page.click('.overflow-menu-button');
+    await expect(page.locator('.tippy-target>#settings-btn')).toBeVisible();
+
+    // Verify that we have no duplicated items
+    const shownItems = await page.locator('.overflow-menu-items>a').all();
+    expect(shownItems).not.toHaveLength(0);
+    const overflowItems = await page.locator('.tippy-target>a').all();
+    expect(overflowItems).not.toHaveLength(0);
+
+    const items = shownItems.concat(overflowItems);
+    expect(Array.from(new Set(items))).toHaveLength(items.length);
+  });
+
+  test('User overview overflow menu should not be influenced', async ({page}) => {
+    await page.goto('/user2');
+
+    await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0);
+
+    await expect(page.locator('.overflow-menu-button')).toBeVisible();
+    await page.click('.overflow-menu-button');
+    await expect(page.locator('.tippy-target>#settings-btn')).toHaveCount(0);
+
+    // Verify that we have no duplicated items
+    const shownItems = await page.locator('.overflow-menu-items>a').all();
+    expect(shownItems).not.toHaveLength(0);
+    const overflowItems = await page.locator('.tippy-target>a').all();
+    expect(overflowItems).not.toHaveLength(0);
+
+    const items = shownItems.concat(overflowItems);
+    expect(Array.from(new Set(items))).toHaveLength(items.length);
+  });
+});
diff --git a/web_src/js/webcomponents/overflow-menu.js b/web_src/js/webcomponents/overflow-menu.js
index 604fce7d4b..a69ce1681c 100644
--- a/web_src/js/webcomponents/overflow-menu.js
+++ b/web_src/js/webcomponents/overflow-menu.js
@@ -69,13 +69,35 @@ window.customElements.define('overflow-menu', class extends HTMLElement {
     this.tippyItems = [];
     const menuRight = this.offsetLeft + this.offsetWidth;
     const menuItems = this.menuItemsEl.querySelectorAll('.item');
+    const settingItem = this.menuItemsEl.querySelector('#settings-btn');
     for (const item of menuItems) {
       const itemRight = item.offsetLeft + item.offsetWidth;
-      if (menuRight - itemRight < 38) { // roughly the width of .overflow-menu-button
+      // Width of the settings button plus a small value to get the next item to the left if there is directly one
+      // If no setting button is in the menu the default threshold is 38 - roughly the width of .overflow-menu-button
+      const overflowBtnThreshold = 38;
+      const threshold = settingItem?.offsetWidth ?? overflowBtnThreshold;
+      // If we have a settings item on the right-hand side, we must also check if the first,
+      // possibly overflowing item would still fit on the left-hand side of the overflow menu
+      // If not, it must be added to the array (twice). The duplicate is removed with the shift.
+      if (settingItem && !this.tippyItems?.length && item !== settingItem && menuRight - itemRight < overflowBtnThreshold) {
+        this.tippyItems.push(settingItem);
+      }
+      if (menuRight - itemRight < threshold) {
         this.tippyItems.push(item);
       }
     }
 
+    // Special handling for settings button on right. Only done if a setting item is present
+    if (settingItem) {
+      // If less than 2 items overflow, remove all items (only settings "overflowed" - because it's on the right side)
+      if (this.tippyItems?.length < 2) {
+        this.tippyItems = [];
+      } else {
+        // Remove the first item of the list, because we have always one item more in the array due to the big threshold above
+        this.tippyItems.shift();
+      }
+    }
+
     // if there are no overflown items, remove any previously created button
     if (!this.tippyItems?.length) {
       const btn = this.querySelector('.overflow-menu-button');

From a5df62209924b9b1fe8dbd31c2455212c8fa2b50 Mon Sep 17 00:00:00 2001
From: Earl Warren <contact@earl-warren.org>
Date: Fri, 26 Apr 2024 15:17:45 +0200
Subject: [PATCH 005/107] docs(release-notes): 7.0.1

---
 RELEASE-NOTES.md                | 15 ++++++++++++++-
 release-notes/8.0.0/fix/3399.md |  1 -
 release-notes/8.0.0/fix/3444.md |  1 -
 release-notes/8.0.0/fix/3451.md |  1 -
 4 files changed, 14 insertions(+), 4 deletions(-)
 delete mode 100644 release-notes/8.0.0/fix/3399.md
 delete mode 100644 release-notes/8.0.0/fix/3444.md
 delete mode 100644 release-notes/8.0.0/fix/3451.md

diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 85c8f8bcc1..9980338629 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -8,6 +8,19 @@ A [patch or minor release](https://semver.org/spec/v2.0.0.html) (e.g. upgrading
 
 - [8.0.0](/release-notes/8.0.0/)
 
+## 7.0.1
+
+This is a bug fix release. See the documentation for more information on the [upgrade procedure](https://forgejo.org/docs/v7.0/admin/upgrade/).
+
+In addition to the following notable bug fixes, you can browse the [full list of commits](https://codeberg.org/forgejo/forgejo/compare/v7.0.0...v7.0.1) included in this release.
+
+* **Bug fixes:**
+  * [PR](https://codeberg.org/forgejo/forgejo/pulls/3466): LFS data corruption when running the [`forgejo doctor check --fix`](https://forgejo.org/docs/v7.0/admin/command-line/#doctor-check) CLI command or setting [`[cron.gc_lfs].ENABLED=true`](https://forgejo.org/docs/v7.0/admin/config-cheat-sheet/#cron---garbage-collect-lfs-pointers-in-repositories-crongc_lfs) (the default is `false`).
+  * [PR](https://codeberg.org/forgejo/forgejo/pulls/3412): [non backward compatible change](https://codeberg.org/forgejo/forgejo/issues/3399) in the [`forgejo admin user create`](https://forgejo.org/docs/v7.0/admin/command-line/#admin-user-create) CLI command.
+  * [PR](https://codeberg.org/forgejo/forgejo/pulls/3448): error 500 because of an incorrect evaluation of the template when visiting the LFS settings of a repository.
+  * [PR](https://codeberg.org/forgejo/forgejo/pulls/3464): `GET /repos/{owner}/{name}` API endpoint [always returns an empty string for the `object_format_name` field](https://codeberg.org/forgejo/forgejo/issues/3458).
+  * [PR](https://codeberg.org/forgejo/forgejo/pulls/3444): fuzzy search [may fail with bleve](https://codeberg.org/forgejo/forgejo/issues/3443).
+
 ## 7.0.0
 
 The [complete list of commits](https://codeberg.org/forgejo/forgejo/commits/branch/v7.0/forgejo) included in the `Forgejo v7.0.0` release can be reviewed from the command line with:
@@ -19,7 +32,7 @@ $ git -C forgejo log --oneline --no-merges origin/v1.21/forgejo..origin/v7.0/for
 
 * **Regressions and workarounds:**
   * Running the [`forgejo doctor check --fix`](https://forgejo.org/docs/v7.0/admin/command-line/#doctor-check) CLI command or setting [`[cron.gc_lfs].ENABLED=true`](https://forgejo.org/docs/v7.0/admin/config-cheat-sheet/#cron---garbage-collect-lfs-pointers-in-repositories-crongc_lfs) (the default is `false`) will corrupt the LFS storage. The workaround is to not run the doctor CLI command and disable the `cron.gc_lfs`. This regression will be [fixed in 7.0.1](https://codeberg.org/forgejo/forgejo/issues/3438).
-  * The [`fogejo admin user create`](https://forgejo.org/docs/v7.0/admin/command-line/#admin-user-create) CLI command [requires a password](https://codeberg.org/forgejo/forgejo/commit/b122c6ef8b9254120432aed373cbe075331132ac) change by default when creating the first user and the `--admin` flag is not specified. The `--must-change-password=false` argument must be given to not require a password change. This regression will be [fixed in 7.0.1](https://codeberg.org/forgejo/forgejo/issues/3399).
+  * The [`forgejo admin user create`](https://forgejo.org/docs/v7.0/admin/command-line/#admin-user-create) CLI command [requires a password](https://codeberg.org/forgejo/forgejo/commit/b122c6ef8b9254120432aed373cbe075331132ac) change by default when creating the first user and the `--admin` flag is not specified. The `--must-change-password=false` argument must be given to not require a password change. This regression will be [fixed in 7.0.1](https://codeberg.org/forgejo/forgejo/issues/3399).
 * **Breaking changes requiring manual intervention:**
   * [MySQL 8.0 or PostgreSQL 12](https://codeberg.org/forgejo/forgejo/commit/e94f9fcafdcf284561e7fb33f60156a69c4ad6a5) are the minimum supported versions. The database must be migrated before upgrading. The requirements regarding SQLite did not change.
   * The `per_page` parameter is [no longer a synonym for `limit`](https://codeberg.org/forgejo/forgejo/commit/0aab2d38a7d91bc8caff332e452364468ce52d9a) in the [/repos/{owner}/{repo}/releases](https://code.forgejo.org/api/swagger/#/repository/repoListReleases) API endpoint.
diff --git a/release-notes/8.0.0/fix/3399.md b/release-notes/8.0.0/fix/3399.md
deleted file mode 100644
index d25d66d5f0..0000000000
--- a/release-notes/8.0.0/fix/3399.md
+++ /dev/null
@@ -1 +0,0 @@
-The regression in the [`fogejo admin user create`](https://forgejo.org/docs/v7.0/admin/command-line/#admin-user-create) CLI command [is fixed](https://codeberg.org/forgejo/forgejo/issues/3399) and it is backward compatible.
diff --git a/release-notes/8.0.0/fix/3444.md b/release-notes/8.0.0/fix/3444.md
deleted file mode 100644
index 4988fdad15..0000000000
--- a/release-notes/8.0.0/fix/3444.md
+++ /dev/null
@@ -1 +0,0 @@
-Fixed bleve indexer failing when [fuzziness exceeds the maximum 2](https://codeberg.org/forgejo/forgejo/pulls/3444)
diff --git a/release-notes/8.0.0/fix/3451.md b/release-notes/8.0.0/fix/3451.md
deleted file mode 100644
index e0c307e896..0000000000
--- a/release-notes/8.0.0/fix/3451.md
+++ /dev/null
@@ -1 +0,0 @@
-Fixed an error 500 when visiting [the LFS settings](https://codeberg.org/forgejo/forgejo/pulls/3451) at `/{owner}/{repo}/settings/lfs/find?oid=...`.

From 08f5a25d3b8104c6e73b7af32e4c0d23813fb539 Mon Sep 17 00:00:00 2001
From: Baptiste Daroussin <bapt@FreeBSD.org>
Date: Fri, 26 Apr 2024 22:38:58 +0000
Subject: [PATCH 006/107] ldap: default domain name (#3414)

When the ldap synchronizer is look for an email address and fails at
finding one, it falls back at creating one using "localhost.local"
domain.

This new field makes this domain name configurable.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3414
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Baptiste Daroussin <bapt@FreeBSD.org>
Co-committed-by: Baptiste Daroussin <bapt@FreeBSD.org>
---
 options/locale/locale_en-US.ini          |   1 +
 release-notes/8.0.0/3414.md              |   1 +
 routers/web/admin/auths.go               |   1 +
 services/auth/source/ldap/source.go      |   1 +
 services/auth/source/ldap/source_sync.go |   6 +-
 services/forms/auth_form.go              |   1 +
 templates/admin/auth/edit.tmpl           |   4 +
 templates/admin/auth/source/ldap.tmpl    |   4 +
 tests/integration/auth_ldap_test.go      | 102 +++++++++++++++++++----
 9 files changed, 105 insertions(+), 16 deletions(-)
 create mode 100644 release-notes/8.0.0/3414.md

diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index d7680f3545..7125d97d9b 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -3100,6 +3100,7 @@ auths.attribute_mail = Email attribute
 auths.attribute_ssh_public_key = Public SSH key attribute
 auths.attribute_avatar = Avatar attribute
 auths.attributes_in_bind = Fetch attributes in bind DN context
+auths.default_domain_name = Default domain name used for the email address
 auths.allow_deactivate_all = Allow an empty search result to deactivate all users
 auths.use_paged_search = Use paged search
 auths.search_page_size = Page size
diff --git a/release-notes/8.0.0/3414.md b/release-notes/8.0.0/3414.md
new file mode 100644
index 0000000000..2e10483e23
--- /dev/null
+++ b/release-notes/8.0.0/3414.md
@@ -0,0 +1 @@
+Allow to customize the domain name used as a fallback when synchronizing sources from ldap [`ldap: default domain name`](https://codeberg.org/forgejo/forgejo/pulls/3414)
diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go
index ba487d1045..799b7e8a84 100644
--- a/routers/web/admin/auths.go
+++ b/routers/web/admin/auths.go
@@ -129,6 +129,7 @@ func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source {
 		UserDN:                form.UserDN,
 		BindPassword:          form.BindPassword,
 		UserBase:              form.UserBase,
+		DefaultDomainName:     form.DefaultDomainName,
 		AttributeUsername:     form.AttributeUsername,
 		AttributeName:         form.AttributeName,
 		AttributeSurname:      form.AttributeSurname,
diff --git a/services/auth/source/ldap/source.go b/services/auth/source/ldap/source.go
index dc4cb2c940..ba407b351a 100644
--- a/services/auth/source/ldap/source.go
+++ b/services/auth/source/ldap/source.go
@@ -34,6 +34,7 @@ type Source struct {
 	BindPassword          string // Bind DN password
 	UserBase              string // Base search path for users
 	UserDN                string // Template for the DN of the user for simple auth
+	DefaultDomainName     string // DomainName used if none are in the field, default "localhost.local"
 	AttributeUsername     string // Username attribute
 	AttributeName         string // First name attribute
 	AttributeSurname      string // Surname attribute
diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go
index 62f052d68c..7c5d3da595 100644
--- a/services/auth/source/ldap/source_sync.go
+++ b/services/auth/source/ldap/source_sync.go
@@ -105,7 +105,11 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
 		}
 
 		if len(su.Mail) == 0 {
-			su.Mail = fmt.Sprintf("%s@localhost.local", su.Username)
+			domainName := source.DefaultDomainName
+			if len(domainName) == 0 {
+				domainName = "localhost.local"
+			}
+			su.Mail = fmt.Sprintf("%s@%s", su.Username, domainName)
 		}
 
 		fullName := composeFullName(su.Name, su.Surname, su.Username)
diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go
index c9f3182b3a..a3eca9473b 100644
--- a/services/forms/auth_form.go
+++ b/services/forms/auth_form.go
@@ -26,6 +26,7 @@ type AuthenticationForm struct {
 	AttributeUsername             string
 	AttributeName                 string
 	AttributeSurname              string
+	DefaultDomainName             string
 	AttributeMail                 string
 	AttributeSSHPublicKey         string
 	AttributeAvatar               string
diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl
index 687a277a84..8a8bd61148 100644
--- a/templates/admin/auth/edit.tmpl
+++ b/templates/admin/auth/edit.tmpl
@@ -97,6 +97,10 @@
 						<label for="attribute_mail">{{ctx.Locale.Tr "admin.auths.attribute_mail"}}</label>
 						<input id="attribute_mail" name="attribute_mail" value="{{$cfg.AttributeMail}}" placeholder="mail" required>
 					</div>
+					<div class="field">
+						<label for="default_domain_name">{{ctx.Locale.Tr "admin.auths.default_domain_name"}}</label>
+						<input id="default_domain_name" name="default_domain_name" value="{{$cfg.DefaultDomainName}}" placeholder="localhost.local" >
+					</div>
 					<div class="field">
 						<label for="attribute_ssh_public_key">{{ctx.Locale.Tr "admin.auths.attribute_ssh_public_key"}}</label>
 						<input id="attribute_ssh_public_key" name="attribute_ssh_public_key" value="{{$cfg.AttributeSSHPublicKey}}" placeholder="SshPublicKey">
diff --git a/templates/admin/auth/source/ldap.tmpl b/templates/admin/auth/source/ldap.tmpl
index 680849c8ea..6cb6643f26 100644
--- a/templates/admin/auth/source/ldap.tmpl
+++ b/templates/admin/auth/source/ldap.tmpl
@@ -71,6 +71,10 @@
 		<label for="attribute_mail">{{ctx.Locale.Tr "admin.auths.attribute_mail"}}</label>
 		<input id="attribute_mail" name="attribute_mail" value="{{.attribute_mail}}" placeholder="mail">
 	</div>
+	<div class="field">
+		<label for="default_domain_name">{{ctx.Locale.Tr "admin.auths.default_domain_name"}}</label>
+		<input id="default_domain_name" name="default_domain_name" value="{{.default_domain_name}}" placeholder="localhost.local">
+	</div>
 	<div class="field">
 		<label for="attribute_ssh_public_key">{{ctx.Locale.Tr "admin.auths.attribute_ssh_public_key"}}</label>
 		<input id="attribute_ssh_public_key" name="attribute_ssh_public_key" value="{{.attribute_ssh_public_key}}" placeholder="SshPublicKey">
diff --git a/tests/integration/auth_ldap_test.go b/tests/integration/auth_ldap_test.go
index 3a5fdb97a6..06677287c0 100644
--- a/tests/integration/auth_ldap_test.go
+++ b/tests/integration/auth_ldap_test.go
@@ -112,13 +112,17 @@ func getLDAPServerPort() string {
 	return port
 }
 
-func buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, groupFilter, groupTeamMap, groupTeamMapRemoval string) map[string]string {
+func buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, mailKeyAttribute, defaultDomainName, groupFilter, groupTeamMap, groupTeamMapRemoval string) map[string]string {
 	// Modify user filter to test group filter explicitly
 	userFilter := "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))"
 	if groupFilter != "" {
 		userFilter = "(&(objectClass=inetOrgPerson)(uid=%s))"
 	}
 
+	if len(mailKeyAttribute) == 0 {
+		mailKeyAttribute = "mail"
+	}
+
 	return map[string]string{
 		"_csrf":                    csrf,
 		"type":                     "2",
@@ -134,8 +138,9 @@ func buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, groupFilter, groupTeamMap
 		"attribute_username":       "uid",
 		"attribute_name":           "givenName",
 		"attribute_surname":        "sn",
-		"attribute_mail":           "mail",
+		"attribute_mail":           mailKeyAttribute,
 		"attribute_ssh_public_key": sshKeyAttribute,
+		"default_domain_name":      defaultDomainName,
 		"is_sync_enabled":          "on",
 		"is_active":                "on",
 		"groups_enabled":           "on",
@@ -148,7 +153,7 @@ func buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, groupFilter, groupTeamMap
 	}
 }
 
-func addAuthSourceLDAP(t *testing.T, sshKeyAttribute, groupFilter string, groupMapParams ...string) {
+func addAuthSourceLDAP(t *testing.T, sshKeyAttribute, mailKeyAttribute, defaultDomainName, groupFilter string, groupMapParams ...string) {
 	groupTeamMapRemoval := "off"
 	groupTeamMap := ""
 	if len(groupMapParams) == 2 {
@@ -157,7 +162,7 @@ func addAuthSourceLDAP(t *testing.T, sshKeyAttribute, groupFilter string, groupM
 	}
 	session := loginUser(t, "user1")
 	csrf := GetCSRF(t, session, "/admin/auths/new")
-	req := NewRequestWithValues(t, "POST", "/admin/auths/new", buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, groupFilter, groupTeamMap, groupTeamMapRemoval))
+	req := NewRequestWithValues(t, "POST", "/admin/auths/new", buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, mailKeyAttribute, defaultDomainName, groupFilter, groupTeamMap, groupTeamMapRemoval))
 	session.MakeRequest(t, req, http.StatusSeeOther)
 }
 
@@ -167,7 +172,7 @@ func TestLDAPUserSignin(t *testing.T) {
 		return
 	}
 	defer tests.PrepareTestEnv(t)()
-	addAuthSourceLDAP(t, "", "")
+	addAuthSourceLDAP(t, "", "", "", "")
 
 	u := gitLDAPUsers[0]
 
@@ -184,7 +189,7 @@ func TestLDAPUserSignin(t *testing.T) {
 
 func TestLDAPAuthChange(t *testing.T) {
 	defer tests.PrepareTestEnv(t)()
-	addAuthSourceLDAP(t, "", "")
+	addAuthSourceLDAP(t, "", "", "", "")
 
 	session := loginUser(t, "user1")
 	req := NewRequest(t, "GET", "/admin/auths")
@@ -205,7 +210,7 @@ func TestLDAPAuthChange(t *testing.T) {
 	binddn, _ := doc.Find(`input[name="bind_dn"]`).Attr("value")
 	assert.Equal(t, "uid=gitea,ou=service,dc=planetexpress,dc=com", binddn)
 
-	req = NewRequestWithValues(t, "POST", href, buildAuthSourceLDAPPayload(csrf, "", "", "", "off"))
+	req = NewRequestWithValues(t, "POST", href, buildAuthSourceLDAPPayload(csrf, "", "", "", "", "", "off"))
 	session.MakeRequest(t, req, http.StatusSeeOther)
 
 	req = NewRequest(t, "GET", href)
@@ -215,6 +220,21 @@ func TestLDAPAuthChange(t *testing.T) {
 	assert.Equal(t, host, getLDAPServerHost())
 	binddn, _ = doc.Find(`input[name="bind_dn"]`).Attr("value")
 	assert.Equal(t, "uid=gitea,ou=service,dc=planetexpress,dc=com", binddn)
+	domainname, _ := doc.Find(`input[name="default_domain_name"]`).Attr("value")
+	assert.Equal(t, "", domainname)
+
+	req = NewRequestWithValues(t, "POST", href, buildAuthSourceLDAPPayload(csrf, "", "", "test.org", "", "", "off"))
+	session.MakeRequest(t, req, http.StatusSeeOther)
+
+	req = NewRequest(t, "GET", href)
+	resp = session.MakeRequest(t, req, http.StatusOK)
+	doc = NewHTMLParser(t, resp.Body)
+	host, _ = doc.Find(`input[name="host"]`).Attr("value")
+	assert.Equal(t, host, getLDAPServerHost())
+	binddn, _ = doc.Find(`input[name="bind_dn"]`).Attr("value")
+	assert.Equal(t, "uid=gitea,ou=service,dc=planetexpress,dc=com", binddn)
+	domainname, _ = doc.Find(`input[name="default_domain_name"]`).Attr("value")
+	assert.Equal(t, "test.org", domainname)
 }
 
 func TestLDAPUserSync(t *testing.T) {
@@ -223,7 +243,7 @@ func TestLDAPUserSync(t *testing.T) {
 		return
 	}
 	defer tests.PrepareTestEnv(t)()
-	addAuthSourceLDAP(t, "", "")
+	addAuthSourceLDAP(t, "", "", "", "")
 	auth.SyncExternalUsers(context.Background(), true)
 
 	// Check if users exists
@@ -252,7 +272,7 @@ func TestLDAPUserSyncWithEmptyUsernameAttribute(t *testing.T) {
 
 	session := loginUser(t, "user1")
 	csrf := GetCSRF(t, session, "/admin/auths/new")
-	payload := buildAuthSourceLDAPPayload(csrf, "", "", "", "")
+	payload := buildAuthSourceLDAPPayload(csrf, "", "", "", "", "", "")
 	payload["attribute_username"] = ""
 	req := NewRequestWithValues(t, "POST", "/admin/auths/new", payload)
 	session.MakeRequest(t, req, http.StatusSeeOther)
@@ -300,7 +320,7 @@ func TestLDAPUserSyncWithGroupFilter(t *testing.T) {
 		return
 	}
 	defer tests.PrepareTestEnv(t)()
-	addAuthSourceLDAP(t, "", "(cn=git)")
+	addAuthSourceLDAP(t, "", "", "", "(cn=git)")
 
 	// Assert a user not a member of the LDAP group "cn=git" cannot login
 	// This test may look like TestLDAPUserSigninFailed but it is not.
@@ -359,7 +379,7 @@ func TestLDAPUserSigninFailed(t *testing.T) {
 		return
 	}
 	defer tests.PrepareTestEnv(t)()
-	addAuthSourceLDAP(t, "", "")
+	addAuthSourceLDAP(t, "", "", "", "")
 
 	u := otherLDAPUsers[0]
 	testLoginFailed(t, u.UserName, u.Password, translation.NewLocale("en-US").TrString("form.username_password_incorrect"))
@@ -371,7 +391,7 @@ func TestLDAPUserSSHKeySync(t *testing.T) {
 		return
 	}
 	defer tests.PrepareTestEnv(t)()
-	addAuthSourceLDAP(t, "sshPublicKey", "")
+	addAuthSourceLDAP(t, "sshPublicKey", "", "", "")
 
 	auth.SyncExternalUsers(context.Background(), true)
 
@@ -404,7 +424,7 @@ func TestLDAPGroupTeamSyncAddMember(t *testing.T) {
 		return
 	}
 	defer tests.PrepareTestEnv(t)()
-	addAuthSourceLDAP(t, "", "", "on", `{"cn=ship_crew,ou=people,dc=planetexpress,dc=com":{"org26": ["team11"]},"cn=admin_staff,ou=people,dc=planetexpress,dc=com": {"non-existent": ["non-existent"]}}`)
+	addAuthSourceLDAP(t, "", "", "", "", "on", `{"cn=ship_crew,ou=people,dc=planetexpress,dc=com":{"org26": ["team11"]},"cn=admin_staff,ou=people,dc=planetexpress,dc=com": {"non-existent": ["non-existent"]}}`)
 	org, err := organization.GetOrgByName(db.DefaultContext, "org26")
 	assert.NoError(t, err)
 	team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11")
@@ -449,7 +469,7 @@ func TestLDAPGroupTeamSyncRemoveMember(t *testing.T) {
 		return
 	}
 	defer tests.PrepareTestEnv(t)()
-	addAuthSourceLDAP(t, "", "", "on", `{"cn=dispatch,ou=people,dc=planetexpress,dc=com": {"org26": ["team11"]}}`)
+	addAuthSourceLDAP(t, "", "", "", "", "on", `{"cn=dispatch,ou=people,dc=planetexpress,dc=com": {"org26": ["team11"]}}`)
 	org, err := organization.GetOrgByName(db.DefaultContext, "org26")
 	assert.NoError(t, err)
 	team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11")
@@ -487,6 +507,58 @@ func TestLDAPPreventInvalidGroupTeamMap(t *testing.T) {
 
 	session := loginUser(t, "user1")
 	csrf := GetCSRF(t, session, "/admin/auths/new")
-	req := NewRequestWithValues(t, "POST", "/admin/auths/new", buildAuthSourceLDAPPayload(csrf, "", "", `{"NOT_A_VALID_JSON"["MISSING_DOUBLE_POINT"]}`, "off"))
+	req := NewRequestWithValues(t, "POST", "/admin/auths/new", buildAuthSourceLDAPPayload(csrf, "", "", "", "", `{"NOT_A_VALID_JSON"["MISSING_DOUBLE_POINT"]}`, "off"))
 	session.MakeRequest(t, req, http.StatusOK) // StatusOK = failed, StatusSeeOther = ok
 }
+
+func TestLDAPUserSyncInvalidMail(t *testing.T) {
+	if skipLDAPTests() {
+		t.Skip()
+		return
+	}
+	defer tests.PrepareTestEnv(t)()
+	addAuthSourceLDAP(t, "", "nonexisting", "", "")
+	auth.SyncExternalUsers(context.Background(), true)
+
+	// Check if users exists
+	for _, gitLDAPUser := range gitLDAPUsers {
+		dbUser, err := user_model.GetUserByName(db.DefaultContext, gitLDAPUser.UserName)
+		assert.NoError(t, err)
+		assert.Equal(t, gitLDAPUser.UserName, dbUser.Name)
+		assert.Equal(t, gitLDAPUser.UserName+"@localhost.local", dbUser.Email)
+		assert.Equal(t, gitLDAPUser.IsAdmin, dbUser.IsAdmin)
+		assert.Equal(t, gitLDAPUser.IsRestricted, dbUser.IsRestricted)
+	}
+
+	// Check if no users exist
+	for _, otherLDAPUser := range otherLDAPUsers {
+		_, err := user_model.GetUserByName(db.DefaultContext, otherLDAPUser.UserName)
+		assert.True(t, user_model.IsErrUserNotExist(err))
+	}
+}
+
+func TestLDAPUserSyncInvalidMailDefaultDomain(t *testing.T) {
+	if skipLDAPTests() {
+		t.Skip()
+		return
+	}
+	defer tests.PrepareTestEnv(t)()
+	addAuthSourceLDAP(t, "", "nonexisting", "test.org", "")
+	auth.SyncExternalUsers(context.Background(), true)
+
+	// Check if users exists
+	for _, gitLDAPUser := range gitLDAPUsers {
+		dbUser, err := user_model.GetUserByName(db.DefaultContext, gitLDAPUser.UserName)
+		assert.NoError(t, err)
+		assert.Equal(t, gitLDAPUser.UserName, dbUser.Name)
+		assert.Equal(t, gitLDAPUser.UserName+"@test.org", dbUser.Email)
+		assert.Equal(t, gitLDAPUser.IsAdmin, dbUser.IsAdmin)
+		assert.Equal(t, gitLDAPUser.IsRestricted, dbUser.IsRestricted)
+	}
+
+	// Check if no users exist
+	for _, otherLDAPUser := range otherLDAPUsers {
+		_, err := user_model.GetUserByName(db.DefaultContext, otherLDAPUser.UserName)
+		assert.True(t, user_model.IsErrUserNotExist(err))
+	}
+}

From 01d9faefa5cf76c8ddbab6156547d615d5c5b8b3 Mon Sep 17 00:00:00 2001
From: Renovate Bot <forgejo-renovate-action@forgejo.org>
Date: Sat, 27 Apr 2024 00:07:16 +0000
Subject: [PATCH 007/107] Update module connectrpc.com/connect to v1.16.1

---
 go.mod | 2 +-
 go.sum | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index 2f4c64b2da..2492285502 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	code.gitea.io/gitea-vet v0.2.3
 	code.gitea.io/sdk/gitea v0.17.1
 	codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
-	connectrpc.com/connect v1.15.0
+	connectrpc.com/connect v1.16.1
 	gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669
 	gitea.com/go-chi/cache v0.2.0
 	gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
diff --git a/go.sum b/go.sum
index ce55e89399..3ada0845c4 100644
--- a/go.sum
+++ b/go.sum
@@ -43,8 +43,8 @@ code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8=
 code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM=
 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
-connectrpc.com/connect v1.15.0 h1:lFdeCbZrVVDydAqwr4xGV2y+ULn+0Z73s5JBj2LikWo=
-connectrpc.com/connect v1.15.0/go.mod h1:bQmjpDY8xItMnttnurVgOkHUBMRT9cpsNi2O4AjKhmA=
+connectrpc.com/connect v1.16.1 h1:rOdrK/RTI/7TVnn3JsVxt3n028MlTRwmK5Q4heSpjis=
+connectrpc.com/connect v1.16.1/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw=
 dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
 dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=

From b406025aae8c050b029a9ad2497a75436feb57e4 Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Wed, 24 Apr 2024 20:41:35 +0200
Subject: [PATCH 008/107] Move branch_selection to sidebar folder

---
 templates/repo/issue/new_form.tmpl                              | 2 +-
 templates/repo/issue/view_content/sidebar.tmpl                  | 2 +-
 .../issue/{ => view_content/sidebar}/branch_selector_field.tmpl | 0
 3 files changed, 2 insertions(+), 2 deletions(-)
 rename templates/repo/issue/{ => view_content/sidebar}/branch_selector_field.tmpl (100%)

diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl
index 88a6c39e52..465cb44f6f 100644
--- a/templates/repo/issue/new_form.tmpl
+++ b/templates/repo/issue/new_form.tmpl
@@ -47,7 +47,7 @@
 	</div>
 
 	<div class="issue-content-right ui segment">
-		{{template "repo/issue/branch_selector_field" .}}
+		{{template "repo/issue/view_content/sidebar/branch_selector_field" .}}
 
 		<input id="label_ids" name="label_ids" type="hidden" value="{{.label_ids}}">
 		{{template "repo/issue/labels/labels_selector_field" .}}
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index cbea32d303..e8b1b2258f 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -1,5 +1,5 @@
 <div class="issue-content-right ui segment">
-	{{template "repo/issue/branch_selector_field" .}}
+	{{template "repo/issue/view_content/sidebar/branch_selector_field" .}}
 	{{if .Issue.IsPull}}
 		<input id="reviewer_id" name="reviewer_id" type="hidden" value="{{.reviewer_id}}">
 		<div class="ui {{if or (and (not .Reviewers) (not .TeamReviewers)) (not .CanChooseReviewer) .Repository.IsArchived}}disabled{{end}} floating jump select-reviewers-modify dropdown">
diff --git a/templates/repo/issue/branch_selector_field.tmpl b/templates/repo/issue/view_content/sidebar/branch_selector_field.tmpl
similarity index 100%
rename from templates/repo/issue/branch_selector_field.tmpl
rename to templates/repo/issue/view_content/sidebar/branch_selector_field.tmpl

From 66cab785fce8878af2208baf8de45f5dbbc00061 Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Wed, 24 Apr 2024 21:06:22 +0200
Subject: [PATCH 009/107] Split PR-only templates

---
 .../repo/issue/view_content/sidebar.tmpl      | 125 +-----------------
 .../view_content/sidebar/pull_review.tmpl     |  45 +++++++
 .../view_content/sidebar/pull_reviewers.tmpl  |  67 ++++++++++
 .../issue/view_content/sidebar/pull_wip.tmpl  |  11 ++
 4 files changed, 125 insertions(+), 123 deletions(-)
 create mode 100644 templates/repo/issue/view_content/sidebar/pull_review.tmpl
 create mode 100644 templates/repo/issue/view_content/sidebar/pull_reviewers.tmpl
 create mode 100644 templates/repo/issue/view_content/sidebar/pull_wip.tmpl

diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index e8b1b2258f..37280f69e4 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -1,129 +1,8 @@
 <div class="issue-content-right ui segment">
 	{{template "repo/issue/view_content/sidebar/branch_selector_field" .}}
 	{{if .Issue.IsPull}}
-		<input id="reviewer_id" name="reviewer_id" type="hidden" value="{{.reviewer_id}}">
-		<div class="ui {{if or (and (not .Reviewers) (not .TeamReviewers)) (not .CanChooseReviewer) .Repository.IsArchived}}disabled{{end}} floating jump select-reviewers-modify dropdown">
-			<a class="text tw-flex tw-items-center muted">
-				<strong>{{ctx.Locale.Tr "repo.issues.review.reviewers"}}</strong>
-				{{if and .CanChooseReviewer (not .Repository.IsArchived)}}
-					{{svg "octicon-gear" 16 "tw-ml-1"}}
-				{{end}}
-			</a>
-			<div class="filter menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/request_review">
-				{{if .Reviewers}}
-					<div class="ui icon search input">
-						<i class="icon">{{svg "octicon-search" 16}}</i>
-						<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_reviewers"}}">
-					</div>
-				{{end}}
-				{{if .Reviewers}}
-					{{range .Reviewers}}
-						{{if .User}}
-							<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_{{.ItemID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
-								<span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check"}}</span>
-								<span class="text">
-									{{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}{{template "repo/search_name" .User}}
-								</span>
-							</a>
-						{{end}}
-					{{end}}
-				{{end}}
-				{{if .TeamReviewers}}
-					{{if .Reviewers}}
-						<div class="divider"></div>
-					{{end}}
-					{{range .TeamReviewers}}
-						{{if .Team}}
-							<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_team_{{.Team.ID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
-								<span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check" 16}}</span>
-								<span class="text">
-									{{svg "octicon-people" 16 "tw-ml-4 tw-mr-1"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}
-								</span>
-							</a>
-						{{end}}
-					{{end}}
-				{{end}}
-			</div>
-		</div>
-
-		<div class="ui assignees list">
-			<span class="no-select item {{if or .OriginalReviews .PullReviewers}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_reviewers"}}</span>
-			<div class="selected">
-				{{range .PullReviewers}}
-					<div class="item tw-flex tw-items-center tw-py-2">
-						<div class="tw-flex tw-items-center tw-flex-1">
-							{{if .User}}
-								<a class="muted sidebar-item-link" href="{{.User.HomeLink}}">{{ctx.AvatarUtils.Avatar .User 20 "tw-mr-2"}}{{.User.GetDisplayName}}</a>
-							{{else if .Team}}
-								<span class="text">{{svg "octicon-people" 20 "tw-mr-2"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}</span>
-							{{end}}
-						</div>
-						<div class="tw-flex tw-items-center tw-gap-2">
-							{{if (and $.Permission.IsAdmin (or (eq .Review.Type 1) (eq .Review.Type 3)) (not $.Issue.IsClosed) (not $.Issue.PullRequest.HasMerged))}}
-								<a href="#" class="ui muted icon tw-flex tw-items-center show-modal" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dismiss_review"}}" data-modal="#dismiss-review-modal-{{.Review.ID}}">
-									{{svg "octicon-x" 20}}
-								</a>
-								<div class="ui small modal" id="dismiss-review-modal-{{.Review.ID}}">
-									<div class="header">
-										{{ctx.Locale.Tr "repo.issues.dismiss_review"}}
-									</div>
-									<div class="content">
-										<div class="ui warning message">
-											{{ctx.Locale.Tr "repo.issues.dismiss_review_warning"}}
-										</div>
-										<form class="ui form dismiss-review-form" id="dismiss-review-{{.Review.ID}}" action="{{$.RepoLink}}/issues/dismiss_review" method="post">
-											{{$.CsrfTokenHtml}}
-											<input type="hidden" name="review_id" value="{{.Review.ID}}">
-											<div class="field">
-												<label for="message">{{ctx.Locale.Tr "action.review_dismissed_reason"}}</label>
-												<input id="message" name="message">
-											</div>
-											<div class="text right actions">
-												<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
-												<button class="ui red button" type="submit">{{ctx.Locale.Tr "ok"}}</button>
-											</div>
-										</form>
-									</div>
-								</div>
-							{{end}}
-							{{if .Review.Stale}}
-								<span data-tooltip-content="{{ctx.Locale.Tr "repo.issues.is_stale"}}">
-									{{svg "octicon-hourglass" 16}}
-								</span>
-							{{end}}
-							{{if and .CanChange (or .Checked (and (not $.Issue.IsClosed) (not $.Issue.PullRequest.HasMerged)))}}
-								<a href="#" class="ui muted icon re-request-review{{if .Checked}} checked{{end}}" data-tooltip-content="{{if .Checked}}{{ctx.Locale.Tr "repo.issues.remove_request_review"}}{{else}}{{ctx.Locale.Tr "repo.issues.re_request_review"}}{{end}}" data-issue-id="{{$.Issue.ID}}" data-id="{{.ItemID}}" data-update-url="{{$.RepoLink}}/issues/request_review">{{if .Checked}}{{svg "octicon-trash"}}{{else}}{{svg "octicon-sync"}}{{end}}</a>
-							{{end}}
-							{{svg (printf "octicon-%s" .Review.Type.Icon) 16 (printf "text %s" (.Review.HTMLTypeColorName))}}
-						</div>
-					</div>
-				{{end}}
-				{{range .OriginalReviews}}
-					<div class="item tw-flex tw-items-center tw-py-2">
-						<div class="tw-flex tw-items-center tw-flex-1">
-							<a class="muted" href="{{$.Repository.OriginalURL}}" data-tooltip-content="{{ctx.Locale.Tr "repo.migrated_from_fake" $.Repository.GetOriginalURLHostname}}">
-								{{svg (MigrationIcon $.Repository.GetOriginalURLHostname) 20 "tw-mr-2"}}
-								{{.OriginalAuthor}}
-							</a>
-						</div>
-						<div class="tw-flex tw-items-center tw-gap-2">
-							{{svg (printf "octicon-%s" .Type.Icon) 16 (printf "text %s" (.HTMLTypeColorName))}}
-						</div>
-					</div>
-				{{end}}
-			</div>
-		</div>
-		{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .HasMerged) (not .Issue.IsClosed)}}
-			<div class="toggle-wip" data-title="{{.Issue.Title}}" data-wip-prefixes="{{JsonUtils.EncodeToString .PullRequestWorkInProgressPrefixes}}" data-update-url="{{.Issue.Link}}/title">
-				<a class="muted">
-					{{if .IsPullWorkInProgress}}
-						{{ctx.Locale.Tr "repo.pulls.ready_for_review"}} {{ctx.Locale.Tr "repo.pulls.remove_prefix" (index .PullRequestWorkInProgressPrefixes 0)}}
-					{{else}}
-						{{ctx.Locale.Tr "repo.pulls.still_in_progress"}} {{ctx.Locale.Tr "repo.pulls.add_prefix" (index .PullRequestWorkInProgressPrefixes 0)}}
-					{{end}}
-				</a>
-			</div>
-		{{end}}
+		{{template "repo/issue/view_content/sidebar/pull_review" .}}
+		{{template "repo/issue/view_content/sidebar/pull_wip" .}}
 		<div class="divider"></div>
 	{{end}}
 
diff --git a/templates/repo/issue/view_content/sidebar/pull_review.tmpl b/templates/repo/issue/view_content/sidebar/pull_review.tmpl
new file mode 100644
index 0000000000..930c2a6392
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/pull_review.tmpl
@@ -0,0 +1,45 @@
+<input id="reviewer_id" name="reviewer_id" type="hidden" value="{{.reviewer_id}}">
+<div class="ui {{if or (and (not .Reviewers) (not .TeamReviewers)) (not .CanChooseReviewer) .Repository.IsArchived}}disabled{{end}} floating jump select-reviewers-modify dropdown">
+	<a class="text tw-flex tw-items-center muted">
+		<strong>{{ctx.Locale.Tr "repo.issues.review.reviewers"}}</strong>
+		{{if and .CanChooseReviewer (not .Repository.IsArchived)}}
+			{{svg "octicon-gear" 16 "tw-ml-1"}}
+		{{end}}
+	</a>
+	<div class="filter menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/request_review">
+		{{if .Reviewers}}
+			<div class="ui icon search input">
+				<i class="icon">{{svg "octicon-search" 16}}</i>
+				<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_reviewers"}}">
+			</div>
+		{{end}}
+		{{if .Reviewers}}
+			{{range .Reviewers}}
+				{{if .User}}
+					<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_{{.ItemID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
+						<span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check"}}</span>
+						<span class="text">
+							{{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}{{template "repo/search_name" .User}}
+						</span>
+					</a>
+				{{end}}
+			{{end}}
+		{{end}}
+		{{if .TeamReviewers}}
+			{{if .Reviewers}}
+				<div class="divider"></div>
+			{{end}}
+			{{range .TeamReviewers}}
+				{{if .Team}}
+					<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_team_{{.Team.ID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
+						<span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check" 16}}</span>
+						<span class="text">
+							{{svg "octicon-people" 16 "tw-ml-4 tw-mr-1"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}
+						</span>
+					</a>
+				{{end}}
+			{{end}}
+		{{end}}
+	</div>
+</div>
+{{template "repo/issue/view_content/sidebar/pull_reviewers" .}}
diff --git a/templates/repo/issue/view_content/sidebar/pull_reviewers.tmpl b/templates/repo/issue/view_content/sidebar/pull_reviewers.tmpl
new file mode 100644
index 0000000000..102508fdaf
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/pull_reviewers.tmpl
@@ -0,0 +1,67 @@
+<div class="ui assignees list">
+			<span class="no-select item {{if or .OriginalReviews .PullReviewers}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_reviewers"}}</span>
+			<div class="selected">
+				{{range .PullReviewers}}
+					<div class="item tw-flex tw-items-center tw-py-2">
+						<div class="tw-flex tw-items-center tw-flex-1">
+							{{if .User}}
+								<a class="muted sidebar-item-link" href="{{.User.HomeLink}}">{{ctx.AvatarUtils.Avatar .User 20 "tw-mr-2"}}{{.User.GetDisplayName}}</a>
+							{{else if .Team}}
+								<span class="text">{{svg "octicon-people" 20 "tw-mr-2"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}</span>
+							{{end}}
+						</div>
+						<div class="tw-flex tw-items-center tw-gap-2">
+							{{if (and $.Permission.IsAdmin (or (eq .Review.Type 1) (eq .Review.Type 3)) (not $.Issue.IsClosed) (not $.Issue.PullRequest.HasMerged))}}
+								<a href="#" class="ui muted icon tw-flex tw-items-center show-modal" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dismiss_review"}}" data-modal="#dismiss-review-modal-{{.Review.ID}}">
+									{{svg "octicon-x" 20}}
+								</a>
+								<div class="ui small modal" id="dismiss-review-modal-{{.Review.ID}}">
+									<div class="header">
+										{{ctx.Locale.Tr "repo.issues.dismiss_review"}}
+									</div>
+									<div class="content">
+										<div class="ui warning message">
+											{{ctx.Locale.Tr "repo.issues.dismiss_review_warning"}}
+										</div>
+										<form class="ui form dismiss-review-form" id="dismiss-review-{{.Review.ID}}" action="{{$.RepoLink}}/issues/dismiss_review" method="post">
+											{{$.CsrfTokenHtml}}
+											<input type="hidden" name="review_id" value="{{.Review.ID}}">
+											<div class="field">
+												<label for="message">{{ctx.Locale.Tr "action.review_dismissed_reason"}}</label>
+												<input id="message" name="message">
+											</div>
+											<div class="text right actions">
+												<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
+												<button class="ui red button" type="submit">{{ctx.Locale.Tr "ok"}}</button>
+											</div>
+										</form>
+									</div>
+								</div>
+							{{end}}
+							{{if .Review.Stale}}
+								<span data-tooltip-content="{{ctx.Locale.Tr "repo.issues.is_stale"}}">
+									{{svg "octicon-hourglass" 16}}
+								</span>
+							{{end}}
+							{{if and .CanChange (or .Checked (and (not $.Issue.IsClosed) (not $.Issue.PullRequest.HasMerged)))}}
+								<a href="#" class="ui muted icon re-request-review{{if .Checked}} checked{{end}}" data-tooltip-content="{{if .Checked}}{{ctx.Locale.Tr "repo.issues.remove_request_review"}}{{else}}{{ctx.Locale.Tr "repo.issues.re_request_review"}}{{end}}" data-issue-id="{{$.Issue.ID}}" data-id="{{.ItemID}}" data-update-url="{{$.RepoLink}}/issues/request_review">{{if .Checked}}{{svg "octicon-trash"}}{{else}}{{svg "octicon-sync"}}{{end}}</a>
+							{{end}}
+							{{svg (printf "octicon-%s" .Review.Type.Icon) 16 (printf "text %s" (.Review.HTMLTypeColorName))}}
+						</div>
+					</div>
+				{{end}}
+				{{range .OriginalReviews}}
+					<div class="item tw-flex tw-items-center tw-py-2">
+						<div class="tw-flex tw-items-center tw-flex-1">
+							<a class="muted" href="{{$.Repository.OriginalURL}}" data-tooltip-content="{{ctx.Locale.Tr "repo.migrated_from_fake" $.Repository.GetOriginalURLHostname}}">
+								{{svg (MigrationIcon $.Repository.GetOriginalURLHostname) 20 "tw-mr-2"}}
+								{{.OriginalAuthor}}
+							</a>
+						</div>
+						<div class="tw-flex tw-items-center tw-gap-2">
+							{{svg (printf "octicon-%s" .Type.Icon) 16 (printf "text %s" (.HTMLTypeColorName))}}
+						</div>
+					</div>
+				{{end}}
+			</div>
+		</div>
diff --git a/templates/repo/issue/view_content/sidebar/pull_wip.tmpl b/templates/repo/issue/view_content/sidebar/pull_wip.tmpl
new file mode 100644
index 0000000000..f1588b3f80
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/pull_wip.tmpl
@@ -0,0 +1,11 @@
+{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .HasMerged) (not .Issue.IsClosed)}}
+	<div class="toggle-wip" data-title="{{.Issue.Title}}" data-wip-prefixes="{{JsonUtils.EncodeToString .PullRequestWorkInProgressPrefixes}}" data-update-url="{{.Issue.Link}}/title">
+		<a class="muted">
+			{{if .IsPullWorkInProgress}}
+				{{ctx.Locale.Tr "repo.pulls.ready_for_review"}} {{ctx.Locale.Tr "repo.pulls.remove_prefix" (index .PullRequestWorkInProgressPrefixes 0)}}
+			{{else}}
+				{{ctx.Locale.Tr "repo.pulls.still_in_progress"}} {{ctx.Locale.Tr "repo.pulls.add_prefix" (index .PullRequestWorkInProgressPrefixes 0)}}
+			{{end}}
+		</a>
+	</div>
+{{end}}

From 3d5c1c6cef82d366b762a41261b1a14793b37edb Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Wed, 24 Apr 2024 22:54:55 +0200
Subject: [PATCH 010/107] Split milestones and projects

---
 .../repo/issue/view_content/sidebar.tmpl      | 80 +------------------
 .../view_content/sidebar/milestones.tmpl      | 22 +++++
 .../issue/view_content/sidebar/projects.tmpl  | 54 +++++++++++++
 3 files changed, 78 insertions(+), 78 deletions(-)
 create mode 100644 templates/repo/issue/view_content/sidebar/milestones.tmpl
 create mode 100644 templates/repo/issue/view_content/sidebar/projects.tmpl

diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index 37280f69e4..b410d1d19c 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -11,86 +11,10 @@
 
 	<div class="divider"></div>
 
-	<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-milestone dropdown">
-		<a class="text muted flex-text-block">
-			<strong>{{ctx.Locale.Tr "repo.issues.new.milestone"}}</strong>
-			{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
-				{{svg "octicon-gear" 16 "tw-ml-1"}}
-			{{end}}
-		</a>
-		<div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/milestone">
-			{{template "repo/issue/milestone/select_menu" .}}
-		</div>
-	</div>
-	<div class="ui select-milestone list">
-		<span class="no-select item {{if .Issue.Milestone}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_milestone"}}</span>
-		<div class="selected">
-			{{if .Issue.Milestone}}
-				<a class="item muted sidebar-item-link" href="{{.RepoLink}}/milestone/{{.Issue.Milestone.ID}}">
-					{{svg "octicon-milestone" 18 "tw-mr-2"}}
-					{{.Issue.Milestone.Name}}
-				</a>
-			{{end}}
-		</div>
-	</div>
-
+	{{template "repo/issue/view_content/sidebar/milestones" .}}
 	<div class="divider"></div>
 
-	<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-project dropdown">
-		<a class="text muted flex-text-block">
-			<strong>{{ctx.Locale.Tr "repo.issues.new.projects"}}</strong>
-			{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
-				{{svg "octicon-gear" 16 "tw-ml-1"}}
-			{{end}}
-		</a>
-		<div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/projects">
-			{{if or .OpenProjects .ClosedProjects}}
-			<div class="ui icon search input">
-				<i class="icon">{{svg "octicon-search" 16}}</i>
-				<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_projects"}}">
-			</div>
-			{{end}}
-			<div class="no-select item">{{ctx.Locale.Tr "repo.issues.new.clear_projects"}}</div>
-			{{if and (not .OpenProjects) (not .ClosedProjects)}}
-				<div class="disabled item">
-					{{ctx.Locale.Tr "repo.issues.new.no_items"}}
-				</div>
-			{{end}}
-			{{if .OpenProjects}}
-				<div class="divider"></div>
-				<div class="header">
-					{{ctx.Locale.Tr "repo.issues.new.open_projects"}}
-				</div>
-				{{range .OpenProjects}}
-					<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
-						{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
-					</a>
-				{{end}}
-			{{end}}
-			{{if .ClosedProjects}}
-				<div class="divider"></div>
-				<div class="header">
-					{{ctx.Locale.Tr "repo.issues.new.closed_projects"}}
-				</div>
-				{{range .ClosedProjects}}
-					<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
-						{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
-					</a>
-				{{end}}
-			{{end}}
-		</div>
-	</div>
-	<div class="ui select-project list">
-		<span class="no-select item {{if .Issue.Project}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_projects"}}</span>
-		<div class="selected">
-			{{if .Issue.Project}}
-				<a class="item muted sidebar-item-link" href="{{.Issue.Project.Link ctx}}">
-					{{svg .Issue.Project.IconName 18 "tw-mr-2"}}{{.Issue.Project.Title}}
-				</a>
-			{{end}}
-		</div>
-	</div>
-
+	{{template "repo/issue/view_content/sidebar/projects" .}}
 	<div class="divider"></div>
 
 	<input id="assignee_id" name="assignee_id" type="hidden" value="{{.assignee_id}}">
diff --git a/templates/repo/issue/view_content/sidebar/milestones.tmpl b/templates/repo/issue/view_content/sidebar/milestones.tmpl
new file mode 100644
index 0000000000..661ca80743
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/milestones.tmpl
@@ -0,0 +1,22 @@
+<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-milestone dropdown">
+	<a class="text muted flex-text-block">
+		<strong>{{ctx.Locale.Tr "repo.issues.new.milestone"}}</strong>
+		{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
+			{{svg "octicon-gear" 16 "tw-ml-1"}}
+		{{end}}
+	</a>
+	<div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/milestone">
+		{{template "repo/issue/milestone/select_menu" .}}
+	</div>
+</div>
+<div class="ui select-milestone list">
+	<span class="no-select item {{if .Issue.Milestone}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_milestone"}}</span>
+	<div class="selected">
+		{{if .Issue.Milestone}}
+			<a class="item muted sidebar-item-link" href="{{.RepoLink}}/milestone/{{.Issue.Milestone.ID}}">
+				{{svg "octicon-milestone" 18 "tw-mr-2"}}
+				{{.Issue.Milestone.Name}}
+			</a>
+		{{end}}
+	</div>
+</div>
diff --git a/templates/repo/issue/view_content/sidebar/projects.tmpl b/templates/repo/issue/view_content/sidebar/projects.tmpl
new file mode 100644
index 0000000000..91d75f3bd9
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/projects.tmpl
@@ -0,0 +1,54 @@
+<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-project dropdown">
+	<a class="text muted flex-text-block">
+		<strong>{{ctx.Locale.Tr "repo.issues.new.projects"}}</strong>
+		{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
+			{{svg "octicon-gear" 16 "tw-ml-1"}}
+		{{end}}
+	</a>
+	<div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/projects">
+		{{if or .OpenProjects .ClosedProjects}}
+		<div class="ui icon search input">
+			<i class="icon">{{svg "octicon-search" 16}}</i>
+			<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_projects"}}">
+		</div>
+		{{end}}
+		<div class="no-select item">{{ctx.Locale.Tr "repo.issues.new.clear_projects"}}</div>
+		{{if and (not .OpenProjects) (not .ClosedProjects)}}
+			<div class="disabled item">
+				{{ctx.Locale.Tr "repo.issues.new.no_items"}}
+			</div>
+		{{end}}
+		{{if .OpenProjects}}
+			<div class="divider"></div>
+			<div class="header">
+				{{ctx.Locale.Tr "repo.issues.new.open_projects"}}
+			</div>
+			{{range .OpenProjects}}
+				<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
+					{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
+				</a>
+			{{end}}
+		{{end}}
+		{{if .ClosedProjects}}
+			<div class="divider"></div>
+			<div class="header">
+				{{ctx.Locale.Tr "repo.issues.new.closed_projects"}}
+			</div>
+			{{range .ClosedProjects}}
+				<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
+					{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
+				</a>
+			{{end}}
+		{{end}}
+	</div>
+</div>
+<div class="ui select-project list">
+	<span class="no-select item {{if .Issue.Project}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_projects"}}</span>
+	<div class="selected">
+		{{if .Issue.Project}}
+			<a class="item muted sidebar-item-link" href="{{.Issue.Project.Link ctx}}">
+				{{svg .Issue.Project.IconName 18 "tw-mr-2"}}{{.Issue.Project.Title}}
+			</a>
+		{{end}}
+	</div>
+</div>

From 019d32235129afbc1b9effe86511dcec8a7979cc Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Wed, 24 Apr 2024 22:58:03 +0200
Subject: [PATCH 011/107] Split assignees

---
 .../repo/issue/view_content/sidebar.tmpl      | 47 +------------------
 .../issue/view_content/sidebar/assignees.tmpl | 45 ++++++++++++++++++
 2 files changed, 46 insertions(+), 46 deletions(-)
 create mode 100644 templates/repo/issue/view_content/sidebar/assignees.tmpl

diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index b410d1d19c..effbdc3072 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -17,52 +17,7 @@
 	{{template "repo/issue/view_content/sidebar/projects" .}}
 	<div class="divider"></div>
 
-	<input id="assignee_id" name="assignee_id" type="hidden" value="{{.assignee_id}}">
-	<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-assignees-modify dropdown">
-		<a class="text muted flex-text-block">
-			<strong>{{ctx.Locale.Tr "repo.issues.new.assignees"}}</strong>
-			{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
-				{{svg "octicon-gear" 16 "tw-ml-1"}}
-			{{end}}
-		</a>
-		<div class="filter menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/assignee">
-			<div class="ui icon search input">
-				<i class="icon">{{svg "octicon-search" 16}}</i>
-				<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_assignees"}}">
-			</div>
-			<div class="no-select item">{{ctx.Locale.Tr "repo.issues.new.clear_assignees"}}</div>
-			{{range .Assignees}}
-
-				{{$AssigneeID := .ID}}
-				<a class="item{{range $.Issue.Assignees}}{{if eq .ID $AssigneeID}} checked{{end}}{{end}}" href="#" data-id="{{.ID}}" data-id-selector="#assignee_{{.ID}}">
-					{{$checked := false}}
-					{{range $.Issue.Assignees}}
-						{{if eq .ID $AssigneeID}}
-							{{$checked = true}}
-						{{end}}
-					{{end}}
-					<span class="octicon-check {{if not $checked}}tw-invisible{{end}}">{{svg "octicon-check"}}</span>
-					<span class="text">
-						{{ctx.AvatarUtils.Avatar . 20 "tw-mr-2"}}{{template "repo/search_name" .}}
-					</span>
-				</a>
-			{{end}}
-		</div>
-	</div>
-	<div class="ui assignees list">
-		<span class="no-select item {{if .Issue.Assignees}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_assignees"}}</span>
-		<div class="selected">
-			{{range .Issue.Assignees}}
-				<div class="item">
-					<a class="muted sidebar-item-link" href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?assignee={{.ID}}">
-						{{ctx.AvatarUtils.Avatar . 28 "tw-mr-2"}}
-						{{.GetDisplayName}}
-					</a>
-				</div>
-			{{end}}
-		</div>
-	</div>
-
+	{{template "repo/issue/view_content/sidebar/assignees" .}}
 	<div class="divider"></div>
 
 	{{if .Participants}}
diff --git a/templates/repo/issue/view_content/sidebar/assignees.tmpl b/templates/repo/issue/view_content/sidebar/assignees.tmpl
new file mode 100644
index 0000000000..e51bda95de
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/assignees.tmpl
@@ -0,0 +1,45 @@
+<input id="assignee_id" name="assignee_id" type="hidden" value="{{.assignee_id}}">
+<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-assignees-modify dropdown">
+	<a class="text muted flex-text-block">
+		<strong>{{ctx.Locale.Tr "repo.issues.new.assignees"}}</strong>
+		{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
+			{{svg "octicon-gear" 16 "tw-ml-1"}}
+		{{end}}
+	</a>
+	<div class="filter menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/assignee">
+		<div class="ui icon search input">
+			<i class="icon">{{svg "octicon-search" 16}}</i>
+			<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_assignees"}}">
+		</div>
+		<div class="no-select item">{{ctx.Locale.Tr "repo.issues.new.clear_assignees"}}</div>
+		{{range .Assignees}}
+
+			{{$AssigneeID := .ID}}
+			<a class="item{{range $.Issue.Assignees}}{{if eq .ID $AssigneeID}} checked{{end}}{{end}}" href="#" data-id="{{.ID}}" data-id-selector="#assignee_{{.ID}}">
+				{{$checked := false}}
+				{{range $.Issue.Assignees}}
+					{{if eq .ID $AssigneeID}}
+						{{$checked = true}}
+					{{end}}
+				{{end}}
+				<span class="octicon-check {{if not $checked}}tw-invisible{{end}}">{{svg "octicon-check"}}</span>
+				<span class="text">
+					{{ctx.AvatarUtils.Avatar . 20 "tw-mr-2"}}{{template "repo/search_name" .}}
+				</span>
+			</a>
+		{{end}}
+	</div>
+</div>
+<div class="ui assignees list">
+	<span class="no-select item {{if .Issue.Assignees}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_assignees"}}</span>
+	<div class="selected">
+		{{range .Issue.Assignees}}
+			<div class="item">
+				<a class="muted sidebar-item-link" href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?assignee={{.ID}}">
+					{{ctx.AvatarUtils.Avatar . 28 "tw-mr-2"}}
+					{{.GetDisplayName}}
+				</a>
+			</div>
+		{{end}}
+	</div>
+</div>

From dfaeb4692c6933df3e7a19462f567ec1acbd379f Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Wed, 24 Apr 2024 23:01:03 +0200
Subject: [PATCH 012/107] Split participants

---
 templates/repo/issue/view_content/sidebar.tmpl           | 9 +--------
 .../repo/issue/view_content/sidebar/participants.tmpl    | 8 ++++++++
 2 files changed, 9 insertions(+), 8 deletions(-)
 create mode 100644 templates/repo/issue/view_content/sidebar/participants.tmpl

diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index effbdc3072..5972f3dbcf 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -21,14 +21,7 @@
 	<div class="divider"></div>
 
 	{{if .Participants}}
-		<span class="text"><strong>{{ctx.Locale.TrN .NumParticipants "repo.issues.num_participants_one" "repo.issues.num_participants_few" .NumParticipants}}</strong></span>
-		<div class="ui list tw-flex tw-flex-wrap">
-			{{range .Participants}}
-				<a {{if gt .ID 0}}href="{{.HomeLink}}"{{end}} data-tooltip-content="{{.GetDisplayName}}">
-					{{ctx.AvatarUtils.Avatar . 28 "tw-my-0.5 tw-mr-1"}}
-				</a>
-			{{end}}
-		</div>
+		{{template "repo/issue/view_content/sidebar/participants" .}}
 	{{end}}
 
 	{{if and $.IssueWatch (not .Repository.IsArchived)}}
diff --git a/templates/repo/issue/view_content/sidebar/participants.tmpl b/templates/repo/issue/view_content/sidebar/participants.tmpl
new file mode 100644
index 0000000000..93e2579d8f
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/participants.tmpl
@@ -0,0 +1,8 @@
+<span class="text"><strong>{{ctx.Locale.TrN .NumParticipants "repo.issues.num_participants_one" "repo.issues.num_participants_few" .NumParticipants}}</strong></span>
+<div class="ui list tw-flex tw-flex-wrap">
+	{{range .Participants}}
+		<a {{if gt .ID 0}}href="{{.HomeLink}}"{{end}} data-tooltip-content="{{.GetDisplayName}}">
+			{{ctx.AvatarUtils.Avatar . 28 "tw-my-0.5 tw-mr-1"}}
+		</a>
+	{{end}}
+</div>

From 35c967653b61e54a07e27619cfcf5ac78810ffc4 Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Wed, 24 Apr 2024 23:03:47 +0200
Subject: [PATCH 013/107] Split watching

---
 templates/repo/issue/view_content/sidebar.tmpl       | 8 ++------
 templates/repo/issue/view_content/sidebar/watch.tmpl | 6 ++++++
 2 files changed, 8 insertions(+), 6 deletions(-)
 create mode 100644 templates/repo/issue/view_content/sidebar/watch.tmpl

diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index 5972f3dbcf..c3e8e24d03 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -27,13 +27,9 @@
 	{{if and $.IssueWatch (not .Repository.IsArchived)}}
 		<div class="divider"></div>
 
-		<div class="ui watching">
-			<span class="text"><strong>{{ctx.Locale.Tr "notification.notifications"}}</strong></span>
-			<div class="tw-mt-2">
-				{{template "repo/issue/view_content/watching" .}}
-			</div>
-		</div>
+		{{template "repo/issue/view_content/sidebar/watch" .}}
 	{{end}}
+
 	{{if .Repository.IsTimetrackerEnabled $.Context}}
 		{{if and .CanUseTimetracker (not .Repository.IsArchived)}}
 			<div class="divider"></div>
diff --git a/templates/repo/issue/view_content/sidebar/watch.tmpl b/templates/repo/issue/view_content/sidebar/watch.tmpl
new file mode 100644
index 0000000000..852738a706
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/watch.tmpl
@@ -0,0 +1,6 @@
+<div class="ui watching">
+	<span class="text"><strong>{{ctx.Locale.Tr "notification.notifications"}}</strong></span>
+	<div class="tw-mt-2">
+		{{template "repo/issue/view_content/watching" .}}
+	</div>
+</div>

From 47ef51d51ee46976171bab80314baa3798e45d13 Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Wed, 24 Apr 2024 23:04:58 +0200
Subject: [PATCH 014/107] Unify watching template

---
 .../repo/issue/view_content/sidebar/watch.tmpl      | 13 ++++++++++++-
 templates/repo/issue/view_content/watching.tmpl     | 12 ------------
 2 files changed, 12 insertions(+), 13 deletions(-)
 delete mode 100644 templates/repo/issue/view_content/watching.tmpl

diff --git a/templates/repo/issue/view_content/sidebar/watch.tmpl b/templates/repo/issue/view_content/sidebar/watch.tmpl
index 852738a706..6c74b140c8 100644
--- a/templates/repo/issue/view_content/sidebar/watch.tmpl
+++ b/templates/repo/issue/view_content/sidebar/watch.tmpl
@@ -1,6 +1,17 @@
 <div class="ui watching">
 	<span class="text"><strong>{{ctx.Locale.Tr "notification.notifications"}}</strong></span>
 	<div class="tw-mt-2">
-		{{template "repo/issue/view_content/watching" .}}
+		<form hx-boost="true" hx-sync="this:replace" hx-target="this" method="post" action="{{.Issue.Link}}/watch">
+			<input type="hidden" name="watch" value="{{if $.IssueWatch.IsWatching}}0{{else}}1{{end}}">
+			<button class="fluid ui button">
+				{{if $.IssueWatch.IsWatching}}
+					{{svg "octicon-mute" 16 "tw-mr-2"}}
+					{{ctx.Locale.Tr "repo.issues.unsubscribe"}}
+				{{else}}
+					{{svg "octicon-unmute" 16 "tw-mr-2"}}
+					{{ctx.Locale.Tr "repo.issues.subscribe"}}
+				{{end}}
+			</button>
+		</form>
 	</div>
 </div>
diff --git a/templates/repo/issue/view_content/watching.tmpl b/templates/repo/issue/view_content/watching.tmpl
deleted file mode 100644
index 05936d090b..0000000000
--- a/templates/repo/issue/view_content/watching.tmpl
+++ /dev/null
@@ -1,12 +0,0 @@
-<form hx-boost="true" hx-sync="this:replace" hx-target="this" method="post" action="{{.Issue.Link}}/watch">
-	<input type="hidden" name="watch" value="{{if $.IssueWatch.IsWatching}}0{{else}}1{{end}}">
-	<button class="fluid ui button">
-		{{if $.IssueWatch.IsWatching}}
-			{{svg "octicon-mute" 16 "tw-mr-2"}}
-			{{ctx.Locale.Tr "repo.issues.unsubscribe"}}
-		{{else}}
-			{{svg "octicon-unmute" 16 "tw-mr-2"}}
-			{{ctx.Locale.Tr "repo.issues.subscribe"}}
-		{{end}}
-	</button>
-</form>

From 27a9fd1792cad0de5d12606595283f670cb5e0b8 Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Thu, 25 Apr 2024 13:24:26 +0200
Subject: [PATCH 015/107] Split timetracking

---
 .../repo/issue/view_content/sidebar.tmpl      | 74 +------------------
 .../view_content/sidebar/timetracking.tmpl    | 73 ++++++++++++++++++
 2 files changed, 74 insertions(+), 73 deletions(-)
 create mode 100644 templates/repo/issue/view_content/sidebar/timetracking.tmpl

diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index c3e8e24d03..866c9e4c12 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -31,79 +31,7 @@
 	{{end}}
 
 	{{if .Repository.IsTimetrackerEnabled $.Context}}
-		{{if and .CanUseTimetracker (not .Repository.IsArchived)}}
-			<div class="divider"></div>
-			<div class="ui timetrack">
-				<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.tracker"}}</strong></span>
-				<div class="tw-mt-2">
-					<form method="post" action="{{.Issue.Link}}/times/stopwatch/toggle" id="toggle_stopwatch_form">
-						{{$.CsrfTokenHtml}}
-					</form>
-					<form method="post" action="{{.Issue.Link}}/times/stopwatch/cancel" id="cancel_stopwatch_form">
-						{{$.CsrfTokenHtml}}
-					</form>
-					{{if $.IsStopwatchRunning}}
-						<button class="ui fluid button issue-stop-time">
-							{{svg "octicon-stopwatch" 16 "tw-mr-2"}}
-							{{ctx.Locale.Tr "repo.issues.stop_tracking"}}
-						</button>
-						<button class="ui fluid button issue-cancel-time tw-mt-2">
-							{{svg "octicon-trash" 16 "tw-mr-2"}}
-							{{ctx.Locale.Tr "repo.issues.cancel_tracking"}}
-						</button>
-					{{else}}
-						{{if .HasUserStopwatch}}
-							<div class="ui warning message">
-								{{ctx.Locale.Tr "repo.issues.tracking_already_started" .OtherStopwatchURL}}
-							</div>
-						{{end}}
-						<button class="ui fluid button issue-start-time" data-tooltip-content='{{ctx.Locale.Tr "repo.issues.start_tracking"}}'>
-							{{svg "octicon-stopwatch" 16 "tw-mr-2"}}
-							{{ctx.Locale.Tr "repo.issues.start_tracking_short"}}
-						</button>
-						<div class="ui mini modal issue-start-time-modal">
-							<div class="header">{{ctx.Locale.Tr "repo.issues.add_time"}}</div>
-							<div class="content">
-								<form method="post" id="add_time_manual_form" action="{{.Issue.Link}}/times/add" class="ui input fluid tw-gap-2">
-									{{$.CsrfTokenHtml}}
-									<input placeholder='{{ctx.Locale.Tr "repo.issues.add_time_hours"}}' type="number" name="hours">
-									<input placeholder='{{ctx.Locale.Tr "repo.issues.add_time_minutes"}}' type="number" name="minutes" class="ui compact">
-								</form>
-							</div>
-							<div class="actions">
-								<button class="ui primary approve button">{{ctx.Locale.Tr "repo.issues.add_time_short"}}</button>
-								<button class="ui cancel button">{{ctx.Locale.Tr "repo.issues.add_time_cancel"}}</button>
-							</div>
-						</div>
-						<button class="ui fluid button issue-add-time tw-mt-2" data-tooltip-content='{{ctx.Locale.Tr "repo.issues.add_time"}}'>
-							{{svg "octicon-plus" 16 "tw-mr-2"}}
-							{{ctx.Locale.Tr "repo.issues.add_time_short"}}
-						</button>
-					{{end}}
-				</div>
-			</div>
-		{{end}}
-		{{if .WorkingUsers}}
-			<div class="divider"></div>
-			<div class="ui comments">
-				<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.time_spent_from_all_authors" ($.Issue.TotalTrackedTime | Sec2Time)}}</strong></span>
-				<div>
-					{{range $user, $trackedtime := .WorkingUsers}}
-						<div class="comment tw-mt-2">
-							<a class="avatar">
-								{{ctx.AvatarUtils.Avatar $user}}
-							</a>
-							<div class="content">
-								{{template "shared/user/authorlink" $user}}
-								<div class="text">
-									{{$trackedtime|Sec2Time}}
-								</div>
-							</div>
-						</div>
-					{{end}}
-				</div>
-			</div>
-		{{end}}
+		{{template "repo/issue/view_content/sidebar/timetracking" .}}
 	{{end}}
 
 	<div class="divider"></div>
diff --git a/templates/repo/issue/view_content/sidebar/timetracking.tmpl b/templates/repo/issue/view_content/sidebar/timetracking.tmpl
new file mode 100644
index 0000000000..610600b2fb
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/timetracking.tmpl
@@ -0,0 +1,73 @@
+{{if and .CanUseTimetracker (not .Repository.IsArchived)}}
+	<div class="divider"></div>
+	<div class="ui timetrack">
+		<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.tracker"}}</strong></span>
+		<div class="tw-mt-2">
+			<form method="post" action="{{.Issue.Link}}/times/stopwatch/toggle" id="toggle_stopwatch_form">
+				{{$.CsrfTokenHtml}}
+			</form>
+			<form method="post" action="{{.Issue.Link}}/times/stopwatch/cancel" id="cancel_stopwatch_form">
+				{{$.CsrfTokenHtml}}
+			</form>
+			{{if $.IsStopwatchRunning}}
+				<button class="ui fluid button issue-stop-time">
+					{{svg "octicon-stopwatch" 16 "tw-mr-2"}}
+					{{ctx.Locale.Tr "repo.issues.stop_tracking"}}
+				</button>
+				<button class="ui fluid button issue-cancel-time tw-mt-2">
+					{{svg "octicon-trash" 16 "tw-mr-2"}}
+					{{ctx.Locale.Tr "repo.issues.cancel_tracking"}}
+				</button>
+			{{else}}
+				{{if .HasUserStopwatch}}
+					<div class="ui warning message">
+						{{ctx.Locale.Tr "repo.issues.tracking_already_started" .OtherStopwatchURL}}
+					</div>
+				{{end}}
+				<button class="ui fluid button issue-start-time" data-tooltip-content='{{ctx.Locale.Tr "repo.issues.start_tracking"}}'>
+					{{svg "octicon-stopwatch" 16 "tw-mr-2"}}
+					{{ctx.Locale.Tr "repo.issues.start_tracking_short"}}
+				</button>
+				<div class="ui mini modal issue-start-time-modal">
+					<div class="header">{{ctx.Locale.Tr "repo.issues.add_time"}}</div>
+					<div class="content">
+						<form method="post" id="add_time_manual_form" action="{{.Issue.Link}}/times/add" class="ui input fluid tw-gap-2">
+							{{$.CsrfTokenHtml}}
+							<input placeholder='{{ctx.Locale.Tr "repo.issues.add_time_hours"}}' type="number" name="hours">
+							<input placeholder='{{ctx.Locale.Tr "repo.issues.add_time_minutes"}}' type="number" name="minutes" class="ui compact">
+						</form>
+					</div>
+					<div class="actions">
+						<button class="ui primary approve button">{{ctx.Locale.Tr "repo.issues.add_time_short"}}</button>
+						<button class="ui cancel button">{{ctx.Locale.Tr "repo.issues.add_time_cancel"}}</button>
+					</div>
+				</div>
+				<button class="ui fluid button issue-add-time tw-mt-2" data-tooltip-content='{{ctx.Locale.Tr "repo.issues.add_time"}}'>
+					{{svg "octicon-plus" 16 "tw-mr-2"}}
+					{{ctx.Locale.Tr "repo.issues.add_time_short"}}
+				</button>
+			{{end}}
+		</div>
+	</div>
+{{end}}
+{{if .WorkingUsers}}
+	<div class="divider"></div>
+	<div class="ui comments">
+		<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.time_spent_from_all_authors" ($.Issue.TotalTrackedTime | Sec2Time)}}</strong></span>
+		<div>
+			{{range $user, $trackedtime := .WorkingUsers}}
+				<div class="comment tw-mt-2">
+					<a class="avatar">
+						{{ctx.AvatarUtils.Avatar $user}}
+					</a>
+					<div class="content">
+						{{template "shared/user/authorlink" $user}}
+						<div class="text">
+							{{$trackedtime|Sec2Time}}
+						</div>
+					</div>
+				</div>
+			{{end}}
+		</div>
+	</div>
+{{end}}

From cc83b9349df9aa49a9736fe747ecbd668c3448fc Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Thu, 25 Apr 2024 13:34:48 +0200
Subject: [PATCH 016/107] Split deadline / due date

---
 .../repo/issue/view_content/sidebar.tmpl      | 42 +------------------
 .../view_content/sidebar/due_deadline.tmpl    | 41 ++++++++++++++++++
 2 files changed, 42 insertions(+), 41 deletions(-)
 create mode 100644 templates/repo/issue/view_content/sidebar/due_deadline.tmpl

diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index 866c9e4c12..35e97085c5 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -35,47 +35,7 @@
 	{{end}}
 
 	<div class="divider"></div>
-	<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.due_date"}}</strong></span>
-	<div class="ui form" id="deadline-loader">
-		<div class="ui negative message tw-hidden" id="deadline-err-invalid-date">
-			{{svg "octicon-x" 16 "close icon"}}
-			{{ctx.Locale.Tr "repo.issues.due_date_invalid"}}
-		</div>
-		{{if ne .Issue.DeadlineUnix 0}}
-			<p>
-				<div class="tw-flex tw-justify-between tw-items-center">
-					<div class="due-date {{if .Issue.IsOverdue}}text red{{end}}" {{if .Issue.IsOverdue}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_overdue"}}"{{end}}>
-						{{svg "octicon-calendar" 16 "tw-mr-2"}}
-						{{DateTime "long" .Issue.DeadlineUnix.FormatDate}}
-					</div>
-					<div>
-						{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
-							<a class="issue-due-edit muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_edit"}}">{{svg "octicon-pencil" 16 "tw-mr-1"}}</a>
-							<a class="issue-due-remove muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_remove"}}">{{svg "octicon-trash"}}</a>
-						{{end}}
-					</div>
-				</div>
-			</p>
-		{{else}}
-			<p>{{ctx.Locale.Tr "repo.issues.due_date_not_set"}}</p>
-		{{end}}
-
-		{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
-			<div {{if ne .Issue.DeadlineUnix 0}} class="tw-hidden"{{end}} id="deadlineForm">
-				<form class="ui fluid action input issue-due-form" action="{{AppSubUrl}}/{{PathEscape .Repository.Owner.Name}}/{{PathEscape .Repository.Name}}/issues/{{.Issue.Index}}/deadline" method="post" id="update-issue-deadline-form">
-					{{$.CsrfTokenHtml}}
-					<input required placeholder="{{ctx.Locale.Tr "repo.issues.due_date_form"}}" {{if gt .Issue.DeadlineUnix 0}}value="{{.Issue.DeadlineUnix.FormatDate}}"{{end}} type="date" name="deadlineDate" id="deadlineDate">
-					<button class="ui icon button">
-						{{if ne .Issue.DeadlineUnix 0}}
-							{{svg "octicon-pencil"}}
-						{{else}}
-							{{svg "octicon-plus"}}
-						{{end}}
-					</button>
-				</form>
-			</div>
-		{{end}}
-	</div>
+	{{template "repo/issue/view_content/sidebar/due_deadline" .}}
 
 	{{if .Repository.IsDependenciesEnabled $.Context}}
 		<div class="divider"></div>
diff --git a/templates/repo/issue/view_content/sidebar/due_deadline.tmpl b/templates/repo/issue/view_content/sidebar/due_deadline.tmpl
new file mode 100644
index 0000000000..2de836b4ed
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/due_deadline.tmpl
@@ -0,0 +1,41 @@
+<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.due_date"}}</strong></span>
+<div class="ui form" id="deadline-loader">
+	<div class="ui negative message tw-hidden" id="deadline-err-invalid-date">
+		{{svg "octicon-x" 16 "close icon"}}
+		{{ctx.Locale.Tr "repo.issues.due_date_invalid"}}
+	</div>
+	{{if ne .Issue.DeadlineUnix 0}}
+		<p>
+			<div class="tw-flex tw-justify-between tw-items-center">
+				<div class="due-date {{if .Issue.IsOverdue}}text red{{end}}" {{if .Issue.IsOverdue}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_overdue"}}"{{end}}>
+					{{svg "octicon-calendar" 16 "tw-mr-2"}}
+					{{DateTime "long" .Issue.DeadlineUnix.FormatDate}}
+				</div>
+				<div>
+					{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
+						<a class="issue-due-edit muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_edit"}}">{{svg "octicon-pencil" 16 "tw-mr-1"}}</a>
+						<a class="issue-due-remove muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_remove"}}">{{svg "octicon-trash"}}</a>
+					{{end}}
+				</div>
+			</div>
+		</p>
+	{{else}}
+		<p>{{ctx.Locale.Tr "repo.issues.due_date_not_set"}}</p>
+	{{end}}
+
+	{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
+		<div {{if ne .Issue.DeadlineUnix 0}} class="tw-hidden"{{end}} id="deadlineForm">
+			<form class="ui fluid action input issue-due-form" action="{{AppSubUrl}}/{{PathEscape .Repository.Owner.Name}}/{{PathEscape .Repository.Name}}/issues/{{.Issue.Index}}/deadline" method="post" id="update-issue-deadline-form">
+				{{$.CsrfTokenHtml}}
+				<input required placeholder="{{ctx.Locale.Tr "repo.issues.due_date_form"}}" {{if gt .Issue.DeadlineUnix 0}}value="{{.Issue.DeadlineUnix.FormatDate}}"{{end}} type="date" name="deadlineDate" id="deadlineDate">
+				<button class="ui icon button">
+					{{if ne .Issue.DeadlineUnix 0}}
+						{{svg "octicon-pencil"}}
+					{{else}}
+						{{svg "octicon-plus"}}
+					{{end}}
+				</button>
+			</form>
+		</div>
+	{{end}}
+</div>

From 0eec8b84a1d63be68d41518bfff0f7f9d9eadb0c Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Thu, 25 Apr 2024 13:37:43 +0200
Subject: [PATCH 017/107] Split issue dependencies

---
 .../repo/issue/view_content/sidebar.tmpl      | 146 +-----------------
 .../view_content/sidebar/dependencies.tmpl    | 145 +++++++++++++++++
 2 files changed, 146 insertions(+), 145 deletions(-)
 create mode 100644 templates/repo/issue/view_content/sidebar/dependencies.tmpl

diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index 35e97085c5..4c14e08013 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -40,151 +40,7 @@
 	{{if .Repository.IsDependenciesEnabled $.Context}}
 		<div class="divider"></div>
 
-		<div class="ui depending">
-			{{if (and (not .BlockedByDependencies) (not .BlockedByDependenciesNotPermitted) (not .BlockingDependencies) (not .BlockingDependenciesNotPermitted))}}
-				<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.dependency.title"}}</strong></span>
-				<br>
-				<p>
-					{{if .Issue.IsPull}}
-						{{ctx.Locale.Tr "repo.issues.dependency.pr_no_dependencies"}}
-					{{else}}
-						{{ctx.Locale.Tr "repo.issues.dependency.issue_no_dependencies"}}
-					{{end}}
-				</p>
-			{{end}}
-
-			{{if or .BlockingDependencies .BlockingDependenciesNotPermitted}}
-				<span class="text" data-tooltip-content="{{if .Issue.IsPull}}{{ctx.Locale.Tr "repo.issues.dependency.pr_close_blocks"}}{{else}}{{ctx.Locale.Tr "repo.issues.dependency.issue_close_blocks"}}{{end}}">
-					<strong>{{ctx.Locale.Tr "repo.issues.dependency.blocks_short"}}</strong>
-				</span>
-				<div class="ui relaxed divided list">
-					{{range .BlockingDependencies}}
-						<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
-							<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
-								<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
-									#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
-								</a>
-								<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
-									{{.Repository.OwnerName}}/{{.Repository.Name}}
-								</div>
-							</div>
-							<div class="item-right tw-flex tw-items-center tw-m-1">
-								{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
-									<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blocking" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
-										{{svg "octicon-trash" 16}}
-									</a>
-								{{end}}
-							</div>
-						</div>
-					{{end}}
-					{{if .BlockingDependenciesNotPermitted}}
-						<div class="item tw-flex tw-items-center tw-justify-between gt-ellipsis">
-							<span>{{ctx.Locale.TrN (len .BlockingDependenciesNotPermitted) "repo.issues.dependency.no_permission_1" "repo.issues.dependency.no_permission_n" (len .BlockingDependenciesNotPermitted)}}</span>
-						</div>
-					{{end}}
-				</div>
-			{{end}}
-
-			{{if or .BlockedByDependencies .BlockedByDependenciesNotPermitted}}
-				<span class="text" data-tooltip-content="{{if .Issue.IsPull}}{{ctx.Locale.Tr "repo.issues.dependency.pr_closing_blockedby"}}{{else}}{{ctx.Locale.Tr "repo.issues.dependency.issue_closing_blockedby"}}{{end}}">
-					<strong>{{ctx.Locale.Tr "repo.issues.dependency.blocked_by_short"}}</strong>
-				</span>
-				<div class="ui relaxed divided list">
-					{{range .BlockedByDependencies}}
-						<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
-							<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
-								<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
-									#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
-								</a>
-								<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
-									{{.Repository.OwnerName}}/{{.Repository.Name}}
-								</div>
-							</div>
-							<div class="item-right tw-flex tw-items-center tw-m-1">
-								{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
-									<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blockedBy" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
-										{{svg "octicon-trash" 16}}
-									</a>
-								{{end}}
-							</div>
-						</div>
-					{{end}}
-					{{if $.CanCreateIssueDependencies}}
-						{{range .BlockedByDependenciesNotPermitted}}
-							<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
-								<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
-									<div class="gt-ellipsis">
-										<span data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.no_permission.can_remove"}}">{{svg "octicon-lock" 16}}</span>
-										<span class="title" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
-											#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
-										</span>
-									</div>
-									<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
-										{{.Repository.OwnerName}}/{{.Repository.Name}}
-									</div>
-								</div>
-								<div class="item-right tw-flex tw-items-center tw-m-1">
-									{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
-										<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blocking" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
-											{{svg "octicon-trash" 16}}
-										</a>
-									{{end}}
-								</div>
-							</div>
-						{{end}}
-					{{else if .BlockedByDependenciesNotPermitted}}
-						<div class="item tw-flex tw-items-center tw-justify-between gt-ellipsis">
-							<span>{{ctx.Locale.TrN (len .BlockedByDependenciesNotPermitted) "repo.issues.dependency.no_permission_1" "repo.issues.dependency.no_permission_n" (len .BlockedByDependenciesNotPermitted)}}</span>
-						</div>
-					{{end}}
-				</div>
-			{{end}}
-
-			{{if and .CanCreateIssueDependencies (not .Repository.IsArchived)}}
-				<div>
-					<form method="post" action="{{.Issue.Link}}/dependency/add" id="addDependencyForm">
-						{{$.CsrfTokenHtml}}
-						<div class="ui fluid action input">
-							<div class="ui search selection dropdown" id="new-dependency-drop-list" data-issue-id="{{.Issue.ID}}">
-								<input name="newDependency" type="hidden">
-								{{svg "octicon-triangle-down" 14 "dropdown icon"}}
-								<input type="text" class="search">
-								<div class="default text">{{ctx.Locale.Tr "repo.issues.dependency.add"}}</div>
-							</div>
-							<button class="ui icon button">
-								{{svg "octicon-plus"}}
-							</button>
-						</div>
-					</form>
-				</div>
-			{{end}}
-		</div>
-
-		{{if and .CanCreateIssueDependencies (not .Repository.IsArchived)}}
-			<input type="hidden" id="crossRepoSearch" value="{{.AllowCrossRepositoryDependencies}}">
-
-			<div class="ui g-modal-confirm modal remove-dependency">
-				<div class="header">
-					{{svg "octicon-trash"}}
-					{{ctx.Locale.Tr "repo.issues.dependency.remove_header"}}
-				</div>
-				<div class="content">
-					<form method="post" action="{{.Issue.Link}}/dependency/delete" id="removeDependencyForm">
-						{{$.CsrfTokenHtml}}
-						<input type="hidden" value="" name="removeDependencyID" id="removeDependencyID">
-						<input type="hidden" value="" name="dependencyType" id="dependencyType">
-					</form>
-					<p>{{if .Issue.IsPull}}
-						{{ctx.Locale.Tr "repo.issues.dependency.pr_remove_text"}}
-					{{else}}
-						{{ctx.Locale.Tr "repo.issues.dependency.issue_remove_text"}}
-					{{end}}</p>
-				</div>
-				{{$ModalButtonCancelText := ctx.Locale.Tr "repo.issues.dependency.cancel"}}
-				{{$ModalButtonOkText := ctx.Locale.Tr "repo.issues.dependency.remove"}}
-				{{template "base/modal_actions_confirm" (dict "." . "ModalButtonCancelText" $ModalButtonCancelText "ModalButtonOkText" $ModalButtonOkText)}}
-			</div>
-		{{end}}
+		{{template "repo/issue/view_content/sidebar/dependencies" .}}
 	{{end}}
 
 	<div class="divider"></div>
diff --git a/templates/repo/issue/view_content/sidebar/dependencies.tmpl b/templates/repo/issue/view_content/sidebar/dependencies.tmpl
new file mode 100644
index 0000000000..791bd5c4a1
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/dependencies.tmpl
@@ -0,0 +1,145 @@
+<div class="ui depending">
+	{{if (and (not .BlockedByDependencies) (not .BlockedByDependenciesNotPermitted) (not .BlockingDependencies) (not .BlockingDependenciesNotPermitted))}}
+		<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.dependency.title"}}</strong></span>
+		<br>
+		<p>
+			{{if .Issue.IsPull}}
+				{{ctx.Locale.Tr "repo.issues.dependency.pr_no_dependencies"}}
+			{{else}}
+				{{ctx.Locale.Tr "repo.issues.dependency.issue_no_dependencies"}}
+			{{end}}
+		</p>
+	{{end}}
+
+	{{if or .BlockingDependencies .BlockingDependenciesNotPermitted}}
+		<span class="text" data-tooltip-content="{{if .Issue.IsPull}}{{ctx.Locale.Tr "repo.issues.dependency.pr_close_blocks"}}{{else}}{{ctx.Locale.Tr "repo.issues.dependency.issue_close_blocks"}}{{end}}">
+			<strong>{{ctx.Locale.Tr "repo.issues.dependency.blocks_short"}}</strong>
+		</span>
+		<div class="ui relaxed divided list">
+			{{range .BlockingDependencies}}
+				<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
+					<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
+						<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
+							#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
+						</a>
+						<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
+							{{.Repository.OwnerName}}/{{.Repository.Name}}
+						</div>
+					</div>
+					<div class="item-right tw-flex tw-items-center tw-m-1">
+						{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
+							<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blocking" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
+								{{svg "octicon-trash" 16}}
+							</a>
+						{{end}}
+					</div>
+				</div>
+			{{end}}
+			{{if .BlockingDependenciesNotPermitted}}
+				<div class="item tw-flex tw-items-center tw-justify-between gt-ellipsis">
+					<span>{{ctx.Locale.TrN (len .BlockingDependenciesNotPermitted) "repo.issues.dependency.no_permission_1" "repo.issues.dependency.no_permission_n" (len .BlockingDependenciesNotPermitted)}}</span>
+				</div>
+			{{end}}
+		</div>
+	{{end}}
+
+	{{if or .BlockedByDependencies .BlockedByDependenciesNotPermitted}}
+		<span class="text" data-tooltip-content="{{if .Issue.IsPull}}{{ctx.Locale.Tr "repo.issues.dependency.pr_closing_blockedby"}}{{else}}{{ctx.Locale.Tr "repo.issues.dependency.issue_closing_blockedby"}}{{end}}">
+			<strong>{{ctx.Locale.Tr "repo.issues.dependency.blocked_by_short"}}</strong>
+		</span>
+		<div class="ui relaxed divided list">
+			{{range .BlockedByDependencies}}
+				<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
+					<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
+						<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
+							#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
+						</a>
+						<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
+							{{.Repository.OwnerName}}/{{.Repository.Name}}
+						</div>
+					</div>
+					<div class="item-right tw-flex tw-items-center tw-m-1">
+						{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
+							<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blockedBy" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
+								{{svg "octicon-trash" 16}}
+							</a>
+						{{end}}
+					</div>
+				</div>
+			{{end}}
+			{{if $.CanCreateIssueDependencies}}
+				{{range .BlockedByDependenciesNotPermitted}}
+					<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
+						<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
+							<div class="gt-ellipsis">
+								<span data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.no_permission.can_remove"}}">{{svg "octicon-lock" 16}}</span>
+								<span class="title" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}">
+									#{{.Issue.Index}} {{.Issue.Title | RenderEmoji $.Context}}
+								</span>
+							</div>
+							<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
+								{{.Repository.OwnerName}}/{{.Repository.Name}}
+							</div>
+						</div>
+						<div class="item-right tw-flex tw-items-center tw-m-1">
+							{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
+								<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blocking" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
+									{{svg "octicon-trash" 16}}
+								</a>
+							{{end}}
+						</div>
+					</div>
+				{{end}}
+			{{else if .BlockedByDependenciesNotPermitted}}
+				<div class="item tw-flex tw-items-center tw-justify-between gt-ellipsis">
+					<span>{{ctx.Locale.TrN (len .BlockedByDependenciesNotPermitted) "repo.issues.dependency.no_permission_1" "repo.issues.dependency.no_permission_n" (len .BlockedByDependenciesNotPermitted)}}</span>
+				</div>
+			{{end}}
+		</div>
+	{{end}}
+
+	{{if and .CanCreateIssueDependencies (not .Repository.IsArchived)}}
+		<div>
+			<form method="post" action="{{.Issue.Link}}/dependency/add" id="addDependencyForm">
+				{{$.CsrfTokenHtml}}
+				<div class="ui fluid action input">
+					<div class="ui search selection dropdown" id="new-dependency-drop-list" data-issue-id="{{.Issue.ID}}">
+						<input name="newDependency" type="hidden">
+						{{svg "octicon-triangle-down" 14 "dropdown icon"}}
+						<input type="text" class="search">
+						<div class="default text">{{ctx.Locale.Tr "repo.issues.dependency.add"}}</div>
+					</div>
+					<button class="ui icon button">
+						{{svg "octicon-plus"}}
+					</button>
+				</div>
+			</form>
+		</div>
+	{{end}}
+</div>
+
+{{if and .CanCreateIssueDependencies (not .Repository.IsArchived)}}
+	<input type="hidden" id="crossRepoSearch" value="{{.AllowCrossRepositoryDependencies}}">
+
+	<div class="ui g-modal-confirm modal remove-dependency">
+		<div class="header">
+			{{svg "octicon-trash"}}
+			{{ctx.Locale.Tr "repo.issues.dependency.remove_header"}}
+		</div>
+		<div class="content">
+			<form method="post" action="{{.Issue.Link}}/dependency/delete" id="removeDependencyForm">
+				{{$.CsrfTokenHtml}}
+				<input type="hidden" value="" name="removeDependencyID" id="removeDependencyID">
+				<input type="hidden" value="" name="dependencyType" id="dependencyType">
+			</form>
+			<p>{{if .Issue.IsPull}}
+				{{ctx.Locale.Tr "repo.issues.dependency.pr_remove_text"}}
+			{{else}}
+				{{ctx.Locale.Tr "repo.issues.dependency.issue_remove_text"}}
+			{{end}}</p>
+		</div>
+		{{$ModalButtonCancelText := ctx.Locale.Tr "repo.issues.dependency.cancel"}}
+		{{$ModalButtonOkText := ctx.Locale.Tr "repo.issues.dependency.remove"}}
+		{{template "base/modal_actions_confirm" (dict "." . "ModalButtonCancelText" $ModalButtonCancelText "ModalButtonOkText" $ModalButtonOkText)}}
+	</div>
+{{end}}

From a8f8667a039171bd37a95a1789771c447b8ef287 Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Thu, 25 Apr 2024 14:43:51 +0200
Subject: [PATCH 018/107] Split references and actions

---
 .../repo/issue/view_content/sidebar.tmpl      | 123 +-----------------
 .../issue/view_content/sidebar/actions.tmpl   | 114 ++++++++++++++++
 .../issue/view_content/sidebar/reference.tmpl |   7 +
 3 files changed, 123 insertions(+), 121 deletions(-)
 create mode 100644 templates/repo/issue/view_content/sidebar/actions.tmpl
 create mode 100644 templates/repo/issue/view_content/sidebar/reference.tmpl

diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index 4c14e08013..b6fe04be63 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -44,131 +44,12 @@
 	{{end}}
 
 	<div class="divider"></div>
-	<div class="ui equal width compact grid">
-		{{$issueReferenceLink := printf "%s#%d" .Issue.Repo.FullName .Issue.Index}}
-		<div class="row tw-items-center" data-tooltip-content="{{$issueReferenceLink}}">
-			<span class="text column truncate">{{ctx.Locale.Tr "repo.issues.reference_link" $issueReferenceLink}}</span>
-			<button class="ui two wide button column tw-p-2" data-clipboard-text="{{$issueReferenceLink}}">{{svg "octicon-copy" 14}}</button>
-		</div>
-	</div>
+	{{template "repo/issue/view_content/sidebar/reference" .}}
 
 	{{if and .IsRepoAdmin (not .Repository.IsArchived)}}
 		<div class="divider"></div>
 
-		{{if or .PinEnabled .Issue.IsPinned}}
-			<form class="tw-mt-1 form-fetch-action single-button-form" method="post" {{if $.NewPinAllowed}}action="{{.Issue.Link}}/pin"{{else}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.max_pinned"}}"{{end}}>
-				{{$.CsrfTokenHtml}}
-				<button class="fluid ui button {{if not $.NewPinAllowed}}disabled{{end}}">
-					{{if not .Issue.IsPinned}}
-						{{svg "octicon-pin" 16 "tw-mr-2"}}
-						{{ctx.Locale.Tr "pin"}}
-					{{else}}
-						{{svg "octicon-pin-slash" 16 "tw-mr-2"}}
-						{{ctx.Locale.Tr "unpin"}}
-					{{end}}
-				</button>
-			</form>
-		{{end}}
-
-		<button class="tw-mt-1 fluid ui show-modal button {{if .Issue.IsLocked}} negative {{end}}" data-modal="#lock">
-			{{if .Issue.IsLocked}}
-				{{svg "octicon-key"}}
-				{{ctx.Locale.Tr "repo.issues.unlock"}}
-			{{else}}
-				{{svg "octicon-lock"}}
-				{{ctx.Locale.Tr "repo.issues.lock"}}
-			{{end}}
-		</button>
-		<div class="ui tiny modal" id="lock">
-			<div class="header">
-				{{if .Issue.IsLocked}}
-					{{ctx.Locale.Tr "repo.issues.unlock.title"}}
-				{{else}}
-					{{ctx.Locale.Tr "repo.issues.lock.title"}}
-				{{end}}
-			</div>
-			<div class="content">
-				<div class="ui warning message">
-					{{if .Issue.IsLocked}}
-						{{ctx.Locale.Tr "repo.issues.unlock.notice_1"}}<br>
-						{{ctx.Locale.Tr "repo.issues.unlock.notice_2"}}<br>
-					{{else}}
-						{{ctx.Locale.Tr "repo.issues.lock.notice_1"}}<br>
-						{{ctx.Locale.Tr "repo.issues.lock.notice_2"}}<br>
-						{{ctx.Locale.Tr "repo.issues.lock.notice_3"}}<br>
-					{{end}}
-				</div>
-
-				<form class="ui form form-fetch-action" action="{{.Issue.Link}}{{if .Issue.IsLocked}}/unlock{{else}}/lock{{end}}"
-					method="post">
-					{{.CsrfTokenHtml}}
-
-					{{if not .Issue.IsLocked}}
-						<div class="field">
-							<strong> {{ctx.Locale.Tr "repo.issues.lock.reason"}} </strong>
-						</div>
-
-						<div class="field">
-							<div class="ui fluid dropdown selection">
-
-								<select name="reason">
-									<option value=""> </option>
-									{{range .LockReasons}}
-										<option value="{{.}}">{{.}}</option>
-									{{end}}
-								</select>
-								{{svg "octicon-triangle-down" 14 "dropdown icon"}}
-
-								<div class="default text"> </div>
-
-								<div class="menu">
-									{{range .LockReasons}}
-										<div class="item" data-value="{{.}}">{{.}}</div>
-									{{end}}
-								</div>
-							</div>
-						</div>
-					{{end}}
-
-					<div class="text right actions">
-						<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
-						<button class="ui red button">
-							{{if .Issue.IsLocked}}
-								{{ctx.Locale.Tr "repo.issues.unlock_confirm"}}
-							{{else}}
-								{{ctx.Locale.Tr "repo.issues.lock_confirm"}}
-							{{end}}
-						</button>
-					</div>
-				</form>
-			</div>
-		</div>
-		<button class="tw-mt-1 fluid ui show-modal button" data-modal="#sidebar-delete-issue">
-			{{svg "octicon-trash"}}
-			{{ctx.Locale.Tr "repo.issues.delete"}}
-		</button>
-		<div class="ui g-modal-confirm modal" id="sidebar-delete-issue">
-			<div class="header">
-				{{if .Issue.IsPull}}
-					{{ctx.Locale.Tr "repo.pulls.delete.title"}}
-				{{else}}
-					{{ctx.Locale.Tr "repo.issues.delete.title"}}
-				{{end}}
-			</div>
-			<div class="content">
-				<p>
-					{{if .Issue.IsPull}}
-						{{ctx.Locale.Tr "repo.pulls.delete.text"}}
-					{{else}}
-						{{ctx.Locale.Tr "repo.issues.delete.text"}}
-					{{end}}
-				</p>
-			</div>
-			<form action="{{.Issue.Link}}/delete" method="post">
-				{{.CsrfTokenHtml}}
-				{{template "base/modal_actions_confirm" .}}
-			</form>
-		</div>
+		{{template "repo/issue/view_content/sidebar/actions" .}}
 	{{end}}
 
 	{{if and .Issue.IsPull .IsIssuePoster (not .Issue.IsClosed) .Issue.PullRequest.HeadRepo}}
diff --git a/templates/repo/issue/view_content/sidebar/actions.tmpl b/templates/repo/issue/view_content/sidebar/actions.tmpl
new file mode 100644
index 0000000000..36f21822aa
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/actions.tmpl
@@ -0,0 +1,114 @@
+{{if or .PinEnabled .Issue.IsPinned}}
+	<form class="tw-mt-1 form-fetch-action single-button-form" method="post" {{if $.NewPinAllowed}}action="{{.Issue.Link}}/pin"{{else}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.max_pinned"}}"{{end}}>
+		{{$.CsrfTokenHtml}}
+		<button class="fluid ui button {{if not $.NewPinAllowed}}disabled{{end}}">
+			{{if not .Issue.IsPinned}}
+				{{svg "octicon-pin" 16 "tw-mr-2"}}
+				{{ctx.Locale.Tr "pin"}}
+			{{else}}
+				{{svg "octicon-pin-slash" 16 "tw-mr-2"}}
+				{{ctx.Locale.Tr "unpin"}}
+			{{end}}
+		</button>
+	</form>
+{{end}}
+
+<button class="tw-mt-1 fluid ui show-modal button {{if .Issue.IsLocked}} negative {{end}}" data-modal="#lock">
+	{{if .Issue.IsLocked}}
+		{{svg "octicon-key"}}
+		{{ctx.Locale.Tr "repo.issues.unlock"}}
+	{{else}}
+		{{svg "octicon-lock"}}
+		{{ctx.Locale.Tr "repo.issues.lock"}}
+	{{end}}
+</button>
+<div class="ui tiny modal" id="lock">
+	<div class="header">
+		{{if .Issue.IsLocked}}
+			{{ctx.Locale.Tr "repo.issues.unlock.title"}}
+		{{else}}
+			{{ctx.Locale.Tr "repo.issues.lock.title"}}
+		{{end}}
+	</div>
+	<div class="content">
+		<div class="ui warning message">
+			{{if .Issue.IsLocked}}
+				{{ctx.Locale.Tr "repo.issues.unlock.notice_1"}}<br>
+				{{ctx.Locale.Tr "repo.issues.unlock.notice_2"}}<br>
+			{{else}}
+				{{ctx.Locale.Tr "repo.issues.lock.notice_1"}}<br>
+				{{ctx.Locale.Tr "repo.issues.lock.notice_2"}}<br>
+				{{ctx.Locale.Tr "repo.issues.lock.notice_3"}}<br>
+			{{end}}
+		</div>
+
+		<form class="ui form form-fetch-action" action="{{.Issue.Link}}{{if .Issue.IsLocked}}/unlock{{else}}/lock{{end}}"
+			method="post">
+			{{.CsrfTokenHtml}}
+
+			{{if not .Issue.IsLocked}}
+				<div class="field">
+					<strong> {{ctx.Locale.Tr "repo.issues.lock.reason"}} </strong>
+				</div>
+
+				<div class="field">
+					<div class="ui fluid dropdown selection">
+
+						<select name="reason">
+							<option value=""> </option>
+							{{range .LockReasons}}
+								<option value="{{.}}">{{.}}</option>
+							{{end}}
+						</select>
+						{{svg "octicon-triangle-down" 14 "dropdown icon"}}
+
+						<div class="default text"> </div>
+
+						<div class="menu">
+							{{range .LockReasons}}
+								<div class="item" data-value="{{.}}">{{.}}</div>
+							{{end}}
+						</div>
+					</div>
+				</div>
+			{{end}}
+
+			<div class="text right actions">
+				<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
+				<button class="ui red button">
+					{{if .Issue.IsLocked}}
+						{{ctx.Locale.Tr "repo.issues.unlock_confirm"}}
+					{{else}}
+						{{ctx.Locale.Tr "repo.issues.lock_confirm"}}
+					{{end}}
+				</button>
+			</div>
+		</form>
+	</div>
+</div>
+<button class="tw-mt-1 fluid ui show-modal button" data-modal="#sidebar-delete-issue">
+	{{svg "octicon-trash"}}
+	{{ctx.Locale.Tr "repo.issues.delete"}}
+</button>
+<div class="ui g-modal-confirm modal" id="sidebar-delete-issue">
+	<div class="header">
+		{{if .Issue.IsPull}}
+			{{ctx.Locale.Tr "repo.pulls.delete.title"}}
+		{{else}}
+			{{ctx.Locale.Tr "repo.issues.delete.title"}}
+		{{end}}
+	</div>
+	<div class="content">
+		<p>
+			{{if .Issue.IsPull}}
+				{{ctx.Locale.Tr "repo.pulls.delete.text"}}
+			{{else}}
+				{{ctx.Locale.Tr "repo.issues.delete.text"}}
+			{{end}}
+		</p>
+	</div>
+	<form action="{{.Issue.Link}}/delete" method="post">
+		{{.CsrfTokenHtml}}
+		{{template "base/modal_actions_confirm" .}}
+	</form>
+</div>
diff --git a/templates/repo/issue/view_content/sidebar/reference.tmpl b/templates/repo/issue/view_content/sidebar/reference.tmpl
new file mode 100644
index 0000000000..bbbc099558
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/reference.tmpl
@@ -0,0 +1,7 @@
+<div class="ui equal width compact grid">
+	{{$issueReferenceLink := printf "%s#%d" .Issue.Repo.FullName .Issue.Index}}
+	<div class="row tw-items-center" data-tooltip-content="{{$issueReferenceLink}}">
+		<span class="text column truncate">{{ctx.Locale.Tr "repo.issues.reference_link" $issueReferenceLink}}</span>
+		<button class="ui two wide button column tw-p-2" data-clipboard-text="{{$issueReferenceLink}}">{{svg "octicon-copy" 14}}</button>
+	</div>
+</div>

From 6bd4925753665d87bef393e869cc24f2f11cc1f5 Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Thu, 25 Apr 2024 14:56:00 +0200
Subject: [PATCH 019/107] Split allow edits checkbox

---
 templates/repo/issue/view_content/sidebar.tmpl     | 14 +-------------
 .../sidebar/pull_maintainer_edits.tmpl             | 13 +++++++++++++
 2 files changed, 14 insertions(+), 13 deletions(-)
 create mode 100644 templates/repo/issue/view_content/sidebar/pull_maintainer_edits.tmpl

diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index b6fe04be63..5740b79adb 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -53,18 +53,6 @@
 	{{end}}
 
 	{{if and .Issue.IsPull .IsIssuePoster (not .Issue.IsClosed) .Issue.PullRequest.HeadRepo}}
-		{{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
-			<div class="divider"></div>
-			<div class="inline field">
-				<div class="ui checkbox loading-icon-2px" id="allow-edits-from-maintainers"
-						data-url="{{.Issue.Link}}"
-						data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"
-						data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}"
-					>
-					<label><strong>{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers"}}</strong></label>
-					<input type="checkbox" {{if .Issue.PullRequest.AllowMaintainerEdit}}checked{{end}}>
-				</div>
-			</div>
-		{{end}}
+		{{template "repo/issue/view_content/sidebar/pull_maintainer_edits" .}}
 	{{end}}
 </div>
diff --git a/templates/repo/issue/view_content/sidebar/pull_maintainer_edits.tmpl b/templates/repo/issue/view_content/sidebar/pull_maintainer_edits.tmpl
new file mode 100644
index 0000000000..c6a87adde4
--- /dev/null
+++ b/templates/repo/issue/view_content/sidebar/pull_maintainer_edits.tmpl
@@ -0,0 +1,13 @@
+{{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
+	<div class="divider"></div>
+	<div class="inline field">
+		<div class="ui checkbox loading-icon-2px" id="allow-edits-from-maintainers"
+				data-url="{{.Issue.Link}}"
+				data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"
+				data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}"
+			>
+			<label><strong>{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers"}}</strong></label>
+			<input type="checkbox" {{if .Issue.PullRequest.AllowMaintainerEdit}}checked{{end}}>
+		</div>
+	</div>
+{{end}}

From 66b22c58187649c4fc03dfff2c90d6a8de790465 Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Thu, 25 Apr 2024 15:01:16 +0200
Subject: [PATCH 020/107] Unify allow edits logic

---
 .../repo/issue/view_content/sidebar.tmpl      | 10 ++++++++-
 .../sidebar/pull_maintainer_edits.tmpl        | 21 ++++++++-----------
 2 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index 5740b79adb..ba15539841 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -52,7 +52,15 @@
 		{{template "repo/issue/view_content/sidebar/actions" .}}
 	{{end}}
 
-	{{if and .Issue.IsPull .IsIssuePoster (not .Issue.IsClosed) .Issue.PullRequest.HeadRepo}}
+	{{if and
+		.Issue.IsPull
+		.IsIssuePoster
+		(not .Issue.IsClosed)
+		.Issue.PullRequest.HeadRepo
+		(not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName))
+		.CanWriteToHeadRepo
+	}}
+		<div class="divider"></div>
 		{{template "repo/issue/view_content/sidebar/pull_maintainer_edits" .}}
 	{{end}}
 </div>
diff --git a/templates/repo/issue/view_content/sidebar/pull_maintainer_edits.tmpl b/templates/repo/issue/view_content/sidebar/pull_maintainer_edits.tmpl
index c6a87adde4..6ec5c05fd7 100644
--- a/templates/repo/issue/view_content/sidebar/pull_maintainer_edits.tmpl
+++ b/templates/repo/issue/view_content/sidebar/pull_maintainer_edits.tmpl
@@ -1,13 +1,10 @@
-{{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
-	<div class="divider"></div>
-	<div class="inline field">
-		<div class="ui checkbox loading-icon-2px" id="allow-edits-from-maintainers"
-				data-url="{{.Issue.Link}}"
-				data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"
-				data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}"
-			>
-			<label><strong>{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers"}}</strong></label>
-			<input type="checkbox" {{if .Issue.PullRequest.AllowMaintainerEdit}}checked{{end}}>
-		</div>
+<div class="inline field">
+	<div class="ui checkbox loading-icon-2px" id="allow-edits-from-maintainers"
+			data-url="{{.Issue.Link}}"
+			data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"
+			data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}"
+		>
+		<label><strong>{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers"}}</strong></label>
+		<input type="checkbox" {{if .Issue.PullRequest.AllowMaintainerEdit}}checked{{end}}>
 	</div>
-{{end}}
+</div>

From 5bc72998e0d023cd30bd4474c5a0b37e78345868 Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Thu, 25 Apr 2024 15:08:47 +0200
Subject: [PATCH 021/107] =?UTF-8?q?I=20feel=20responsible=20=E2=80=A6=20(C?=
 =?UTF-8?q?odeowners)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

… for dealing with conflicts and future modification of this template
---
 CODEOWNERS | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/CODEOWNERS b/CODEOWNERS
index 88c71ba17e..e30d2c42b4 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -16,6 +16,8 @@ web_src/.* @caesar @crystal @gusted
 
 # HTML templates used by the backend.
 templates/.* @caesar @crystal @gusted
+## the issue sidebar was touched by fnetx
+templates/repo/issue/view_content/sidebar.* @fnetx
 
 # Files related to Go development.
 

From 004fe91d37ef019962df34dd3c9a678c5ad439bf Mon Sep 17 00:00:00 2001
From: Renovate Bot <forgejo-renovate-action@forgejo.org>
Date: Sat, 27 Apr 2024 02:04:59 +0000
Subject: [PATCH 022/107] Update module gitea.com/gitea/act to v0.261.1

---
 go.mod | 12 ++++++------
 go.sum | 23 ++++++++++++-----------
 2 files changed, 18 insertions(+), 17 deletions(-)

diff --git a/go.mod b/go.mod
index 2f4c64b2da..b403d57912 100644
--- a/go.mod
+++ b/go.mod
@@ -89,13 +89,13 @@ require (
 	github.com/sassoftware/go-rpmutils v0.2.1-0.20240124161140-277b154961dd
 	github.com/sergi/go-diff v1.3.1
 	github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
-	github.com/stretchr/testify v1.8.4
+	github.com/stretchr/testify v1.9.0
 	github.com/syndtr/goleveldb v1.0.0
 	github.com/ulikunitz/xz v0.5.11
 	github.com/urfave/cli/v2 v2.27.1
 	github.com/xanzy/go-gitlab v0.96.0
 	github.com/yohcop/openid-go v1.0.1
-	github.com/yuin/goldmark v1.6.0
+	github.com/yuin/goldmark v1.7.0
 	github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
 	github.com/yuin/goldmark-meta v1.1.0
 	golang.org/x/crypto v0.21.0
@@ -243,8 +243,8 @@ require (
 	github.com/prometheus/client_model v0.5.0 // indirect
 	github.com/prometheus/common v0.46.0 // indirect
 	github.com/prometheus/procfs v0.12.0 // indirect
-	github.com/rhysd/actionlint v1.6.26 // indirect
-	github.com/rivo/uniseg v0.4.4 // indirect
+	github.com/rhysd/actionlint v1.6.27 // indirect
+	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/rogpeppe/go-internal v1.12.0 // indirect
 	github.com/rs/xid v1.5.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
@@ -272,7 +272,7 @@ require (
 	github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
 	github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
 	github.com/zeebo/blake3 v0.2.3 // indirect
-	go.etcd.io/bbolt v1.3.8 // indirect
+	go.etcd.io/bbolt v1.3.9 // indirect
 	go.mongodb.org/mongo-driver v1.13.1 // indirect
 	go.opentelemetry.io/otel v1.22.0 // indirect
 	go.opentelemetry.io/otel/trace v1.22.0 // indirect
@@ -294,7 +294,7 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
 
 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
 
-replace github.com/nektos/act => gitea.com/gitea/act v0.259.1
+replace github.com/nektos/act => gitea.com/gitea/act v0.261.1
 
 replace github.com/gorilla/feeds => github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5
 
diff --git a/go.sum b/go.sum
index ce55e89399..6d69037b56 100644
--- a/go.sum
+++ b/go.sum
@@ -52,8 +52,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
 git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
-gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw=
-gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8=
+gitea.com/gitea/act v0.261.1 h1:iACWLc/k8wct9fCF2WdYKqn2Hxx6NjW9zbOP79HF4H4=
+gitea.com/gitea/act v0.261.1/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
 gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669 h1:RUBX+MK/TsDxpHmymaOaydfigEbbzqUnG1OTZU/HAeo=
 gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc=
 gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
@@ -708,11 +708,11 @@ github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwy
 github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
-github.com/rhysd/actionlint v1.6.26 h1:zi7jPZf3Ks14gCXYAAL47uBziyFlX7+Xwilqhexct9g=
-github.com/rhysd/actionlint v1.6.26/go.mod h1:TIj1DlCgtYLOv5CH9wCK+WJTOr1qAdnFzkGi0IgSCO4=
+github.com/rhysd/actionlint v1.6.27 h1:xxwe8YmveBcC8lydW6GoHMGmB6H/MTqUU60F2p10wjw=
+github.com/rhysd/actionlint v1.6.27/go.mod h1:m2nFUjAnOrxCMXuOMz9evYBRCLUsMnKY2IJl/N5umbk=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
-github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -785,8 +785,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
 github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
@@ -837,8 +838,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
-github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
+github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
 github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
@@ -849,8 +850,8 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
 github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
 github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
 github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
-go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
-go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
+go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
+go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
 go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
 go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
 go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=

From d90b1e2c005bc7fe1fc2bbdc931362505d9230e9 Mon Sep 17 00:00:00 2001
From: Earl Warren <contact@earl-warren.org>
Date: Sat, 27 Apr 2024 10:31:13 +0200
Subject: [PATCH 023/107] fix(renovate): gitea.com/gitea/act requires dashboard
 approval

---
 renovate.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/renovate.json b/renovate.json
index 74432c9dca..c395194167 100644
--- a/renovate.json
+++ b/renovate.json
@@ -37,7 +37,8 @@
       "matchDepNames": [
         "bitnami/minio",
         "github.com/go-ap/activitypub",
-        "github.com/nektos/act"
+        "github.com/nektos/act",
+        "gitea.com/gitea/act"
       ],
       "dependencyDashboardApproval": true
     },

From 3db929a2bee612afe94cc6556d67ff3f86fa6213 Mon Sep 17 00:00:00 2001
From: Earl Warren <contact@earl-warren.org>
Date: Sat, 27 Apr 2024 10:43:27 +0200
Subject: [PATCH 024/107] chore(licenses): github.com/minio/sha256-simd is no
 longer in use

---
 assets/go-licenses.json | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/assets/go-licenses.json b/assets/go-licenses.json
index 472fcb495b..62971df47f 100644
--- a/assets/go-licenses.json
+++ b/assets/go-licenses.json
@@ -749,11 +749,6 @@
     "path": "github.com/minio/minio-go/v7/LICENSE",
     "licenseText": "\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 [yyyy] [name of copyright owner]\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/minio/sha256-simd",
-    "path": "github.com/minio/sha256-simd/LICENSE",
-    "licenseText": "\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 [yyyy] [name of copyright owner]\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/mitchellh/mapstructure",
     "path": "github.com/mitchellh/mapstructure/LICENSE",

From cb004e9d8bb2489d2f768bd04204b876abaef01a Mon Sep 17 00:00:00 2001
From: Codeberg Translate <translate@noreply.codeberg.org>
Date: Sat, 27 Apr 2024 16:37:31 +0000
Subject: [PATCH 025/107] [I18N] Translations update from Weblate (#3359)

Translations update from [Weblate](https://translate.codeberg.org) for [Forgejo/forgejo](https://translate.codeberg.org/projects/forgejo/forgejo/).

Current translation status:

![Weblate translation status](https://translate.codeberg.org/widget/forgejo/forgejo/horizontal-auto.svg)

Co-authored-by: earl-warren <earl-warren@users.noreply.translate.codeberg.org>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: 0ko <0ko@users.noreply.translate.codeberg.org>
Co-authored-by: Dirk <Dirk@users.noreply.translate.codeberg.org>
Co-authored-by: Salif Mehmed <mail@salif.eu>
Co-authored-by: Kita Ikuyo <searinminecraft@courvix.com>
Co-authored-by: leana8959 <leana8959@users.noreply.translate.codeberg.org>
Co-authored-by: yeziruo <yeziruo@users.noreply.translate.codeberg.org>
Co-authored-by: SteffoSpieler <SteffoSpieler@users.noreply.translate.codeberg.org>
Co-authored-by: Mylloon <Mylloon@users.noreply.translate.codeberg.org>
Co-authored-by: lucasmz <lucasmz@users.noreply.translate.codeberg.org>
Co-authored-by: emansije <emansije@users.noreply.translate.codeberg.org>
Co-authored-by: FunctionalHacker <FunctionalHacker@users.noreply.translate.codeberg.org>
Co-authored-by: owofied <furry@users.noreply.translate.codeberg.org>
Co-authored-by: Xinayder <Xinayder@users.noreply.translate.codeberg.org>
Co-authored-by: kecrily <kecrily@users.noreply.translate.codeberg.org>
Co-authored-by: ZilloweZ <ZilloweZ@users.noreply.translate.codeberg.org>
Co-authored-by: toasterbirb <toasterbirb@users.noreply.translate.codeberg.org>
Co-authored-by: Pi-Cla <Pi-Cla@users.noreply.translate.codeberg.org>
Co-authored-by: sinsky <sinsky@users.noreply.translate.codeberg.org>
Co-authored-by: kdh8219 <kdh8219@users.noreply.translate.codeberg.org>
Co-authored-by: 747 <747@users.noreply.translate.codeberg.org>
Co-authored-by: Quitaxd <Quitaxd@users.noreply.translate.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3359
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: Codeberg Translate <translate@noreply.codeberg.org>
Co-committed-by: Codeberg Translate <translate@noreply.codeberg.org>
---
 options/locale/locale_ar.ini    |   1 +
 options/locale/locale_bg.ini    |   6 +-
 options/locale/locale_cs-CZ.ini |   7 +-
 options/locale/locale_de-DE.ini |  11 +-
 options/locale/locale_fi-FI.ini |  26 ++
 options/locale/locale_fil.ini   |  40 ++-
 options/locale/locale_fr-FR.ini |  93 ++++---
 options/locale/locale_ja-JP.ini | 134 +++++++++-
 options/locale/locale_ko-KR.ini |   3 +
 options/locale/locale_pl-PL.ini | 127 ++++++++--
 options/locale/locale_pt-BR.ini | 140 +++++------
 options/locale/locale_pt-PT.ini | 337 +++++++++++++++----------
 options/locale/locale_ru-RU.ini |  62 ++---
 options/locale/locale_tr-TR.ini |  15 ++
 options/locale/locale_zh-CN.ini |  22 +-
 options/locale/locale_zh-HK.ini |  26 ++
 options/locale/locale_zh-TW.ini | 426 +++++++++++++++++++++++---------
 17 files changed, 1064 insertions(+), 412 deletions(-)

diff --git a/options/locale/locale_ar.ini b/options/locale/locale_ar.ini
index e58f7dfdf7..bcd4b6af9f 100644
--- a/options/locale/locale_ar.ini
+++ b/options/locale/locale_ar.ini
@@ -127,6 +127,7 @@ remove_all = أزل الكل
 remove_label_str = أزل العنصر "%s"
 confirm_delete_artifact = هل أنت متأكد أنك تريد حذف العنصر '%s'؟
 toggle_menu = تبديل القائمة
+more_items = عناصر اضافية
 
 [install]
 db_name = اسم قاعدة البيانات
diff --git a/options/locale/locale_bg.ini b/options/locale/locale_bg.ini
index 3e47d9e628..d7093b85d5 100644
--- a/options/locale/locale_bg.ini
+++ b/options/locale/locale_bg.ini
@@ -983,7 +983,7 @@ activity.no_git_activity = Не е имало никаква дейност с 
 pulls.merged_title_desc_few = сля %[1]d подавания от <code>%[2]s</code> в <code>%[3]s</code> %[4]s
 diff.stats_desc_file = %d промени: %d добавяния и %d изтривания
 issues.content_history.created = създадено
-pulls.status_checks_success = Всички проверки бяха успешни
+pulls.status_checks_success = Всички проверки са успешни
 activity.git_stats_pushed_n = са изтласкали
 pulls.select_commit_hold_shift_for_range = Изберете подаване. Задръжте shift + click, за да изберете обхвата
 activity.git_stats_addition_1 = %d добавяне
@@ -1084,7 +1084,7 @@ issues.review.un_resolve_conversation = Нерешаване на обсъжда
 diff.comment.add_single_comment = Добавяне на единичен коментар
 diff.review.header = Изпращане на рецензия
 diff.comment.start_review = Започване на рецензия
-diff.review = Рецензиране
+diff.review = Завършване на рецензията
 diff.review.placeholder = Рецензионен коментар
 issues.num_participants_one = %d участващ
 diff.comment.reply = Отговаряне
@@ -1128,6 +1128,8 @@ issues.draft_title = Чернова
 pulls.reopen_to_merge = Моля, отворете наново тази заявка за сливане, за да извършите сливане.
 pulls.cant_reopen_deleted_branch = Тази заявка за сливане не може да бъде отворена наново, защото клонът бе изтрит.
 pulls.status_checks_hide_all = Скриване на всички проверки
+pulls.status_checks_failure = Някои проверки са неуспешни
+issues.review.add_review_request = поиска рецензия от %s %s
 
 [modal]
 confirm = Потвърждаване
diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini
index 2cdec52aef..49aabeaf05 100644
--- a/options/locale/locale_cs-CZ.ini
+++ b/options/locale/locale_cs-CZ.ini
@@ -159,6 +159,7 @@ filter.not_archived = Není archivováno
 filter.clear = Vymazat filtry
 more_items = Další položky
 invalid_data = Neplatná data: %v
+copy_generic = Kopírovat do schránky
 
 [aria]
 navbar=Navigační lišta
@@ -2517,7 +2518,7 @@ diff.comment.add_single_comment=Přidat jeden komentář
 diff.comment.add_review_comment=Přidat komentář
 diff.comment.start_review=Začít posuzování
 diff.comment.reply=Odpovědět
-diff.review=Posouzení
+diff.review=Dokončit posouzení
 diff.review.header=Odeslat posouzení
 diff.review.placeholder=Posoudit komentář
 diff.review.comment=Okomentovat
@@ -2749,6 +2750,10 @@ settings.sourcehut_builds.secrets = Tajné klíče
 settings.sourcehut_builds.secrets_helper = Udělit práci přístup k tajným klíčům sestavení (vyžaduje oprávnění SECRETS:RO)
 settings.graphql_url = URL GraphQL
 settings.sourcehut_builds.access_token_helper = Přístupový token, který má oprávnění JOBS:RW. Vygenerujte <a target="_blank" rel="noopener noreferrer" href="%s">token builds.sr.ht</a> nebo <a target="_blank" rel="noopener noreferrer" href="%s">token builds.sr.ht s přístupem k tajným klíčům</a> na meta.sr.ht.
+settings.matrix.room_id_helper = ID místnosti lze získat z webového klienta Element > Nastavení místnosti > Rozšířené > Interní ID místnosti. Příklad: %s.
+settings.matrix.access_token_helper = Pro tuto akci je doporučeno vytvořit oddělený účet Matrix. Přístupový token lze získat z webového klienta Element (v soukromé/anonymní kartě) > Uživatelské menu (vlevo nahoře) > Všechna nastavení > O aplikaci a pomoc > Rozšířené > Přístupový token (přímo pod adresou domovského serveru). Soukromou/anonymní kartu zavřete (odhlášením token zneplatníte).
+release.hide_archive_links = Skrýt automaticky generované archivy
+release.hide_archive_links_helper = Pro toto vydání skrýt automaticky generované archivy zdrojového kódu. Užitečné například pokud nahráváte své vlastní.
 
 [graphs]
 component_loading_info = Tohle může chvíli trvat…
diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini
index 7cc59a9d63..07ea493409 100644
--- a/options/locale/locale_de-DE.ini
+++ b/options/locale/locale_de-DE.ini
@@ -157,6 +157,7 @@ filter.public = Öffentlich
 filter.private = Privat
 more_items = Mehr Einträge
 invalid_data = Ungültige Daten: %v
+copy_generic = In die Zwischenablage kopieren
 
 [aria]
 navbar=Navigationsleiste
@@ -644,13 +645,13 @@ change_avatar=Profilbild ändern …
 joined_on=Beigetreten am %s
 repositories=Repositorys
 activity=Öffentliche Aktivität
-followers_few=%d follower
+followers_few=%d Follower
 starred=Favorisierte Repositorys
 watched=Beobachtete Repositorys
 code=Quelltext
 projects=Projekte
 overview=Übersicht
-following_few=%d folge ich
+following_few=%d Folge ich
 follow=Folgen
 unfollow=Nicht mehr folgen
 user_bio=Biografie
@@ -672,7 +673,7 @@ follow_blocked_user = Du kannst diesen Benutzer nicht folgen, weil du ihn blocki
 block_user.detail_3 = Dieser Benutzer kann dich nicht als einen Mitarbeiter hinzufügen, und du kannst ihn nicht als Mitarbeiter hinzufügen.
 unblock = Nicht mehr blockieren
 followers_one = %d Follower
-following_one = einem gefolgt
+following_one = %d Folge ich
 
 [settings]
 profile=Profil
@@ -2514,7 +2515,7 @@ diff.comment.add_single_comment=Einzelnen Kommentar hinzufügen
 diff.comment.add_review_comment=Kommentar hinzufügen
 diff.comment.start_review=Review starten
 diff.comment.reply=Antworten
-diff.review=Reviewen
+diff.review=Review abschließen
 diff.review.header=Review einreichen
 diff.review.placeholder=Kommentar zum Review
 diff.review.comment=Kommentieren
@@ -2735,6 +2736,8 @@ settings.graphql_url = GraphQL-URL
 settings.matrix.room_id_helper = Die Raum-ID kann über den Element-Webclient ermittelt werden: Raumeinstellungen > Erweitert > Interne Raum-ID. Beispielsweise %s.
 settings.sourcehut_builds.access_token_helper = Zugangstoken der die JOBS:RW-Freigabe hat. Generiere auf meta.sr.ht einen <a target="_blank" rel="noopener noreferrer" href="%s">builds.sr.ht-Token</a> oder einen <a target="_blank" rel="noopener noreferrer" href="%s">builds.sr.ht-Token mit Zugriff auf die Secrets</a>.
 settings.matrix.access_token_helper = Es wird empfohlen, einen dedizierten Matrix-Account hierfür anzulegen. Der Zugangstoken kann in einem Incognito-Tab über den Element-Webclient geholt werden: Benutzermenü (oben links) > Alle Einstellungen > Hilfe & Über > Erweitert > Zugangstoken (direkt unter der Homeserver-URL). Schließe das Incognito-Tab dann (Abmelden würde den Token ungültig werden lassen).
+release.hide_archive_links = Automatisch generierte Archive verstecken
+release.hide_archive_links_helper = Verstecke automatisch generierte Quellcodearchive für diesen Release. Zum Beispiel, wenn du deine eigenen hochlädst.
 
 [graphs]
 
diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini
index b56d9c8967..1560075d6b 100644
--- a/options/locale/locale_fi-FI.ini
+++ b/options/locale/locale_fi-FI.ini
@@ -114,14 +114,40 @@ concept_user_organization=Organisaatio
 
 
 name=Nimi
+enable_javascript = Tämä sivu vaatii Javascriptin.
+new_project_column = Uusi sarake
+retry = Yritä uudelleen
+copy_type_unsupported = Tätä tiedostotyyppiä ei voi kopioida
+locked = Lukittu
+filter = Suodatin
+filter.is_archived = Arkistoitu
+filter.not_archived = Ei arkistoitu
+filter.public = Julkinen
+filter.private = Yksityinen
+copy_content = Kopioi sisältö
+download_logs = Lataa lokit
+show_full_screen = Näytä koko näytössä
+unknown = Tuntematon
+show_timestamps = Näytä aikaleimat
+show_log_seconds = Näytä sekunnit
+copy_generic = Kopioi leikepöydälle
 
 [aria]
+footer.links = Linkit
 
 [heatmap]
+less = Vähemmän
+more = Enemmän
 
 [editor]
+buttons.code.tooltip = Lisää koodia
+buttons.link.tooltip = Lisää linkki
+buttons.mention.tooltip = Mainitse käyttäjä tai tiimi
+buttons.list.task.tooltip = Lisää tehtävälista
 
 [filter]
+string.asc = A - Ö
+string.desc = Ö - A
 
 [error]
 occurred=Virhe tapahtui
diff --git a/options/locale/locale_fil.ini b/options/locale/locale_fil.ini
index 68dbab40a4..45430fe366 100644
--- a/options/locale/locale_fil.ini
+++ b/options/locale/locale_fil.ini
@@ -140,6 +140,7 @@ home = Panimula
 dashboard = Dashboard
 more_items = Higit pang mga item
 invalid_data = Hindi wastong datos: %v
+copy_generic = Kopyahin sa clipboard
 
 [home]
 search_repos = Maghanap ng Repository…
@@ -592,6 +593,12 @@ admin_cannot_delete_self = Hindi mo maaring burahin ang sarili mo kapag isa kang
 required_prefix = Ang input ay dapat magsimula sa "%s"
 FullName = Buong pangalan
 Description = Paglalarawan
+Pronouns = Mga panghalip
+Website = Website
+To = Pangalan ng branch
+AccessToken = Token ng pag-access
+Biography = Byograpya
+Location = Lokasyon
 
 [user]
 joined_on = Sumali noong %s
@@ -922,7 +929,7 @@ remove_account_link_success = Tinanggal na ang naka-link na account.
 visibility.limited_tooltip = Makikita lamang ng mga naka-authenticate na user
 webauthn_delete_key_desc = Kapag magtanggal ka ng security key hindi ka na makaka-sign in gamit niyan. Magpatuloy?
 manage_account_links_desc = Ang mga panlabas na account na ito ay naka-link sa iyong Forgejo account.
-hooks.desc = Magdagdag mg mga webhook na mati-trigger para sa <strong>lahat ng mga repositoryo</strong> na minamay-ari mo.
+hooks.desc = Magdagdag ng mga webhook na mati-trigger para sa <strong>lahat ng mga repositoryo</strong> na minamay-ari mo.
 orgs_none = Hindi ka isang miyembro ng anumang mga organisasyon.
 oauth2_application_create_description = Ang mga OAuth2 application ay pinapayagan ang mga third-party na aplikasyon na i-access ang mga user account sa instansya na ito.
 oauth2_application_locked = Ang Forgejo ay pini-pre register ang ibang mga OAuth2 application sa startup kapag naka-enable sa config. Para iwasan ang hindi inaasahang gawain, hindi ito maaring i-edit o tanggalin. Mangyaring sumangguni sa dokumentasyon ng OAuth2 para sa karagdagang impormasyon.
@@ -1429,6 +1436,12 @@ milestones.title = Pamagat
 milestones.desc = paglalarawan
 pulls.blocked_by_user = Hindi ka makakagawa ng [pull request] sa [repository] na ito dahil hinarang ka ng may-ari ng [repository].
 pulls.no_merge_access = Hindi ka pinapayagang isali ang [pull request] na ito.
+editor.commit_directly_to_this_branch = Direktang mag-commit sa branch na <strong class="branch-name">%s</strong>.
+editor.branch_already_exists = Umiiral na ang branch na "%s" sa repositoryo na ito.
+editor.file_editing_no_longer_exists = Ang file na ine-edit, "%s", ay hindi na umiiral sa repositoryo na ito.
+editor.filename_is_a_directory = Ang pangalan ng file "%s" ay ginagamit na bilang pangalan ng direktoryo sa repositoryo na ito.
+editor.file_is_a_symlink = `Ang %s ay isang symbolink link. Hindi mae-edit ang mga symbolic link sa web editor`
+editor.directory_is_a_file = Ang pangalan ng direktoryo "%s" ay ginagamit na bilang pangalan ng file sa repositoryo na ito.
 
 [search]
 commit_kind = Maghanap ng mga commit...
@@ -1690,6 +1703,31 @@ repos.owner = May-ari
 repos.lfs_size = Laki ng LFS
 packages.package_manage_panel = Ipamahala ang mga package
 auths.attribute_mail = Attribute ng email
+config.server_config = Configuration ng server
+config.app_name = Pangalan ng instansya
+config.lfs_root_path = Root path ng LFS
+config.log_file_root_path = Path ng log
+config.ssh_root_path = Root path
+config.script_type = Uri ng script
+config.reverse_auth_user = Authentication user ng reverse proxy
+config.ssh_domain = Server domain ng SSH
+config.custom_conf = File path ng configuration
+config.app_url = Base URL
+config.offline_mode = Lokal na mode
+config.ssh_port = Port
+config.custom_file_root_path = Pasadyang root path ng file
+config.domain = Domain ng server
+config.disable_router_log = I-disable ang router log
+config.run_user = User na tatakbo bilang
+config.run_mode = Mode ng pagtakbo
+config.app_data_path = Path ng data ng app
+config.repo_root_path = Root path ng repositoryo
+config.ssh_config = Configuration ng SSH
+config.ssh_enabled = Naka-enable
+config.ssh_start_builtin_server = Gamitin ang built-in server
+config.ssh_listen_port = Listen port
+config.ssh_keygen_path = Path ng keygen ("ssh-keygen")
+config.ssh_key_test_path = Path ng key test
 
 [org]
 repo_updated = Binago
diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini
index 03100f722a..4ffccbca44 100644
--- a/options/locale/locale_fr-FR.ini
+++ b/options/locale/locale_fr-FR.ini
@@ -151,13 +151,14 @@ filter.not_fork = Non dupliqué
 filter.not_mirror = Non répliqué
 filter.is_template = Modèle
 filter.not_template = Pas un modèle
-filter.public = Public
+filter.public = Publique
 filter.private = Privé
 filter = Filtre
 filter.is_mirror = Répliqué
 toggle_menu = Menu va-et-vient
 more_items = Plus d'éléments
 invalid_data = Données invalides : %v
+copy_generic = Copier dans le presse-papiers
 
 [aria]
 navbar=Barre de navigation
@@ -633,6 +634,14 @@ admin_cannot_delete_self=Vous ne pouvez pas vous supprimer vous-même lorsque vo
 unsupported_login_type = Ce type de compte ne peut être supprimé.
 unset_password = L'utilisateur connecté n'a pas de mot de passe.
 required_prefix = Le texte entré doit commencer par "%s"
+AccessToken = Jeton d'accès
+FullName = Nom complet
+Description = Description
+Pronouns = Qualités
+Biography = Biographie
+Website = Site web
+Location = Emplacement
+To = Nom de la branche
 
 [user]
 change_avatar=Changer votre avatar…
@@ -1255,7 +1264,7 @@ video_not_supported_in_browser=Votre navigateur ne supporte pas la balise « vi
 audio_not_supported_in_browser=Votre navigateur ne supporte pas la balise « audio » HTML5.
 stored_lfs=Stocké avec Git LFS
 symbolic_link=Lien symbolique
-executable_file=Fichiers exécutables
+executable_file=Fichier exécutable
 vendored=Externe
 generated=Générée
 commit_graph=Graphe des révisions
@@ -1437,7 +1446,7 @@ issues.new.no_items=Pas d'élément
 issues.new.milestone=Jalon
 issues.new.no_milestone=Sans jalon
 issues.new.clear_milestone=Effacer le jalon
-issues.new.open_milestone=Ouvrir un jalon
+issues.new.open_milestone=Jalons ouverts
 issues.new.closed_milestone=Jalons fermés
 issues.new.assignees=Assignés
 issues.new.clear_assignees=Supprimer les affectations
@@ -2311,9 +2320,9 @@ settings.authorization_header_desc=Si présent, sera ajouté aux requêtes comme
 settings.active=Actif
 settings.active_helper=Les informations sur les événements déclenchés seront envoyées à cette url de Webhook.
 settings.add_hook_success=Nouveau Webhook ajouté.
-settings.update_webhook=Actualiser le déclencheur
+settings.update_webhook=Actualiser le déclencheur « webhook »
 settings.update_hook_success=Déclencheur Web actualisé.
-settings.delete_webhook=Retirer le webhook
+settings.delete_webhook=Retirer le déclencheur
 settings.recent_deliveries=Livraisons récentes
 settings.hook_type=Type de déclencheur
 settings.slack_token=Jeton
@@ -2361,13 +2370,13 @@ settings.protected_branch_can_push_no=Vous ne pouvez pas pousser
 settings.branch_protection=Paramètres de protection pour les branches du motif "<b>%s</b>"
 settings.protect_this_branch=Activer la protection de branche
 settings.protect_this_branch_desc=Empêche les suppressions et limite les poussées et fusions sur cette branche.
-settings.protect_disable_push=Désactiver la soumission
+settings.protect_disable_push=Désactiver la soumission (push)
 settings.protect_disable_push_desc=Aucune soumission ne sera possible sur cette branche.
-settings.protect_enable_push=Activer la soumission
+settings.protect_enable_push=Activer la soumission (push)
 settings.protect_enable_push_desc=Toute personne ayant un accès en écriture sera autorisée à soumettre sur cette branche (sans forcer).
 settings.protect_enable_merge=Activer la fusion
 settings.protect_enable_merge_desc=Toute personne ayant un accès en écriture sera autorisée à fusionner les demandes d'ajout dans cette branche.
-settings.protect_whitelist_committers=Liste blanche des soumissions
+settings.protect_whitelist_committers=Liste blanche des soumissions (push)
 settings.protect_whitelist_committers_desc=Seuls les utilisateurs ou les équipes autorisés pourront soumettre sur cette branche (sans forcer).
 settings.protect_whitelist_deploy_keys=Mettez les clés de déploiement sur liste blanche avec accès en écriture pour soumettre.
 settings.protect_whitelist_users=Utilisateurs sur liste blanche :
@@ -2378,7 +2387,7 @@ settings.protect_merge_whitelist_committers=Activer la liste blanche pour la fus
 settings.protect_merge_whitelist_committers_desc=N'autoriser que les utilisateurs et les équipes en liste blanche d'appliquer les demandes de fusion sur cette branche.
 settings.protect_merge_whitelist_users=Utilisateurs en liste blanche de fusion :
 settings.protect_merge_whitelist_teams=Équipes en liste blanche de fusion :
-settings.protect_check_status_contexts=Activer le contrôle qualité
+settings.protect_check_status_contexts=Activer le contrôle de status
 settings.protect_status_check_patterns=Motifs de vérification des statuts :
 settings.protect_status_check_patterns_desc=Entrez des motifs pour spécifier quelles vérifications doivent réussir avant que des branches puissent être fusionnées. Un motif par ligne. Un motif ne peut être vide.
 settings.protect_check_status_contexts_desc=Exiger le status « succès » avant de fusionner. Quand activée, une branche protégée ne peux accepter que des soumissions ou des fusions ayant le status « succès ». Lorsqu'il n'y a pas de contexte, la dernière révision fait foi.
@@ -2396,7 +2405,7 @@ settings.dismiss_stale_approvals=Révoquer automatiquement les approbations pér
 settings.dismiss_stale_approvals_desc=Lorsque des nouvelles révisions changent le contenu de la demande d’ajout, les approbations existantes sont révoquées.
 settings.ignore_stale_approvals=Ignorer les approbations obsolètes
 settings.ignore_stale_approvals_desc=Ignorer les approbations d’anciennes révisions (évaluations obsolètes) du décompte des approbations de la demande d’ajout. Non pertinent quand les évaluations obsolètes sont déjà révoquées.
-settings.require_signed_commits=Exiger des révisions signées
+settings.require_signed_commits=Exiger des révisions (commits) signées
 settings.require_signed_commits_desc=Rejeter les soumissions sur cette branche lorsqu'ils ne sont pas signés ou vérifiables.
 settings.protect_branch_name_pattern=Motif de nom de branche protégé
 settings.protect_branch_name_pattern_desc=Motifs de nom de branche protégé. Consultez la <a href="github.com/gobwas/glob">documentation</a> pour la syntaxe du motif. Exemples : <code>main</code>, <code>release/**</code>
@@ -2404,7 +2413,7 @@ settings.protect_patterns=Motifs
 settings.protect_protected_file_patterns=Liste des fichiers et motifs protégés (séparés par un point virgule ";") :
 settings.protect_protected_file_patterns_desc=Les fichiers protégés ne peuvent être modifiés, même si l'utilisateur a le droit d'ajouter, éditer ou supprimer des fichiers dans cette branche. Plusieurs motifs peuvent être séparés par un point-virgule (;). Voir la documentation de <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> pour la syntaxe des motifs.  Exemples: <code>.forgejo/workflows/test.yml</code>, <code>/docs/**/*.txt</code>. ; », qui ne pourront pas être modifiés même si les utilisateurs disposent des droits sur la branche. Voir la <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>syntaxe glob</a>. Exemples : <code>.drone.yml ; /docs/**/*.txt</code>.
 settings.protect_unprotected_file_patterns=Liste des fichiers et motifs exclus (séparés par un point virgule ";") :
-settings.protect_unprotected_file_patterns_desc=Les fichiers non-protégés qui peuvent être modifiés  si l'utilisateur a le droit d'écriture, prenant le pas sur les restrictions de push. Plusieurs motifs peuvent être séparés par un point-virgule (;). Voir la documentation de <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> pour la syntaxe des motifs.  Exemples: <code>.forgejo/workflows/test.yml</code>, <code>/docs/**/*.txt</code>. ; », qui pourront être modifiés malgré la protection de branche, par les utilisateurs autorisés. Voir la <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>syntaxe Glob</a>. Exemples : <code>.drone.yml ; /docs/**/*.txt</code>.
+settings.protect_unprotected_file_patterns_desc=Les fichiers non-protégés qui peuvent être modifiés si l'utilisateur a le droit d'écriture, prenant le pas sur les restrictions de push. Plusieurs motifs peuvent être séparés par un point-virgule (;). Voir la documentation de <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> pour la syntaxe des motifs.  Exemples: <code>.forgejo/workflows/test.yml</code>, <code>/docs/**/*.txt</code>. ; », qui pourront être modifiés malgré la protection de branche, par les utilisateurs autorisés. Voir la <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>syntaxe Glob</a>. Exemples : <code>.drone.yml ; /docs/**/*.txt</code>.
 settings.add_protected_branch=Activer la protection
 settings.delete_protected_branch=Désactiver la protection
 settings.update_protect_branch_success=La règle de protection de branche "%s" a été mise à jour.
@@ -2437,7 +2446,7 @@ settings.tags.protection.allowed.noone=Personne
 settings.tags.protection.create=Ajouter une règle
 settings.tags.protection.none=Il n'y a pas d'étiquettes protégées.
 settings.tags.protection.pattern.description=Vous pouvez utiliser au choix un nom unique, un motif de glob ou une expression régulière qui correspondra à plusieurs étiquettes. Pour plus d’informations, consultez le <a target="_blank" rel="noopener" href="https://forgejo.org/docs/latest/user/protection/#protected-tags">guide sur les étiquettes protégées</a>.
-settings.bot_token=Jeton de bot
+settings.bot_token=Jeton (token) de bot
 settings.chat_id=ID de conversation
 settings.thread_id=ID du fil
 settings.matrix.homeserver_url=URL du serveur d'accueil
@@ -2725,19 +2734,23 @@ issues.num_participants_one = %d participant
 issues.archived_label_description = (Archivé) %s
 settings.add_webhook.invalid_path = L'emplacement ne peut pas contenir ni ".", ni "..", ni être vide, et ne peut pas commencer ou se terminer par un slash.
 settings.sourcehut_builds.secrets_helper = Permettre au job d'accéder aux secrets de build (nécessite la permission SECRETS:RO)
-size_format = %[1]s : %[2]s ; %[3]s : %[4]s
+size_format = %[1]s : %[2]s, %[3]s : %[4]s ; %[3]s : %[4]s
 settings.sourcehut_builds.visibility = Visibilité du job
 settings.sourcehut_builds.secrets = Secrets
-settings.sourcehut_builds.manifest_path = Construire le chemin du manifeste
+settings.sourcehut_builds.manifest_path = Chemin du manifest de build
 settings.sourcehut_builds.graphql_url = URL GraphQL (e.g. https://builds.sr.ht/query)
 release.download_count_one = %s téléchargement
 release.download_count_few = %s téléchargements
 release.system_generated = Cet attachement a été généré automatiquement.
-settings.enforce_on_admins_desc = Les administrateurs du dépôt ne peuvent pas contourner cette règle.
-settings.web_hook_name_sourcehut_builds = SourceHut Builds
-settings.enforce_on_admins = Contraindre les administrateurs du dépôt par cette règle
+settings.enforce_on_admins_desc = Les administrateurs du dépôt ne peuvent pas passer outre cette règle
+settings.web_hook_name_sourcehut_builds = Builds SourceHut
+settings.enforce_on_admins = Appliquer cette règles aux administrateurs du dépôt
 settings.rename_branch_failed_protected = Impossible de renommer la branche %s car il s'agit d'une branche protégée.
 settings.event_pull_request_enforcement = Amélioration
+settings.graphql_url = URL GraphQL
+settings.matrix.room_id_helper = L'identifiant du salon peut être obtenu dans le client web Element. Par exemple : %s.
+settings.sourcehut_builds.access_token_helper = Un jeton d'accès ayant des permissions JOBS:RW. Génère un <a target="_blank" rel="noopener noreferrer" href="%s">jeton builds.sr.ht</a> ou un <a target="_blank" rel="noopener noreferrer" href="%s">jeton builds.sr.ht token ayant accès aux secrets</a> sur meta.sr.ht.
+settings.matrix.access_token_helper = Il est recommandé de créer un compte Matrix dédié pour cela. Le jeton d'accès peut être obtenu depuis le client web Element (dans un onglet privé/incognito). Il faut ensuite fermer l'onglet privé/icognito (se déconnecter invaliderait le jeton).
 
 [graphs]
 component_loading=Chargement de %s…
@@ -2951,7 +2964,7 @@ dashboard.heap_memory_released=Mémoire tas (Heap) libérée
 dashboard.heap_objects=Objets tas (Heap)
 dashboard.bootstrap_stack_usage=Utilisation pile bootstrap
 dashboard.stack_memory_obtained=Mémoire pile obtenue
-dashboard.mspan_structures_usage=Utilisation des Structures MSpan
+dashboard.mspan_structures_usage=Utilisation des structures MSpan
 dashboard.mspan_structures_obtained=Structures MSpan obtenues
 dashboard.mcache_structures_usage=Utilisation des structures MCache
 dashboard.mcache_structures_obtained=Structures MCache obtenues
@@ -3026,9 +3039,9 @@ users.list_status_filter.reset=Réinitialiser
 users.list_status_filter.is_active=Actif
 users.list_status_filter.not_active=Inactif
 users.list_status_filter.is_admin=Administrateur
-users.list_status_filter.not_admin=Non Administrateur
+users.list_status_filter.not_admin=Non administrateur
 users.list_status_filter.is_restricted=Restreint
-users.list_status_filter.not_restricted=Non restraint
+users.list_status_filter.not_restricted=Non restreint
 users.list_status_filter.is_prohibit_login=Interdit de connexion
 users.list_status_filter.not_prohibit_login=Autorisé à se connecter
 users.list_status_filter.is_2fa_enabled=2FA Activé
@@ -3112,7 +3125,7 @@ auths.attribute_username=Attribut nom d'utilisateur
 auths.attribute_username_placeholder=Laisser vide afin d'utiliser le nom d'utilisateur spécifié dans Forgejo.
 auths.attribute_name=Attribut prénom
 auths.attribute_surname=Attribut nom de famille
-auths.attribute_mail=Attribut e-mail
+auths.attribute_mail=Attribut courriel
 auths.attribute_ssh_public_key=Attribut clef SSH publique
 auths.attribute_avatar=Attribut de l'avatar
 auths.attributes_in_bind=Aller chercher les attributs dans le contexte de liaison DN
@@ -3227,7 +3240,7 @@ config.repo_root_path=Emplacement des Dépôts
 config.lfs_root_path=Répertoire racine LFS
 config.log_file_root_path=Chemin des fichiers logs
 config.script_type=Type de script
-config.reverse_auth_user=Annuler l'authentification de l'utilisateur
+config.reverse_auth_user=Annuler l'authentification par proxy de l'utilisateur
 
 config.ssh_config=Configuration SSH
 config.ssh_enabled=Activé
@@ -3297,10 +3310,10 @@ config.mailer_sendmail_args=Arguments supplémentaires pour Sendmail
 config.mailer_sendmail_timeout=Délai d’attente de Sendmail
 config.mailer_use_dummy=Factice
 config.test_email_placeholder=E-mail (ex: test@example.com)
-config.send_test_mail=Envoyer un e-mail de test
+config.send_test_mail=Envoyer un courriel de test
 config.send_test_mail_submit=Envoyer
-config.test_mail_failed=Impossible d'envoyer un email de test à "%s" : %v
-config.test_mail_sent=Un e-mail de test a été envoyé à "%s".
+config.test_mail_failed=Impossible d'envoyer un courriel de test à "%s" : %v
+config.test_mail_sent=Un courriel de test a été envoyé à "%s".
 
 config.oauth_config=Configuration OAuth
 config.oauth_enabled=Activé
@@ -3308,7 +3321,7 @@ config.oauth_enabled=Activé
 config.cache_config=Configuration du cache
 config.cache_adapter=Adaptateur du cache
 config.cache_interval=Intervales du cache
-config.cache_conn=Liaison du Cache
+config.cache_conn=Connexion du cache
 config.cache_item_ttl=Durée de vie des éléments dans le cache
 
 config.session_config=Configuration de session
@@ -3374,7 +3387,7 @@ monitor.queue.type=Type
 monitor.queue.exemplar=Type d'exemple
 monitor.queue.numberworkers=Nombre de processus
 monitor.queue.activeworkers=Processus actifs
-monitor.queue.maxnumberworkers=Nombre maximale de processus
+monitor.queue.maxnumberworkers=Nombre maximal de processus
 monitor.queue.numberinqueue=Position dans la queue
 monitor.queue.review_add=Examiner / ajouter des processus
 monitor.queue.settings.title=Paramètres du réservoir
@@ -3390,7 +3403,7 @@ monitor.queue.settings.remove_all_items_done=Tous les éléments de la file d'at
 notices.system_notice_list=Notifications systèmes
 notices.view_detail_header=Voir les détails de la notification
 notices.operations=Opérations
-notices.select_all=Tout Sélectionner
+notices.select_all=Tout sélectionner
 notices.deselect_all=Tout désélectionner
 notices.inverse_selection=Inverser la sélection
 notices.delete_selected=Supprimer les éléments sélectionnés
@@ -3412,11 +3425,13 @@ self_check.database_fix_mysql = Les utilisateurs de MySQL/MariaDB peuvent utilis
 
 self_check.no_problem_found=Aucun problème trouvé pour l’instant.
 self_check.database_collation_mismatch=Exige que la base de données utilise la collation %s.
-self_check.database_collation_case_insensitive=La base de données utilise la collation %s, insensible à la casse. Bien que Gitea soit compatible, il peut y avoir quelques rares cas qui ne fonctionnent pas comme prévu.
+self_check.database_collation_case_insensitive=La base de données utilise la collation %s, insensible à la casse. Bien que Forgejo soit compatible, il peut y avoir quelques rares cas qui ne fonctionnent pas comme prévu.
 self_check.database_inconsistent_collation_columns=La base de données utilise la collation %s, mais ces colonnes utilisent des collations différentes. Cela peut causer des problèmes imprévus.
-self_check.database_fix_mysql=Pour les utilisateurs de MySQL ou MariaDB, vous pouvez utiliser la commande « gitea doctor convert » dans un terminal ou exécuter une requête du type « ALTER … COLLATE ... » pour résoudre les problèmes de collation.
+self_check.database_fix_mysql=Pour les utilisateurs de MySQL ou MariaDB, vous pouvez utiliser la commande « forgejo doctor convert » dans un terminal ou exécuter une requête du type « ALTER … COLLATE ... » pour résoudre les problèmes de collation.
 config_settings = Paramètres
 config_summary = Résumé
+auths.tips.gmail_settings = Paramètres Gmail :
+auths.tip.gitlab_new = Enregistrer une nouvelle application sur https://gitlab.com/-/profile/applications
 
 [action]
 create_repo=a créé le dépôt <a href="%s">%s</a>
@@ -3530,7 +3545,7 @@ details=Détails
 details.author=Auteur
 details.project_site=Site du projet
 details.repository_site=Site du dépôt
-details.documentation_site=Site de documentation
+details.documentation_site=Site de la documentation
 details.license=Licence
 assets=Ressources
 versions=Versions
@@ -3661,6 +3676,7 @@ owner.settings.chef.keypair.description=Une paire de clés est nécessaire pour
 rpm.repository = Information sur le dépôt
 rpm.repository.architectures = Architectures
 rpm.repository.multiple_groups = Ce paquet est disponible dans plusieurs groupes.
+owner.settings.cargo.rebuild.no_index = Incapable de reconstruire, index non initialisé.
 
 [secrets]
 secrets=Secrets
@@ -3769,7 +3785,7 @@ variables.creation.success=La variable « %s » a été ajoutée.
 variables.update.failed=Impossible d’éditer la variable.
 variables.update.success=La variable a bien été modifiée.
 runs.no_workflows.quick_start = Vous ne savez pas comment commencer avec Forgejo Action ? Consultez <a target="_blank" rel="noopener noreferrer" href="%s">le guide de démarrage rapide</a>.
-runs.no_workflows.documentation = Pour plus d’informations sur les Actions Forgejo, voir <a target="_blank" rel="noopener noreferrer" href="%s">la documentation</a>.
+runs.no_workflows.documentation = Pour plus d’informations sur Forgejo Actions, voir <a target="_blank" rel="noopener noreferrer" href="%s">la documentation</a>.
 variables.id_not_exist = La variable numéro %d n’existe pas.
 runs.workflow = Workflow
 
@@ -3804,14 +3820,14 @@ recent_commits.what = commits récents
 search = Rechercher...
 type_tooltip = Type de recherche
 fuzzy = Approximatif
-code_search_by_git_grep = Les résultats de recherche dans le code sont fournis par "git grep". Les résultats pourraient être plus pertinents si l'administrateur du site active les indexeurs de dépôt.
+code_search_by_git_grep = Les résultats de recherche dans le code sont fournis par "git grep". Les résultats pourraient être plus pertinents si l'administrateur du site active les indexeurs de code source.
 runner_kind = Chercher les runners...
 no_results = Aucun résultat n'a été trouvé.
 keyword_search_unavailable = La recherche par mot-clé n'est pas disponible actuellement. Veuillez contacter l'administrateur du site.
 fuzzy_tooltip = Inclure les résultats proches des termes recherchés
 match = Correspondance
 match_tooltip = Uniquement inclure les résultats correspondant exactement aux termes recherchés
-repo_kind = Chercher dans le dépôt...
+repo_kind = Chercher dans les dépôt...
 user_kind = Chercher les utilisateurs...
 org_kind = Chercher les organisations...
 team_kind = Chercher les équipes...
@@ -3821,3 +3837,12 @@ package_kind = Chercher les paquets...
 project_kind = Chercher les projets...
 branch_kind = Chercher les branches...
 commit_kind = Chercher les commits...
+
+
+[munits.data]
+b = o
+
+[markup]
+filepreview.line = Ligne %[1]d dans %[2]s
+filepreview.lines = Lignes %[1]d jusqu'à %[2]d dans %[3]s
+filepreview.truncated = L'aperçu a été tronqué
\ No newline at end of file
diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini
index e2ac989ae0..d6137f39fe 100644
--- a/options/locale/locale_ja-JP.ini
+++ b/options/locale/locale_ja-JP.ini
@@ -143,13 +143,13 @@ confirm_delete_selected=選択したすべてのアイテムを削除してよ
 name=名称
 value=値
 filter.is_archived = アーカイブ
-filter.not_archived = 非アーカイブ
+filter.not_archived = アーカイブされていない
 filter.is_fork = フォーク
 filter.is_mirror = ミラー
-filter.not_mirror = 非ミラー
+filter.not_mirror = ミラーされていない
 filter.is_template = テンプレート
 filter = フィルター
-filter.not_fork = 非フォーク
+filter.not_fork = フォークされていない
 filter.clear = フィルタをクリアする
 filter.public = 公開
 filter.private = 非公開
@@ -157,6 +157,7 @@ toggle_menu = トグルメニュー
 filter.not_template = テンプレートではない
 invalid_data = 無効なデータ: %v
 more_items = さらに表示
+copy_generic = クリップボード
 
 [aria]
 navbar=ナビゲーションバー
@@ -169,6 +170,8 @@ number_of_contributions_in_the_last_12_months=過去 12 か月間で %s 件の
 contributions_zero=貢献なし
 less=少
 more=多
+contributions_one = 貢献
+contributions_few = 貢献
 
 [editor]
 buttons.heading.tooltip=見出し追加
@@ -240,7 +243,7 @@ err_admin_name_pattern_not_allowed=管理者のユーザー名が不正です。
 err_admin_name_is_invalid=管理者のユーザー名が不正です
 
 general_title=基本設定
-app_name=インスタンスの名前
+app_name=インスタンス名
 app_name_helper=企業名をここに入れることができます。
 repo_path=リポジトリのルートパス
 repo_path_helper=リモートGitリポジトリはこのディレクトリに保存されます。
@@ -332,7 +335,7 @@ switch_dashboard_context=ダッシュボードのコンテキスト切替
 my_repos=リポジトリ
 show_more_repos=リポジトリをさらに表示…
 collaborative_repos=共同リポジトリ
-my_orgs=自分の組織
+my_orgs=組織
 my_mirrors=自分のミラー
 view_home=%s を表示
 search_repos=リポジトリを探す…
@@ -373,6 +376,10 @@ code_search_results=`"%s" の検索結果`
 code_last_indexed_at=最終取得 %s
 relevant_repositories_tooltip=フォークリポジトリや、トピック、アイコン、説明のいずれも無いリポジトリは表示されません。
 relevant_repositories=妥当と思われるリポジトリのみを表示しています。 <a href="%s">フィルタリングしない結果を表示</a>。
+stars_few = %d のスター
+stars_one = %d のスター
+forks_one = %d のフォーク
+forks_few = %d のフォーク
 
 [auth]
 create_new_account=アカウントを登録
@@ -448,11 +455,13 @@ change_unconfirmed_email = 登録時に間違ったメール アドレスを入
 change_unconfirmed_email_error = メール アドレスを変更できません: %v
 change_unconfirmed_email_summary = アクティベーションメールの送信先メールアドレスを変更します。
 last_admin=最後の管理者は削除できません。少なくとも一人の管理者が必要です。
+tab_signin = サインイン
+tab_signup = サインアップ
 
 [mail]
 view_it_on=%s で見る
 reply=またはこのメールに直接返信してください
-link_not_working_do_paste=開かないですか? コピーしてブラウザーに貼り付けてみてください。
+link_not_working_do_paste=リンクが開きませんか? コピーしてブラウザーのURLバーに貼り付けてみてください。
 hi_user_x=こんにちは、<b>%s</b> さん。
 
 activate_account=あなたのアカウントをアクティベートしてください。
@@ -468,7 +477,7 @@ register_notify=Forgejoへようこそ
 register_notify.title=%[1]s さん、%[2]s にようこそ
 register_notify.text_1=これは %s への登録確認メールです!
 register_notify.text_2=あなたはユーザー名 %s でログインできるようになりました。
-register_notify.text_3=このアカウントがあなたに作成されたものであれば、最初に<a href="%s">パスワードを設定</a>してください。
+register_notify.text_3=他の人があなたのアカウントを作成した場合、最初に<a href="%s">パスワードを設定</a>してください。
 
 reset_password=アカウントを回復
 reset_password.title=%s さん、あなたのアカウントの復元がリクエストされました
@@ -502,8 +511,8 @@ release.downloads=ダウンロード:
 release.download.zip=ソースコード (ZIP)
 release.download.targz=ソースコード (TAR.GZ)
 
-repo.transfer.subject_to=%s が "%s" を %s に移転しようとしています
-repo.transfer.subject_to_you=%s が "%s" をあなたに移転しようとしています
+repo.transfer.subject_to=%s が "%s" を %s に移譲しようとしています
+repo.transfer.subject_to_you=%s が "%s" をあなたに移譲しようとしています
 repo.transfer.to_you=あなた
 repo.transfer.body=承認または拒否するには %s を開きます。 もしくは単に無視してもかまいません。
 
@@ -621,6 +630,15 @@ username_error_no_dots = `英数字 (「0-9」、「a-z」、「A-Z」)、ダッ
 admin_cannot_delete_self=あなたが管理者である場合、自分自身を削除することはできません。最初に管理者権限を削除してください。
 unset_password = ログインしたユーザーにパスワードが設定されていません。
 unsupported_login_type = このログインタイプでは、アカウントの削除はサポートされていません。
+required_prefix = "%s"から始まる必要があります
+AccessToken = アクセストークン
+FullName = 氏名
+Description = 説明
+Pronouns = 代名詞
+Biography = 経歴
+Website = ウェブサイト
+Location = 場所
+To = ブランチ名
 
 [user]
 change_avatar=アバターを変更…
@@ -628,8 +646,8 @@ joined_on=%sに登録
 repositories=リポジトリ
 activity=公開アクティビティ
 followers_few=%d フォロワー
-starred=スター付きリポジトリ
-watched=ウォッチ中リポジトリ
+starred=スターを付けたリポジトリ
+watched=ウォッチ中のリポジトリ
 code=コード
 projects=プロジェクト
 overview=概要
@@ -654,6 +672,8 @@ block_user = ユーザーをブロック
 unblock = ブロックを解除
 block = ブロック
 block_user.detail = このユーザーをブロックした場合、下記の事などが起こります。例えば:
+followers_one = %d のフォロワー
+following_one = %d のフォロワー
 
 [settings]
 profile=プロフィール
@@ -682,7 +702,7 @@ password_username_disabled=非ローカルユーザーのユーザー名は変
 full_name=フルネーム
 website=Webサイト
 location=場所
-update_theme=テーマを更新
+update_theme=テーマを変更
 update_profile=プロフィール更新
 update_language=言語を更新
 update_language_not_found=言語 "%s" は利用できません。
@@ -972,6 +992,14 @@ user_unblock_success = このユーザーをアンブロックするのに成功
 blocked_since = %s からブロック中
 user_block_success = このユーザーをブロックするのに成功しました。
 change_password = パスワードを変更
+pronouns = 代名詞
+pronouns_custom = カスタム
+pronouns_unspecified = 未指定
+update_hints = アップデートのヒント
+additional_repo_units_hint_description = 利用可能なすべての機能が有効になっていないリポジトリに対して、「機能を追加...」ボタンを表示します。
+update_hints_success = ヒントが更新されました。
+hints = ヒント
+additional_repo_units_hint = リポジトリでより多くの機能を有効にすることを推奨する
 
 [repo]
 new_repo_helper=リポジトリには、プロジェクトのすべてのファイルとリビジョン履歴が入ります。 すでにほかの場所でホストしていますか? <a href="%s">リポジトリを移行</a> もどうぞ。
@@ -2660,8 +2688,50 @@ settings.units.add_more = さらに...
 settings.wiki_globally_editable = 誰にでもWikiの編集を許す
 settings.confirmation_string = 確認
 settings.wiki_rename_branch_main_notices_1 = この操作は 取り消し<strong>できません</strong> 。
+stars = スター
+n_tag_few = %s のタグ
+settings.graphql_url = GraphQL URL
+n_branch_one = %s のブランチ
+settings.units.units = リポジトリ機能
+settings.wiki_rename_branch_main_notices_2 = これにより、%s のリポジトリ wiki の内部ブランチの名前が永久に変更されます。既存のチェックアウトを更新する必要があります。
+settings.sourcehut_builds.access_token_helper = JOBS:RW 権限を持つアクセス トークン。meta.sr.ht で <a target="_blank" rel="noopener noreferrer" href="%s">builds.sr.ht トークン</a> または <a target="_blank" rel="noopener noreferrer" href="%s">シークレット アクセスを持つ builds.sr.ht トークン</a> を生成します。
+settings.enforce_on_admins = リポジトリ管理者にこのルールを適用する
+activity.navbar.code_frequency = コードの更新頻度
+settings.wiki_branch_rename_failure = リポジトリ wiki のブランチ名を正規化できませんでした。
+settings.ignore_stale_approvals = 古い承認を無視する
+open_with_editor = %s で開く
+release.download_count_one = %s のダウンロード
+release.download_count_few = %s のダウンロード
+release.system_generated = この添付ファイルは自動的に生成されます。
+settings.archive.mirrors_unavailable = リポジトリがアーカイブされている場合、ミラーは利用できません。
+settings.rename_branch_failed_protected = 保護されたブランチのため、ブランチ %s の名前を変更できません。
+n_tag_one = %s のタグ
+n_branch_few = %s のブランチ
+n_commit_few = %s のコミット
+settings.confirm_wiki_branch_rename = Wikiブランチの名前を変更する
+settings.add_collaborator_blocked_our = リポジトリ所有者が共同作業者をブロックしているため、共同作業者を追加できません。
+settings.sourcehut_builds.visibility = ジョブの可視性
+settings.sourcehut_builds.secrets = シークレット
+settings.ignore_stale_approvals_desc = 古いコミット (古いレビュー) に対して行われた承認を、PR の承認数にカウントしないでください。古いレビューがすでに却下されている場合は関係ありません。
+settings.enforce_on_admins_desc = リポジトリ管理者はこのルールを回避できません。
+release.hide_archive_links = 自動生成されたアーカイブを非表示にする
+n_commit_one = %s のコミット
+settings.wiki_branch_rename_success = リポジトリ wiki のブランチ名が正常に正規化されました。
+settings.add_collaborator_blocked_them = リポジトリ所有者がブロックされているため、共同作業者を追加できません。
+settings.add_webhook.invalid_path = パスには「.」や「..」や空の文字列を含めることはできません。また、スラッシュで開始または終了することはできません。
+settings.sourcehut_builds.manifest_path = Build manifestのパス
+settings.sourcehut_builds.secrets_helper = ジョブにビルドシークレットへのアクセス権を付与します (SECRETS:RO 権限が必要です)
+release.hide_archive_links_helper = このリリース用に自動的に生成されたソース コード アーカイブを非表示にします。たとえば、独自のソース コードをアップロードする場合などです。
+error.broken_git_hook = このリポジトリの Git フックが壊れているようです。<a target="_blank" rel="noreferrer" href="%s">ドキュメント</a>に従って修正し、コミットをいくつかプッシュしてステータスを更新してください。
 
 [graphs]
+component_loading = %s の読み込み中...
+component_loading_failed = %s を読み込めませんでした
+component_loading_info = 少し時間がかかるかもしれません…
+component_failed_to_load = 予期しないエラーが発生しました。
+code_frequency.what = コード頻度
+contributors.what = 貢献者
+recent_commits.what = 最近のコミット
 
 [org]
 org_name_holder=組織名
@@ -2786,6 +2856,7 @@ teams.all_repositories_admin_permission_desc=このチームは<strong>すべて
 teams.invite.title=あなたは組織 <strong>%[2]s</strong> 内のチーム <strong>%[1]s</strong> への参加に招待されました。
 teams.invite.by=%s からの招待
 teams.invite.description=下のボタンをクリックしてチームに参加してください。
+follow_blocked_user = この組織によってブロックされているため、この組織をフォローすることはできません。
 
 [admin]
 dashboard=ダッシュボード
@@ -3314,6 +3385,13 @@ notices.type_2=タスク
 notices.desc=説明
 notices.op=操作
 notices.delete_success=システム通知を削除しました。
+config.open_with_editor_app_help = クローン メニューの「~で開く」エディター。空のままにすると、デフォルトが使用されます。展開してデフォルトを表示します。
+dashboard.sync_repo_tags = Gitデータからデータベースにタグを同期する
+dashboard.sync_tag.started = タグの同期が開始されました
+self_check = セルフチェック
+auths.tips.gmail_settings = Gmail設定:
+self_check.no_problem_found = まだ問題は見つかりません。
+auths.tip.gitlab_new = https://gitlab.com/-/profile/applications で新しいアプリケーションを登録します
 
 
 [action]
@@ -3555,6 +3633,8 @@ owner.settings.cleanuprules.success.delete=クリーンアップルールが削
 owner.settings.chef.title=Chefレジストリ
 owner.settings.chef.keypair=キーペアを生成
 owner.settings.chef.keypair.description=Chefレジストリの認証にはキーペアが必要です。 すでにキーペアを生成していた場合、新しいキーペアを生成すると古いキーペアは破棄されます。
+rpm.repository.multiple_groups = このパッケージは複数のグループで利用できます。
+owner.settings.cargo.rebuild.no_index = 再構築できません、インデックスが初期化されていません。
 
 [secrets]
 secrets=シークレット
@@ -3665,6 +3745,7 @@ runs.no_workflows.quick_start = Forgejo Action の始め方がわからない?
 runs.no_workflows.documentation = Forgejo Action の詳細については、<a target="_blank" rel="noopener noreferrer" href="%s">ドキュメント</a>を参照してください。
 variables.id_not_exist = idが%dの変数は存在しません。
 runs.workflow = ワークフロー
+runs.no_job_without_needs = ワークフローには、依存関係のないジョブが少なくとも 1 つ含まれている必要があります。
 
 [projects]
 type-1.display_name=個人プロジェクト
@@ -3685,3 +3766,32 @@ submodule=サブモジュール
 [search]
 search = 検索...
 type_tooltip = 検索タイプ
+org_kind = 組織の検索...
+code_kind = コードの検索...
+fuzzy = あいまい
+repo_kind = リポジトリの検索...
+code_search_unavailable = コード検索は現在利用できません。サイト管理者にお問い合わせください。
+branch_kind = ブランチの検索...
+commit_kind = コミットの検索...
+user_kind = ユーザーの検索...
+team_kind = チームの検索...
+code_search_by_git_grep = 現在のコード検索結果は「git grep」によって提供されます。サイト管理者がコード インデクサーを有効にすると、より良い結果が得られる可能性があります。
+package_kind = パッケージの検索...
+project_kind = プロジェクトの検索...
+keyword_search_unavailable = キーワードによる検索は現在ご利用いただけません。サイト管理者にお問い合わせください。
+runner_kind = ランナーの検索...
+no_results = 一致する結果が見つかりませんでした。
+
+
+[munits.data]
+pib = PiB
+tib = TiB
+eib = EiB
+kib = KiB
+mib = MiB
+gib = GiB
+b = B
+
+[markup]
+filepreview.lines = %[3]s の %[1]d 行目から %[2]d 行目
+filepreview.line = %[2]s の %[1]d 行目
\ No newline at end of file
diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini
index cd31df2dcb..4d0218bd24 100644
--- a/options/locale/locale_ko-KR.ini
+++ b/options/locale/locale_ko-KR.ini
@@ -84,6 +84,9 @@ concept_user_organization=조직
 
 
 name=이름
+active_stopwatch = 진행중인 타임 트래커
+sign_in_with_provider = %으로 로그인
+logo = 로고
 
 [aria]
 
diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini
index ba9f6765a8..38109eb2c3 100644
--- a/options/locale/locale_pl-PL.ini
+++ b/options/locale/locale_pl-PL.ini
@@ -117,10 +117,56 @@ logo = Logo
 sign_in_with_provider = Zaloguj się za pomocą %s
 enable_javascript = Ta strona wymaga JavaScript.
 value = Wartość
+remove_label_str = Usuń element "%s"
+locked = Zablokowany
+copy_type_unsupported = Ten plik nie może być skopiowany
+pin = Przypnij
+new_project_column = Nowa kolumna
+rerun = Uruchom ponownie
+rerun_all = Uruchom ponownie wszystkie zadania
+retry = Ponów
+view = Widok
+go_back = Wróć
+filter = Filtr
+confirm_delete_artifact = Jesteś penwy że chcesz usunąć artefakt "%s"?
+concept_system_global = Globalne
+concept_user_individual = Indywidualny
+filter.clear = Wyczyść filtry
+copy_hash = Skopiuj hash
+copy_content = Skopiuj zawartość
+invalid_data = Nieprawidłowe dane: %v
+unknown = Nieznane
+rss_feed = Kanał RSS
+unpin = Odepnij
+artifacts = Artefakty
+show_log_seconds = Pokaż sekundy
+show_full_screen = Pokaż pełny ekran
+download_logs = Pobierz logi
+confirm_delete_selected = Potwierdzić usunięcie wszystkich wybranych elementów?
+filter.is_template = Szablon
+filter.public = Publiczne
+filter.private = Prywatne
+copy_generic = Skopiuj do schowka
+toggle_menu = Przełącz menu
+tracked_time_summary = Podsumowanie śledzonego czasu na podstawie filtrów listy problemów
+show_timestamps = Pokaż znaczniki czasu
+filter.not_archived = Nie zarchiwizowane
+filter.not_mirror = Nie lustrzane odbicie
+filter.not_template = Nie szablon
+filter.is_archived = Zarchiwizowane
+filter.is_mirror = Lustrzane odbicie
+more_items = Więcej elementów
 
 [aria]
+navbar = Pasek nawigacji
+footer = Stopka
+footer.software = O Oprogramoiwaniu
+footer.links = Linki
 
 [heatmap]
+contributions_format = {contributions} w dniu {month} {day}, {year}
+less = Mniej
+more = Więcej
 
 [editor]
 buttons.heading.tooltip = Dodaj nagłówek
@@ -135,8 +181,12 @@ buttons.list.task.tooltip = Dodaj listę zadań
 buttons.ref.tooltip = Dodaj odniesienie do zgłoszenia lub pull requestu
 buttons.mention.tooltip = Dodaj wzmiankę o użytkowniku lub zespole
 buttons.switch_to_legacy.tooltip = Zamiast tego użyj starego edytora
+buttons.disable_monospace_font = Wyłącz czcionkę monospace
+buttons.enable_monospace_font = Włącz czcionkę monospace
 
 [filter]
+string.asc = A - Z
+string.desc = Z - A
 
 [error]
 occurred=Wystąpił błąd
@@ -145,16 +195,18 @@ invalid_csrf=Błędne żądanie: nieprawidłowy token CSRF
 not_found=Nie można odnaleźć celu.
 network_error=Błąd sieci
 report_message = Jeśli podejrzewasz że jest to bug w Forgejo, przeszukaj zgłoszenia na <a href="https://codeberg.org/forgejo/forgejo/issues" target="_blank">Codeberg</a> lub otwórz nowe zgłoszenie w razie potrzeby.
+server_internal = Wewnętrzny błąd serwera
 
 [startpage]
 app_desc=Bezbolesna usługa Git na własnym serwerze
 install=Łatwa instalacja
 platform=Wieloplatformowość
-platform_desc=Forgejo ruszy gdziekolwiek <a target="_blank" rel="noopener noreferrer" href="http://golang.org/">Go</a> jest możliwe do skompilowania: Windows, macOS, Linux, ARM, itd. Wybierz swój ulubiony system!
+platform_desc=Forgejo ruszy gdziekolwiek <a target="_blank" rel="noopener noreferrer" href="https://go.dev/">Go</a> jest możliwe do skompilowania: Windows, macOS, Linux, ARM, itd. Wybierz swój ulubiony system!
 lightweight=Niskie wymagania
 lightweight_desc=Forgejo ma niskie minimalne wymagania i może działać na niedrogim Raspberry Pi. Oszczędzaj energię swojego komputera!
 license=Otwarte źródło
 license_desc=Pobierz na <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download">Forgejo</a>! Dołącz do nas dzięki <a target="_blank" rel="noopener noreferrer" href="https://codeberg.org/forgejo/forgejo">swojemu wkładowi</a>, aby uczynić ten projekt jeszcze lepszym. Nie wstydź się zostać współtwórcą!
+install_desc = Po prostu <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download/#installation-from-binary">uruchom plik binarny</a> dla swojej platformy, dostarcz ją za pomocą <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download/#container-image">Dockera</a>, lub weż wersję<a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download">zapakowaną</a>.
 
 [install]
 install=Instalacja
@@ -192,14 +244,14 @@ repo_path=Katalog repozytoriów
 repo_path_helper=Zdalne repozytoria Git zostaną zapisane w tym katalogu.
 lfs_path=Ścieżka główna Git LFS
 lfs_path_helper=W tym katalogu będą przechowywane pliki śledzone za pomocą Git LFS. Pozostaw puste, aby wyłączyć LFS.
-run_user=Uruchom jako nazwa użytkownika
+run_user=Uruchom jako użytkownik
 domain=Domena serwera
 domain_helper=Adres domeny lub hosta serwera.
 ssh_port=Port serwera SSH
-ssh_port_helper=Numer portu, na którym nasłuchuje Twój serwer SSH. Pozostaw puste, aby wyłączyć.
-http_port=Port nasłuchiwania HTTP Forgejo
-http_port_helper=Numer portu nasłuchiwania serwera Forgejo.
-app_url=Podstawowy adres URL Forgejo
+ssh_port_helper=Numer portu, który zostanie użyty dla serwera SSH. Pozostaw puste, aby wyłączyć.
+http_port=Port nasłuchiwania HTTP
+http_port_helper=Numer portu, który będzie używany przez serwer internetowy Forgejo.
+app_url=Podstawowy adres URL
 app_url_helper=Podstawowy adres dla klonowania adresów URL HTTP(S) oraz powiadomień e-mail.
 log_root_path=Ścieżka dla logów
 log_root_path_helper=Pliki logów będą zapisywane w tym katalogu.
@@ -212,7 +264,7 @@ smtp_from=Wyślij e-mail jako
 smtp_from_helper=Adres e-mail, z którego Forgejo będzie korzystać. Wpisz prosty adres e-mail, lub użyj formatu "Nazwa" <email@example.com>.
 mailer_user=Nazwa użytkownika SMTP
 mailer_password=Hasło SMTP
-register_confirm=Wymagają potwierdzenia e-mail przy rejestracji
+register_confirm=Wymagaj potwierdzenia e-mail przy rejestracji
 mail_notify=Włącz powiadomienia e-mail
 server_service_title=Ustawienia serwera i innych usług
 offline_mode=Włącz tryb lokalny
@@ -221,16 +273,16 @@ disable_gravatar=Wyłącz Gravatar
 disable_gravatar_popup=Wyłącz Gravatar i inne usługi zewnętrzne awatarów. Zostanie zastosowany domyślny awatar, chyba że użytkownik prześle swój własny.
 federated_avatar_lookup=Włącz zewnętrzne awatary
 federated_avatar_lookup_popup=Enable federated avatars lookup to use federated open source service based on libravatar.
-disable_registration=Wyłącz rejestrację dwuskładnikową
+disable_registration=Wyłącz samodzielną rejestrację
 disable_registration_popup=Wyłącz samodzielną rejestrację użytkowników. Tylko administratorzy będą w stanie tworzyć nowe konta.
 allow_only_external_registration_popup=Włącz rejestrację wyłącznie za pomocą zewnętrznych usług
-openid_signin=Włącz logowanie za pomocą OpenID
+openid_signin=Włącz logowanie za pomocą OpenID
 openid_signin_popup=Włącz logowanie użytkowników za pomocą OpenID.
-openid_signup=Włącz samodzielną rejestrację za pomocą OpenID
+openid_signup=Włącz samodzielną rejestrację za pomocą OpenID
 openid_signup_popup=Włącz samodzielną rejestrację opartą o OpenID.
 enable_captcha=Włącz CAPTCHA przy rejestracji
 enable_captcha_popup=Wymagaj walidacji CAPTCHA przy samodzielnej rejestracji użytkownika.
-require_sign_in_view=Wymagaj zalogowania w celu przeglądania stron
+require_sign_in_view=Wymagaj zalogowania się, aby wyświetlić zawartość instancji
 admin_setting_desc=Tworzenie konta administratora jest opcjonalne. Pierwszy zarejestrowany użytkownik automatycznie zostanie administratorem.
 admin_title=Ustawienia konta administratora
 admin_name=Nazwa użytkownika administratora
@@ -238,7 +290,7 @@ admin_password=Hasło
 confirm_password=Potwierdź hasło
 admin_email=Adres e-mail
 install_btn_confirm=Zainstaluj Forgejo
-test_git_failed=Nie udało się przetestować polecenia „git”: %v
+test_git_failed=Nie udało się przetestować polecenia "git": %v
 sqlite3_not_available=Twoje wydanie Forgejo nie obsługuje SQLite3. Pobierz oficjalne wydanie z %s (NIE wersję "gobuild").
 invalid_db_setting=Nieprawidłowe ustawienia bazy danych: %v
 invalid_repo_path=Ścieżka repozytorium nie jest poprawna: %v
@@ -258,15 +310,27 @@ default_enable_timetracking_popup=Domyślnie włącz śledzenie czasu dla nowych
 no_reply_address=Ukryta domena e-mail
 no_reply_address_helper=Nazwa domeny dla użytkowników z ukrytym adresem e-mail. Przykładowo, użytkownik "jan" będzie zalogowany na Git'cie jako "jan@noreply.example.org", jeśli domena ukrytego adresu e-mail jest ustawiona na "noreply.example.org".
 password_algorithm=Algorytm hashowania haseł
+invalid_db_table = Tabela bazy danych "%s" jest nieprawidłowa: %v
+allow_dots_in_usernames = Zezwolenie użytkownikom na używanie kropek w nazwach użytkowników. Nie ma to wpływu na istniejące konta.
+invalid_password_algorithm = Nieprawidłowy algorytm hashowania haseł
+smtp_from_invalid = Adres "Wyślij e-mail jako" jest nieprawidłowy
+env_config_keys_prompt = Następujące zmienne środowiskowe zostaną również zastosowane do pliku konfiguracyjnego:
+enable_update_checker_helper_forgejo = Będzie on okresowo sprawdzał dostępność nowych wersji Forgejo poprzez sprawdzanie rekordu TXT DNS pod adresem release.forgejo.org.
+config_location_hint = Te opcje konfiguracji zostaną zapisane w:
+password_algorithm_helper = Ustaw algorytm haszowania haseł. Algorytmy mają różne wymagania i siłę. Algorytm argon2 jest dość bezpieczny, ale zużywa dużo pamięci i może być nieodpowiedni dla małych systemów.
+enable_update_checker = Włącz sprawdzanie aktualizacji
+env_config_keys = Konfiguracja środowiska
+run_user_helper = Nazwa użytkownika systemu operacyjnego, pod którą działa Forgejo. Należy pamiętać, że ten użytkownik musi mieć dostęp do ścieżki głównej repozytorium.
+require_sign_in_view_popup = Ogranicz dostęp do strony jedynie do zalogowanych użytkowników. Odwiedzający zobaczą tylko strony logowania i rejestracji.
 
 [home]
-uname_holder=Nazwa użytkownika lub adres email
+uname_holder=Nazwa użytkownika lub adres e-mail
 password_holder=Hasło
 switch_dashboard_context=Przełącz kontekst pulpitu
 my_repos=Repozytoria
 show_more_repos=Pokaż więcej repozytoriów…
 collaborative_repos=Wspólne repozytoria
-my_orgs=Moje organizacje
+my_orgs=Organizacje
 my_mirrors=Moje kopie lustrzane
 view_home=Zobacz %s
 search_repos=Znajdź repozytorium…
@@ -299,6 +363,10 @@ user_no_results=Nie znaleziono pasującego użytkowników.
 org_no_results=Nie znaleziono pasujących organizacji.
 code_no_results=Nie znaleziono kodu źródłowego odpowiadającego Twojej frazie wyszukiwania.
 code_last_indexed_at=Ostatnio indeksowane %s
+go_to = Przejdź do
+relevant_repositories = Wyświetlane są tylko istotne repozytoria, <a href="%s">pokaż niefiltrowane wyniki</a>.
+stars_one = %d gwiazdka
+stars_few = %d gwiazdek
 
 [auth]
 create_new_account=Zarejestruj konto
@@ -316,7 +384,7 @@ allow_password_change=Użytkownik musi zmienić hasło (zalecane)
 reset_password_mail_sent_prompt=E-mail potwierdzający został wysłany na adres <b>%s</b>. Sprawdź swoją skrzynkę odbiorczą w przeciągu %s, aby ukończyć proces odzyskiwania konta.
 active_your_account=Aktywuj swoje konto
 account_activated=Konto zostało aktywowane
-prohibit_login=Logowanie zabronione
+prohibit_login=Logowanie jest zabronione
 resent_limit_prompt=Zażądano już wiadomości aktywacyjnej. Zaczekaj 3 minuty i spróbuj ponownie.
 has_unconfirmed_mail=Witaj, %s, masz niepotwierdzony adres e-mail (<b>%s</b>). Jeśli nie otrzymałeś wiadomości e-mail z potwierdzeniem lub potrzebujesz wysłać nową, kliknij na poniższy przycisk.
 resend_mail=Kliknij tutaj, aby wysłać e-mail aktywacyjny
@@ -357,6 +425,12 @@ authorize_title=Zezwolić "%s" na dostęp do Twojego konta?
 authorization_failed=Autoryzacja nie powiodła się
 sspi_auth_failed=Uwierzytelnianie SSPI nie powiodło się
 password_pwned_err=Nie udało się ukończyć żądania do HaveIBeenPwned
+remember_me.compromised = Token logowania nie jest już ważny, co może wskazywać na naruszenie bezpieczeństwa konta. Sprawdź swoje konto pod kątem podejrzanych działań.
+sign_up_successful = Konto zostało pomyślnie utworzone. Witamy!
+prohibit_login_desc = Twoje konto jest zablokowane, skontaktuj się z administratorem witryny.
+change_unconfirmed_email_summary = Zmień adres e-mail, na który zostanie wysłana wiadomość aktywacyjna.
+manual_activation_only = Skontaktuj się z administratorem witryny, aby dokończyć aktywację.
+change_unconfirmed_email = Jeśli podczas rejestracji podałeś nieprawidłowy adres e-mail, możesz go zmienić poniżej, a potwierdzenie zostanie wysłane na nowy adres.
 
 [mail]
 view_it_on=Zobacz na %s
@@ -2546,3 +2620,26 @@ runs.commit=Commit
 symbolic_link=Dowiązanie symboliczne
 executable_file = Plik wykonywalny
 
+
+
+[search]
+search = Wyszukaj...
+type_tooltip = Typ wyszukiwania
+fuzzy = Fuzzy
+package_kind = Wyszukaj paczki...
+fuzzy_tooltip = Uwzględnij wyniki, które również pasują do wyszukiwanego hasła
+match = Dopasuj
+match_tooltip = Uwzględniaj tylko wyniki pasujące do wyszukiwanego hasła
+repo_kind = Wyszukaj repozytoria...
+user_kind = Wyszukaj użytkownilków...
+code_search_unavailable = Wyszukiwanie kodu jest obecnie niedostępne. Skontakuj sie z administratorem strony.
+no_results = Nie znaleziono pasujących wyników.
+org_kind = Wyszukaj organizacje...
+team_kind = Wyszukaj zespoły...
+code_kind = Wyszukaj kod...
+code_search_by_git_grep = Obecne wyniki wyszukiwania kodu są dostarczane przez "git grep". Wyniki mogą być lepsze, jeśli administrator witryny włączy indeksator kodu.
+project_kind = Wyszukaj projekty...
+branch_kind = Wyszukaj gałęzie...
+commit_kind = Wyszukaj commity...
+runner_kind = Wyszukaj runnery...
+keyword_search_unavailable = Wyszukiwanie według słów kluczowych jest obecnie niedostępne. Skontaktuj się z administratorem strony.
\ No newline at end of file
diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini
index 5e8af1d750..ce752bbddc 100644
--- a/options/locale/locale_pt-BR.ini
+++ b/options/locale/locale_pt-BR.ini
@@ -56,10 +56,10 @@ mirror=Espelhamento
 new_repo=Novo repositório
 new_migrate=Nova migração
 new_mirror=Novo espelhamento
-new_fork=Novo Fork de Repositório
+new_fork=Novo fork do repositório
 new_org=Nova organização
-new_project=Novo Projeto
-new_project_column=Nova Coluna
+new_project=Novo projeto
+new_project_column=Nova coluna
 manage_org=Gerenciar organizações
 admin_panel=Administração geral
 account_settings=Configurações da conta
@@ -143,7 +143,7 @@ copy_hash = Copiar hash
 tracked_time_summary = Resumo do tempo de rastreamento baseado em filtros da lista de issues
 confirm_delete_artifact = Tem certeza de que deseja excluir o artefato "%s"?
 filter = Filtro
-filter.clear = Limpar filtro
+filter.clear = Limpar filtros
 filter.is_archived = Arquivado
 filter.public = Público
 filter.is_template = Modelo
@@ -236,13 +236,13 @@ err_admin_name_pattern_not_allowed=Nome de usuário administrador é inválido,
 err_admin_name_is_invalid=Nome de usuário do administrador inválido
 
 general_title=Configurações gerais
-app_name=Nome do servidor
+app_name=Título do servidor
 app_name_helper=Você pode inserir o nome da empresa aqui.
-repo_path=Caminho raíz do repositório
+repo_path=Caminho raiz do repositório
 repo_path_helper=Todos os repositórios remotos do Git serão salvos neste diretório.
 lfs_path=Caminho raiz do Git LFS
 lfs_path_helper=Os arquivos armazenados com o Git LFS serão armazenados neste diretório. Deixe em branco para desabilitar.
-run_user=Executar como nome de usuário
+run_user=Executar como usuário
 run_user_helper=O nome de usuário do sistema operacional com o qual o Forgejo é executado. Observe que este usuário deve ter acesso ao caminho da raiz do repositório.
 domain=Domínio do servidor
 domain_helper=Domínio ou endereço de host para o servidor.
@@ -250,48 +250,48 @@ ssh_port=Porta do servidor SSH
 ssh_port_helper=Número da porta que seu servidor SSH está usando. Deixe em branco para desabilitar.
 http_port=Porta HTTP de uso do Forgejo
 http_port_helper=Número da porta que o servidor web do Forgejo irá usar.
-app_url=URL base do Forgejo
+app_url=URL base
 app_url_helper=Endereço base para URLs clone HTTP(S) e notificações por e-mail.
-log_root_path=Caminho do log
+log_root_path=Caminho dos arquivos de registro
 log_root_path_helper=Arquivos de log serão gravados neste diretório.
 
 optional_title=Configurações opcionais
 email_title=Configurações de e-mail
-smtp_addr=Host SMTP
-smtp_port=Porta SMTP
+smtp_addr=Endereço do servidor SMTP
+smtp_port=Porta do servidor SMTP
 smtp_from=Enviar e-mail como
 smtp_from_helper=Endereço de e-mail que o Forgejo irá usar. Digite um endereço de e-mail simples ou use o formato "Nome" <email@example.com>.
-mailer_user=Nome de usuário do SMTP
+mailer_user=Usuário do SMTP
 mailer_password=Senha do SMTP
-register_confirm=Exigir confirmação de e-mail para se cadastrar
-mail_notify=Habilitar notificações de e-mail
-server_service_title=Configurações de servidor e serviços de terceiros
-offline_mode=Habilitar autenticação local
+register_confirm=Exigir confirmação de e-mail para cadastros
+mail_notify=Habilitar notificações por e-mail
+server_service_title=Configurações do servidor e serviços de terceiros
+offline_mode=Habilitar modo local
 offline_mode_popup=Desabilitar redes de entrega de conteúdo de terceiros e entregar todos os recursos localmente.
 disable_gravatar=Desabilitar o gravatar
 disable_gravatar_popup=Desabilitar o gravatar e avatar de fontes de terceiros. Um avatar padrão será usado a menos que um usuário localmente carrega um avatar.
-federated_avatar_lookup=Habilitar avatares federativos
+federated_avatar_lookup=Habilitar avatares federados
 federated_avatar_lookup_popup=Habilitar a busca federativa de avatares a usar o serviço federativo de código aberto baseado no libravatar.
-disable_registration=Desabilitar auto-cadastro
+disable_registration=Somente administradores podem criar novas contas
 disable_registration_popup=Desabilitar auto-cadastro de usuário. Somente os administradores serão capazes de criar novas contas de usuário.
 allow_only_external_registration_popup=Permitir cadastro somente por meio de serviços externos
 openid_signin=Habilitar acesso via OpenID
 openid_signin_popup=Habilitar o acesso de usuários via OpenID.
-openid_signup=Habilitar o auto-cadastro via OpenID
+openid_signup=Habilitar cadastros via OpenID
 openid_signup_popup=Habilitar o auto-cadastro com base no OpenID.
 enable_captcha=Habilitar CAPTCHA ao registrar
 enable_captcha_popup=Obrigar validação por CAPTCHA para auto-cadastro de usuários.
-require_sign_in_view=Exigir acesso do usuário para a visualização de páginas
+require_sign_in_view=Apenas usuários logados podem visualizar páginas
 require_sign_in_view_popup=Limitar o acesso de página aos usuários autenticados. Os visitantes só verão as páginas de autenticação e cadastro.
 admin_setting_desc=Criar uma conta de administrador é opcional. O primeiro usuário cadastrado automaticamente se tornará um administrador.
 admin_title=Configurações da conta de administrador
-admin_name=Nome do usuário administrador
+admin_name=Usuário
 admin_password=Senha
 confirm_password=Confirmar senha
 admin_email=Endereço de e-mail
 install_btn_confirm=Instalar Forgejo
-test_git_failed=Falha ao testar o comando 'git': %v
-sqlite3_not_available=Esta versão do Forgejo não suporta SQLite3. Por favor faça o download da versão binária oficial em %s (não utilize a versão 'gobuild').
+test_git_failed=Falha ao testar o comando "git": %v
+sqlite3_not_available=Esta versão do Forgejo não possui suporte ao SQLite3. Baixe a versão oficial em %s (e não a versão "gobuild").
 invalid_db_setting=Configuração de banco de dados está inválida: %v
 invalid_db_table=A tabela "%s" do banco de dados é inválida: %v
 invalid_repo_path=A raiz do repositório está inválida: %v
@@ -1529,25 +1529,25 @@ issues.ref_reopened_from=`<a href="%[3]s">reabriu esta issue %[4]s</a> <a id="%[
 issues.ref_from=`de %[1]s`
 issues.author=Autor
 issues.author_helper=Este usuário é o autor.
-issues.role.owner=Proprietário
-issues.role.owner_helper=Este usuário é o dono deste repositório.
+issues.role.owner=Proprietário(a)
+issues.role.owner_helper=Este usuário é o proprietário deste repositório.
 issues.role.member=Membro
-issues.re_request_review=Re-solicitar revisão
+issues.re_request_review=Solicitar nova revisão
 issues.is_stale=Houve alterações nessa PR desde essa revisão
 issues.remove_request_review=Remover solicitação de revisão
 issues.remove_request_review_block=Não é possível remover a solicitação de revisão
 issues.dismiss_review=Descartar revisão
 issues.dismiss_review_warning=Tem certeza de que deseja descartar esta revisão?
-issues.sign_in_require_desc=<a href="%s">Acesse</a> para participar desta conversação.
+issues.sign_in_require_desc=<a href="%s">Inicie a sessão</a> para participar desta conversa.
 issues.edit=Editar
 issues.cancel=Cancelar
 issues.save=Salvar
-issues.label_title=Nome da etiqueta
-issues.label_description=Descrição da etiqueta
-issues.label_color=Cor da etiqueta
-issues.label_exclusive=Exclusivo
-issues.label_archive=Arquivar etiqueta
-issues.label_exclusive_desc=Nomeie o rótulo <code>escopo/item</code> para torná-lo mutuamente exclusivo com outros rótulos do <code>escopo/</code>.
+issues.label_title=Nome
+issues.label_description=Descrição
+issues.label_color=Cor
+issues.label_exclusive=Exclusiva
+issues.label_archive=Arquivar
+issues.label_exclusive_desc=Nomeie a etiqueta  <code>escopo/item</code> para torná-la mutuamente exclusiva em relação a outras etiquetas do <code>escopo/</code>.
 issues.label_exclusive_warning=Quaisquer rótulos com escopo conflitantes serão removidos ao editar os rótulos de uma issue ou pull request.
 issues.label_count=%d etiquetas
 issues.label_open_issues=%d issues abertas
@@ -1557,29 +1557,29 @@ issues.label_modify=Editar etiqueta
 issues.label_deletion=Excluir etiqueta
 issues.label_deletion_desc=A exclusão desta etiqueta irá removê-la de todas as issues. Tem certeza que deseja continuar?
 issues.label_deletion_success=A etiqueta foi excluída.
-issues.label.filter_sort.alphabetically=Alfabeticamente
-issues.label.filter_sort.reverse_alphabetically=Alfabeticamente inverso
-issues.label.filter_sort.by_size=Menor tamanho
-issues.label.filter_sort.reverse_by_size=Maior tamanho
-issues.num_participants_few=%d participante(s)
-issues.attachment.open_tab=`Clique para ver "%s" em uma nova aba`
+issues.label.filter_sort.alphabetically=por ordem alfabética
+issues.label.filter_sort.reverse_alphabetically=por ordem alfabética inversa
+issues.label.filter_sort.by_size=por menor tamanho
+issues.label.filter_sort.reverse_by_size=por maior tamanho
+issues.num_participants_few=%d participantes
+issues.attachment.open_tab=`Clique abrir "%s" em uma nova aba`
 issues.attachment.download=`Clique para baixar "%s"`
 issues.subscribe=Inscrever-se
 issues.unsubscribe=Desinscrever
-issues.unpin_issue=Desfixar issue
+issues.unpin_issue=Desafixar issue
 issues.max_pinned=Você não pode fixar mais issues
 issues.pin_comment=afixou este %s
 issues.unpin_comment=desafixou este %s
-issues.lock=Bloquear conversação
-issues.unlock=Desbloquear conversação
-issues.lock.unknown_reason=Não pode-se bloquear uma issue com um motivo desconhecido.
+issues.lock=Trancar conversa
+issues.unlock=Destrancar conversa
+issues.lock.unknown_reason=Não é possível trancar uma issue com um motivo desconhecido.
 issues.lock_duplicate=Uma issue não pode ser bloqueada duas vezes.
 issues.unlock_error=Não pode-se desbloquear uma issue que não esteja bloqueada.
-issues.lock_with_reason=bloqueada como <strong>%s</strong> e conversação limitada para colaboradores %s
-issues.lock_no_reason=bloqueada e conversação limitada para colaboradores %s
-issues.unlock_comment=desbloqueada esta conversação %s
-issues.lock_confirm=Bloquear
-issues.unlock_confirm=Desbloquear
+issues.lock_with_reason=trancou a conversa como sendo <strong>%s</strong> e restringiu-a aos colaboradores %s
+issues.lock_no_reason=trancou a conversa e restringiu-a aos colaboradores %s
+issues.unlock_comment=destrancou esta conversa %s
+issues.lock_confirm=Trancar
+issues.unlock_confirm=Destrancar
 issues.lock.notice_1=- Outros usuários não poderão adicionar novos comentários nesta issue.
 issues.lock.notice_2=- Você e outros colaboradores com acesso a este repositório ainda podem deixar comentários que outros podem ver.
 issues.lock.notice_3=- Você pode sempre desbloquear esta issue novamente no futuro.
@@ -1589,30 +1589,30 @@ issues.lock.reason=Motivo do bloqueio
 issues.lock.title=Conversação bloqueada para esta issue.
 issues.unlock.title=Conversação desbloqueada para esta issue.
 issues.comment_on_locked=Você não pode comentar em uma issue bloqueada.
-issues.delete=Apagar
+issues.delete=Excluir
 issues.delete.title=Apagar esta issue?
 issues.delete.text=Você realmente deseja excluir esta issue? (Isto irá remover permanentemente todo o conteúdo. Considere fechá-la em vez disso, se você pretende mantê-la arquivado)
 issues.tracker=Contador de tempo
-issues.start_tracking_short=Iniciar Cronômetro
-issues.start_tracking=Iniciar Cronômetro
+issues.start_tracking_short=Iniciar cronômetro
+issues.start_tracking=Iniciar contagem de tempo
 issues.start_tracking_history=`começou a trabalhar %s`
 issues.tracker_auto_close=Contador de tempo será parado automaticamente quando esta issue for fechada
 issues.tracking_already_started=`Você já iniciou o cronômetro em <a href="%s">outra issue</a>!`
-issues.stop_tracking=Parar Cronômetro
+issues.stop_tracking=Parar cronômetro
 issues.stop_tracking_history=`parou de trabalhar %s`
 issues.cancel_tracking=Descartar
-issues.cancel_tracking_history=`cronômetro cancelado %s`
+issues.cancel_tracking_history=`cancelou a contagem de tempo %s`
 issues.add_time=Adicionar tempo manualmente
-issues.del_time=Apagar este registro de tempo
+issues.del_time=Excluir este registro de tempo
 issues.add_time_short=Adicionar tempo
 issues.add_time_cancel=Cancelar
 issues.add_time_history=`adicionou tempo gasto %s`
 issues.del_time_history=`removeu tempo gasto %s`
 issues.add_time_hours=Horas
 issues.add_time_minutes=Minutos
-issues.add_time_sum_to_small=Nenhum tempo foi inserido.
-issues.time_spent_total=Tempo total gasto
-issues.time_spent_from_all_authors=`Tempo total gasto: %s`
+issues.add_time_sum_to_small=Nenhum tempo inserido.
+issues.time_spent_total=Total de tempo gasto
+issues.time_spent_from_all_authors=`Total de tempo gasto: %s`
 issues.due_date=Data limite
 issues.invalid_due_date_format=Formato da data limite inválido, deve ser 'dd/mm/aaaa'.
 issues.error_modifying_due_date=Falha ao modificar a data limite.
@@ -1625,19 +1625,19 @@ issues.due_date_form=dd/mm/aaaa
 issues.due_date_form_add=Adicionar data limite
 issues.due_date_form_edit=Editar
 issues.due_date_form_remove=Remover
-issues.due_date_not_set=Data limite não informada.
+issues.due_date_not_set=Não há data limite definida.
 issues.due_date_added=adicionou a data limite %s %s
 issues.due_date_modified=modificou a data limite de %[2]s para %[1]s %[3]s
 issues.due_date_remove=removeu a data limite %s %s
 issues.due_date_overdue=Em atraso
 issues.due_date_invalid=A data limite é inválida ou está fora do intervalo. Por favor, use o formato 'dd/mm/aaaa'.
 issues.dependency.title=Dependências
-issues.dependency.issue_no_dependencies=Nenhuma dependência definida.
-issues.dependency.pr_no_dependencies=Nenhuma dependência definida.
+issues.dependency.issue_no_dependencies=Não há dependências definidas.
+issues.dependency.pr_no_dependencies=Não há dependências definidas.
 issues.dependency.no_permission_1=Você não tem permissão para ler %d dependência
 issues.dependency.no_permission_n=Você não tem permissão para ler %d dependências
 issues.dependency.no_permission.can_remove=Você não tem permissão para ler esta dependência, mas pode remover esta dependência
-issues.dependency.add=Adicione…
+issues.dependency.add=Adicionar dependência…
 issues.dependency.cancel=Cancelar
 issues.dependency.remove=Remover
 issues.dependency.remove_info=Remover esta dependência
@@ -1671,7 +1671,7 @@ issues.review.dismissed_label=Rejeitada
 issues.review.left_comment=deixou um comentário
 issues.review.content.empty=Você precisa deixar um comentário indicando as alterações solicitadas.
 issues.review.reject=solicitou alterações %s
-issues.review.wait=foi solicitada para revisão %s
+issues.review.wait=foi solicitado(a) para revisar %s
 issues.review.add_review_request=solicitou uma revisão de %s %s
 issues.review.remove_review_request=removeu a solicitação de revisão para %s %s
 issues.review.remove_review_request_self=recusou-se a revisar %s
@@ -1681,9 +1681,9 @@ issues.review.review=Revisão
 issues.review.reviewers=Revisores
 issues.review.outdated=Desatualizado
 issues.review.outdated_description=O conteúdo foi alterado desde que este comentário foi feito
-issues.review.option.show_outdated_comments=Mostrar comentários desatualizados
-issues.review.option.hide_outdated_comments=Ocultar comentários desatualizados
-issues.review.show_outdated=Mostrar desatualizado
+issues.review.option.show_outdated_comments=Mostrar comentários obsoletos
+issues.review.option.hide_outdated_comments=Ocultar comentários obsoletos
+issues.review.show_outdated=Mostrar comentários obsoletos
 issues.review.hide_outdated=Ocultar desatualizado
 issues.review.show_resolved=Mostrar resolvidas
 issues.review.hide_resolved=Ocultar resolvidas
@@ -2543,7 +2543,7 @@ generated = Gerado
 clone_in_vscodium = Clonar com VSCodium
 mirror_sync = sincronizado
 desc.sha256 = SHA256
-issues.role.collaborator = Colaborador
+issues.role.collaborator = Colaborador(a)
 issues.label_archived_filter = Mostrar etiquetas arquivadas
 pulls.status_checks_hide_all = Esconder todas as verificações
 pulls.status_checks_show_all = Mostrar todas as verificações
@@ -2559,8 +2559,8 @@ migrate.forgejo.description = Migrar dados do codeberg.org ou outras instâncias
 commits.browse_further = Ver mais
 issues.role.first_time_contributor = Primeira vez contribuindo
 issues.role.first_time_contributor_helper = Esta é a primeira contribuição deste usuário para o repositório.
-issues.role.contributor = Contribuidor
-issues.role.member_helper = Este usuário é um membro da organização dona deste repositório.
+issues.role.contributor = Contribuidor(a)
+issues.role.member_helper = Este usuário é membro da organização proprietária deste repositório.
 issues.role.collaborator_helper = Este usuário foi convidado para colaborar neste repositório.
 pulls.cmd_instruction_checkout_title = Checkout
 settings.wiki_globally_editable = Permitir que qualquer pessoa possa editar a wiki
@@ -2592,7 +2592,7 @@ contributors.contribution_type.filter_label = Tipo de contribuição:
 contributors.contribution_type.commits = Commits
 settings.webhook.test_delivery_desc_disabled = Ative este webhook para testá-lo com um evento simulado.
 activity.navbar.contributors = Contribuidores
-issues.label_archive_tooltip = Rótulos arquivados não serão exibidos nas sugestões de pesquisa de rótulos por padrão.
+issues.label_archive_tooltip = Etiquetas arquivadas não serão exibidas nas sugestões de pesquisa de etiquetas.
 activity.navbar.pulse = Recente
 settings.units.overview = Geral
 settings.units.add_more = Adicionar mais...
@@ -2600,6 +2600,8 @@ pulls.commit_ref_at = `referenciou este pedido de mesclagem no commit <a id="%[1
 pulls.cmd_instruction_merge_title = Mesclar
 settings.units.units = Funcionalidades
 vendored = Externo
+issues.num_participants_one = %d participante
+issues.archived_label_description = (arquivada) %s
 
 [graphs]
 
diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini
index d0c5320cdc..c7fc74b234 100644
--- a/options/locale/locale_pt-PT.ini
+++ b/options/locale/locale_pt-PT.ini
@@ -154,7 +154,10 @@ more_items = Mais itens
 invalid_data = Dados inválidos: %v
 filter.clear = Retirar filtros
 filter.is_archived = Arquivado
-filter.not_template = Não é modelo
+filter.not_template = Não modelo
+toggle_menu = Comutar menu
+filter = Filtro
+copy_generic = Copiar para a área de transferência
 
 [aria]
 navbar=Barra de navegação
@@ -205,7 +208,7 @@ app_desc=Um serviço Git auto-hospedado e fácil de usar
 install=Fácil de instalar
 install_desc=Corra, simplesmente, <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download/#installation-from-binary">o ficheiro binário executável</a> para a sua plataforma, despache-o com o <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download/#container-image">Docker</a>, ou obtenha-o sob a forma de <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download">pacote</a>.
 platform=Multiplataforma
-platform_desc=Forgejo corre em qualquer plataforma onde possa compilar em linguagem <a target="_blank" rel="noopener noreferrer" href="http://golang.org/">Go</a>: Windows, macOS, Linux, ARM, etc. Escolha a sua preferida!
+platform_desc=Forgejo corre em qualquer plataforma onde possa compilar em linguagem <a target="_blank" rel="noopener noreferrer" href="https://go.dev/">Go</a>: Windows, macOS, Linux, ARM, etc. Escolha a sua preferida!
 lightweight=Leve
 lightweight_desc=Forgejo requer poucos recursos e pode correr num simples Raspberry Pi. Economize a energia da sua máquina!
 license=Código aberto
@@ -252,10 +255,10 @@ run_user_helper=O nome de utilizador do sistema operativo que vai executar o For
 domain=Domínio do servidor
 domain_helper=Domínio ou endereço do servidor.
 ssh_port=Porto do servidor SSH
-ssh_port_helper=O número do porto que o seu servidor SSH usa. Deixe em branco para desabilitar.
+ssh_port_helper=O número do porto que o seu servidor SSH usa. Deixe em branco para desabilitar o servidor SSH.
 http_port=Porto de escuta HTTP do Forgejo
-http_port_helper=O número do porto onde o servidor web do Forgejo estará à escuta.
-app_url=URL base do Forgejo
+http_port_helper=O número do porto que será usado pelo servidor web do Forgejo.
+app_url=URL base
 app_url_helper=Endereço base para os URLs e notificações por email das clonagens por HTTP(S).
 log_root_path=Localização dos registos
 log_root_path_helper=Os ficheiros de registo serão escritos nesta pasta.
@@ -286,7 +289,7 @@ openid_signup=Habilitar a auto-inscrição com OpenID
 openid_signup_popup=Habilitar a utilização do OpenID para fazer auto-inscrições.
 enable_captcha=Habilitar CAPTCHA na inscrição
 enable_captcha_popup=Exigir CAPTCHA na auto-inscrição de utilizadores.
-require_sign_in_view=Exigir sessão iniciada para visualizar páginas
+require_sign_in_view=Exigir sessão iniciada para visualizar conteúdo da instância
 require_sign_in_view_popup=Limitar o acesso às páginas aos utilizadores com sessão iniciada. Os visitantes só poderão visualizar as páginas de início de sessão e de inscrição.
 admin_setting_desc=A criação de uma conta de administração é opcional. O primeiro utilizador inscrito tornar-se-á automaticamente num administrador.
 admin_title=Configurações da conta de administração
@@ -295,13 +298,13 @@ admin_password=Senha
 confirm_password=Confirme a senha
 admin_email=Endereço de email
 install_btn_confirm=Instalar Forgejo
-test_git_failed=Não foi possível testar o comando 'git': %v
-sqlite3_not_available=Esta versão do Forgejo não suporta o SQLite3. Descarregue a versão binária oficial em %s (não utilize a versão 'gobuild').
+test_git_failed=Não foi possível testar o comando "git": %v
+sqlite3_not_available=Esta versão do Forgejo não suporta o SQLite3. Descarregue a versão binária oficial em %s (não utilize a versão "gobuild").
 invalid_db_setting=As configurações da base de dados são inválidas: %v
 invalid_db_table=A tabela "%s" da base de dados é inválida: %v
 invalid_repo_path=A localização base dos repositórios é inválida: %v
 invalid_app_data_path=A localização dos dados da aplicação é inválido: %v
-run_user_not_match=O nome de utilizador para 'executar como' não é o nome de utilizador corrente: %s → %s
+run_user_not_match=O nome de utilizador para "executar como utilizador" não é o nome de utilizador corrente: %s → %s
 internal_token_failed=Falha ao gerar o código interno: %v
 secret_key_failed=Falha ao gerar a chave secreta: %v
 save_config_failed=Falhou ao guardar a configuração: %v
@@ -314,7 +317,7 @@ default_allow_create_organization_popup=Permitir, por norma, que os novos utiliz
 default_enable_timetracking=Habilitar, por norma, a contagem do tempo
 default_enable_timetracking_popup=Habilitar, por norma, a contagem do tempo nos novos repositórios.
 no_reply_address=Domínio dos emails ocultos
-no_reply_address_helper=Nome de domínio para utilizadores com um endereço de email oculto. Por exemplo, o nome de utilizador 'silva' será registado no Git como 'silva@semresposta.exemplo.org' se o domínio de email oculto estiver definido como 'semresposta.exemplo.org'.
+no_reply_address_helper=Nome de domínio para utilizadores com um endereço de email oculto. Por exemplo, o nome de utilizador "silva" será registado no Git como "silva@semresposta.exemplo.org" se o domínio de email oculto estiver definido como "semresposta.exemplo.org".
 password_algorithm=Algoritmo de Hash da Senha
 invalid_password_algorithm=Algoritmo de hash da senha inválido
 password_algorithm_helper=Definir o algoritmo de hash da senha. Os algoritmos têm requisitos e resistência distintos. `argon2` é bastante seguro, mas usa muita memória e pode ser inapropriado para sistemas pequenos.
@@ -333,7 +336,7 @@ switch_dashboard_context=Trocar contexto do painel
 my_repos=Repositórios
 show_more_repos=Mostrar mais repositórios…
 collaborative_repos=Repositórios colaborativos
-my_orgs=As minhas organizações
+my_orgs=Organizações
 my_mirrors=As minhas réplicas
 view_home=Ver %s
 search_repos=Procurar um repositório…
@@ -398,7 +401,7 @@ allow_password_change=Exigir que o utilizador mude a senha (recomendado)
 reset_password_mail_sent_prompt=Foi enviado um email de confirmação para <b>%s</b>. Verifique a sua caixa de entrada dentro de %s para completar o processo de recuperação.
 active_your_account=Ponha a sua conta em funcionamento
 account_activated=A conta foi posta em funcionamento
-prohibit_login=Início de sessão proibido
+prohibit_login=É proibido iniciar sessão
 prohibit_login_desc=A sua conta está proibida de iniciar sessão. Contacte o administrador.
 resent_limit_prompt=Já fez um pedido recentemente para enviar um email para pôr a conta em funcionamento. Espere 3 minutos e tente novamente.
 has_unconfirmed_mail=Olá %s, tem um endereço de email não confirmado (<b>%s</b>). Se não recebeu um email de confirmação ou precisa de o voltar a enviar, clique no botão abaixo.
@@ -459,7 +462,7 @@ change_unconfirmed_email_error = Não foi possível mudar o endereço de email:
 [mail]
 view_it_on=Ver em %s
 reply=ou responda a este email imediatamente
-link_not_working_do_paste=Não está a funcionar? Tente copiar e colar no seu navegador.
+link_not_working_do_paste=A ligação não funciona? Tente copiar e colar na barra de URL do seu navegador.
 hi_user_x=Olá <b>%s</b>,
 
 activate_account=Por favor, ponha a sua conta em funcionamento
@@ -474,12 +477,12 @@ activate_email.text=Por favor clique na seguinte ligação para validar o seu en
 register_notify=Bem-vindo(a) ao Forgejo
 register_notify.title=%[1]s, bem-vindo(a) a %[2]s
 register_notify.text_1=este é o seu email de confirmação de registo para %s!
-register_notify.text_2=Agora pode iniciar a sessão com o nome de utilizador: %s.
-register_notify.text_3=Se esta conta foi criada para si, <a href="%s">defina a sua senha</a> primeiro.
+register_notify.text_2=Pode iniciar a sessão usando o seu nome de utilizador: %s
+register_notify.text_3=Se outra pessoa criou esta conta para si, terá de <a href="%s">definir a sua senha</a> primeiro.
 
 reset_password=Recupere a sua conta
-reset_password.title=%s, você pediu para recuperar a sua conta
-reset_password.text=Por favor clique na seguinte ligação para recuperar a sua conta em <b>%s</b>:
+reset_password.title=%s, recebemos um pedido para recuperar a sua conta
+reset_password.text=Se foi você, clique na ligação seguinte para recuperar a sua conta dentro de <b>%s</b>:
 
 register_success=Inscrição bem sucedida
 
@@ -509,13 +512,13 @@ release.downloads=Descargas:
 release.download.zip=Código fonte (ZIP)
 release.download.targz=Código fonte (TAR.GZ)
 
-repo.transfer.subject_to=%s gostaria de transferir "%s" para %s
-repo.transfer.subject_to_you=%s gostaria de transferir "%s" para si
+repo.transfer.subject_to=%s quer transferir o repositório "%s" para %s
+repo.transfer.subject_to_you=%s quer transferir o repositório "%s" para si
 repo.transfer.to_you=você
 repo.transfer.body=Para o aceitar ou rejeitar visite %s, ou ignore-o, simplesmente.
 
-repo.collaborator.added.subject=%s adicionou você a %s
-repo.collaborator.added.text=Foi adicionado(a) como colaborador(a) do repositório:
+repo.collaborator.added.subject=%s adicionou-o/a a %s como colaborador/a
+repo.collaborator.added.text=Foi adicionado/a como colaborador/a do repositório:
 
 team_invite.subject=%[1]s fez-lhe um convite para se juntar à organização %[2]s
 team_invite.text_1=%[1]s fez-lhe um convite para se juntar à equipa %[2]s na organização %[3]s.
@@ -556,8 +559,8 @@ SSPISeparatorReplacement=Separador
 SSPIDefaultLanguage=Idioma predefinido
 
 require_error=` não pode estar em branco.`
-alpha_dash_error=` deve conter apenas caracteres alfanuméricos, hífen ('-') e sublinhado ('_').`
-alpha_dash_dot_error=` deve conter apenas caracteres alfanuméricos, hífen ('-'), sublinhado ('_') e ponto ('.').`
+alpha_dash_error=` deve conter apenas caracteres alfanuméricos, hífen ("-") e sublinhado ("_").`
+alpha_dash_dot_error=` deve conter apenas caracteres alfanuméricos, hífen ("-"), sublinhado ("_") e ponto (".").`
 git_ref_name_error=` tem que ser um nome de referência Git bem formado.`
 size_error=` tem que ser do tamanho %s.`
 min_size_error=` tem que conter pelo menos %s caracteres.`
@@ -567,7 +570,7 @@ url_error=`"%s" não é um URL válido.`
 include_error=` tem que conter o texto "%s".`
 glob_pattern_error=` o padrão glob é inválido: %s.`
 regex_pattern_error=` o padrão regex é inválido: %s.`
-username_error=` só pode conter caracteres alfanuméricos ('0-9','a-z','A-Z'), hífen ('-'), sublinhado ('_') e ponto ('.') Não pode começar nem terminar com caracteres não alfanuméricos, e caracteres não alfanuméricos consecutivos também são proibidos.`
+username_error=` só pode conter caracteres alfanuméricos ("0-9","a-z","A-Z"), hífen ("-"), sublinhado ("_") e ponto (".") Não pode começar nem terminar com caracteres não alfanuméricos, e caracteres não alfanuméricos consecutivos também são proibidos.`
 invalid_group_team_map_error=` o mapeamento é inválido: %s`
 unknown_error=Erro desconhecido:
 captcha_incorrect=O código CAPTCHA está errado.
@@ -603,7 +606,7 @@ enterred_invalid_owner_name=O novo nome de proprietário não é válido.
 enterred_invalid_password=A senha que inseriu está errada.
 user_not_exist=O utilizador não existe.
 team_not_exist=A equipa não existe.
-last_org_owner=Não pode remover o último utilizador da equipa 'proprietários'. Tem que haver pelo menos um proprietário numa organização.
+last_org_owner=Não pode remover o último utilizador da equipa "proprietários". Tem que haver pelo menos um proprietário numa organização.
 cannot_add_org_to_team=Uma organização não pode ser adicionada como membro de uma equipa.
 duplicate_invite_to_team=O(A) utilizador(a) já tinha sido convidado(a) para ser membro da equipa.
 organization_leave_success=Você deixou a organização %s com sucesso.
@@ -633,6 +636,9 @@ Location = Localização
 To = Nome do ramo
 required_prefix = A entrada tem de começar com "%s"
 AccessToken = Código de acesso
+FullName = Nome completo
+Description = Descrição
+Pronouns = Pronomes
 
 [user]
 change_avatar=Mude o seu avatar…
@@ -663,7 +669,7 @@ unblock = Desbloquear
 followers_one = %d seguidor
 following_one = %d seguindo
 block_user.detail = Note que se bloquear este utilizador, serão executadas outras operações, tais como:
-block_user.detail_1 = Está a deixar de ser seguido por este utilizador.
+block_user.detail_1 = Está a deixar de ser seguido/a por este utilizador.
 block_user.detail_2 = Este utilizador não pode interagir com os seus repositórios, questões criadas e comentários.
 block_user.detail_3 = Este/a utilizador/a não o/a pode adicionar como colaborador/a nem você pode o/a adicionar como colaborador/a.
 follow_blocked_user = Não pode seguir este/a utilizador/a porque você o/a bloqueou ou este/a utilizador/a bloqueou-o/a a si.
@@ -682,11 +688,11 @@ applications=Aplicações
 orgs=Gerir organizações
 repos=Repositórios
 delete=Eliminar a conta
-twofa=Autenticação em dois passos
+twofa=Autenticação em dois passos (TOTP)
 account_link=Contas vinculadas
 organization=Organizações
 uid=UID
-webauthn=Chaves de segurança
+webauthn=Autenticação em dois passos (chaves de segurança)
 
 public_profile=Perfil público
 biography_placeholder=Conte-nos um pouco sobre si! (Pode usar Markdown)
@@ -696,9 +702,9 @@ password_username_disabled=Utilizadores não-locais não podem mudar os seus nom
 full_name=Nome completo
 website=Sítio web
 location=Localização
-update_theme=Substituir tema
+update_theme=Mudar tema
 update_profile=Modificar perfil
-update_language=Modificar idioma
+update_language=Mudar idioma
 update_language_not_found=O idioma "%s" não está disponível.
 update_language_success=O idioma foi modificado.
 update_profile_success=O seu perfil foi modificado.
@@ -729,8 +735,8 @@ comment_type_group_project=Planeamento
 comment_type_group_issue_ref=Referência da questão
 saved_successfully=As suas configurações foram guardadas com sucesso.
 privacy=Privacidade
-keep_activity_private=Esconder Trabalho da página do perfil
-keep_activity_private_popup=Torna o trabalho visível apenas para si e para os administradores
+keep_activity_private=Esconder trabalho da página do perfil
+keep_activity_private_popup=O seu trabalho será visível apenas para si e para os administradores da instância
 
 lookup_avatar_by_mail=Procurar avatar com base no endereço de email
 federated_avatar_lookup=Pesquisa de avatar federada
@@ -773,7 +779,7 @@ theme_update_error=O tema escolhido não existe.
 openid_deletion=Remover endereço OpenID
 openid_deletion_desc=Remover este endereço OpenID da sua conta impedirá que inicie a sessão com ele. Quer continuar?
 openid_deletion_success=O endereço OpenID foi removido.
-add_new_email=Adicionar novo endereço de email
+add_new_email=Adicionar endereço de email
 add_new_openid=Adicionar novo URI OpenID
 add_email=Adicionar endereço de email
 add_openid=Adicionar URI OpenID
@@ -789,15 +795,15 @@ manage_ssh_keys=Gerir chaves SSH
 manage_ssh_principals=Gerir Protagonistas de Certificados SSH
 manage_gpg_keys=Gerir chaves GPG
 add_key=Adicionar chave
-ssh_desc=Essas chaves públicas SSH estão associadas à sua conta. As chaves privadas correspondentes permitem acesso total aos seus repositórios.
+ssh_desc=Essas chaves públicas SSH estão associadas à sua conta. As chaves privadas correspondentes permitem acesso total aos seus repositórios. As chaves SSH que tenham sido validadas podem ser usadas para validar cometimentos Git assinados com SSH.
 principal_desc=Estes protagonistas de certificados SSH estão associados à sua conta e permitem acesso total aos seus repositórios.
-gpg_desc=Essas chaves GPG públicas estão associadas à sua conta. Mantenha as suas chaves privadas seguras, uma vez que elas permitem a validação dos cometimentos.
+gpg_desc=Essas chaves GPG públicas estão associadas à sua conta e usadas para verificar os seus cometimentos. Mantenha as suas chaves privadas seguras, uma vez que elas permitem assinar os cometimentos com a sua identidade.
 ssh_helper=<strong>Precisa de ajuda?</strong> Dê uma vista de olhos no guia do GitHub para <a href="%s">criar as suas próprias chaves SSH</a> ou para resolver <a href="%s">problemas comuns</a> que pode encontrar ao usar o SSH.
 gpg_helper=<strong>Precisa de ajuda?</strong> Dê uma vista de olhos no guia do GitHub <a href="%s">sobre GPG</a>.
-add_new_key=Adicionar Chave SSH
+add_new_key=Adicionar chave SSH
 add_new_gpg_key=Adicionar chave GPG
-key_content_ssh_placeholder=Começa com 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'sk-ecdsa-sha2-nistp256@openssh.com', ou 'sk-ssh-ed25519@openssh.com'
-key_content_gpg_placeholder=Começa com '-----BEGIN PGP PUBLIC KEY BLOCK-----'
+key_content_ssh_placeholder=Começa com "ssh-ed25519", "ssh-rsa", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "sk-ecdsa-sha2-nistp256@openssh.com", ou "sk-ssh-ed25519@openssh.com"
+key_content_gpg_placeholder=Começa com "-----BEGIN PGP PUBLIC KEY BLOCK-----"
 add_new_principal=Adicional Protagonista
 ssh_key_been_used=Esta chave SSH já tinha sido adicionada ao servidor.
 ssh_key_name_used=Já existe uma chave SSH com o mesmo nome na sua conta.
@@ -815,7 +821,7 @@ gpg_token=Código
 gpg_token_help=Pode gerar uma assinatura usando o seguinte comando:
 gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
 gpg_token_signature=Assinatura GPG blindada (com armadura ASCII)
-key_signature_gpg_placeholder=Começa com '-----BEGIN PGP SIGNATURE-----'
+key_signature_gpg_placeholder=Começa com "-----BEGIN PGP SIGNATURE-----"
 verify_gpg_key_success=A chave GPG "%s" foi validada.
 ssh_key_verified=Chave validada
 ssh_key_verified_long=A chave foi validada com um código e pode ser usada para validar cometimentos que correspondam a qualquer dos endereços de email em uso por parte deste utilizador.
@@ -825,7 +831,7 @@ ssh_token_required=Tem que fornecer uma assinatura para o código abaixo
 ssh_token=Código
 ssh_token_help=Pode gerar uma assinatura usando o seguinte comando:
 ssh_token_signature=Assinatura SSH blindada (com armadura ASCII)
-key_signature_ssh_placeholder=Começa com '-----BEGIN SSH SIGNATURE-----'
+key_signature_ssh_placeholder=Começa com "-----BEGIN SSH SIGNATURE-----"
 verify_ssh_key_success=A chave SSH "%s" foi validada.
 subkeys=Subchaves
 key_id=ID da chave
@@ -914,7 +920,7 @@ oauth2_application_remove_description=A remoção de uma aplicação OAuth2 impe
 oauth2_application_locked=O Forgejo pré-regista algumas aplicações OAuth2 no arranque, se for habilitado na configuração. Para evitar comportamentos inesperados, estas não podem ser editadas nem removidas. Consulte a documentação sobre o OAuth2, para obter mais informações.
 
 authorized_oauth2_applications=Aplicações OAuth2 autorizadas
-authorized_oauth2_applications_description=Concedeu acesso à sua conta pessoal do Forgejo a estas aplicações de terceiros. Por favor, revogue o acesso às aplicações que já não precisa.
+authorized_oauth2_applications_description=Concedeu acesso à sua conta pessoal do Forgejo a estas aplicações de terceiros. Por favor, revogue o acesso às aplicações que já não estão em uso.
 revoke_key=Revogar
 revoke_oauth2_grant=Revogar acesso
 revoke_oauth2_grant_description=Revogar o acesso desta aplicação de terceiros impedi-la-á de aceder aos seus dados. Tem a certeza?
@@ -925,7 +931,7 @@ twofa_recovery_tip=Se perder o seu dispositivo, poderá usar uma chave de recupe
 twofa_is_enrolled=A autenticação em dois passos <strong>está neste momento habilitada</strong> na sua conta.
 twofa_not_enrolled=A autenticação em dois passos não está neste momento habilitada na sua conta.
 twofa_disable=Desabilitar autenticação em dois passos
-twofa_scratch_token_regenerate=Voltar a gerar o código de recuperação
+twofa_scratch_token_regenerate=Voltar a gerar a chave de recuperação de utilização única
 twofa_scratch_token_regenerated=O seu código de recuperação agora é %s. Guarde-o num lugar seguro, não será mostrado novamente.
 twofa_enroll=Habilitar autenticação em dois passos
 twofa_disable_note=Pode desabilitar a autenticação em dois passos, se for necessário.
@@ -968,7 +974,7 @@ delete_account_title=Eliminar conta de utilizador
 delete_account_desc=Tem a certeza que quer eliminar permanentemente esta conta de utilizador?
 
 email_notifications.enable=Habilitar notificações por email
-email_notifications.onmention=Enviar email somente quando mencionado(a)
+email_notifications.onmention=Enviar email somente quando for mencionado/a
 email_notifications.disable=Desabilitar notificações por email
 email_notifications.submit=Definir preferência do email
 email_notifications.andyourown=e as suas próprias notificações
@@ -1010,7 +1016,7 @@ visibility=Visibilidade
 visibility_description=Somente o proprietário ou os membros da organização, se tiverem direitos, poderão vê-lo.
 visibility_helper=Tornar o repositório privado
 visibility_helper_forced=O administrador obriga a que os repositórios novos sejam privados.
-visibility_fork_helper=(alterar este parâmetro irá alterar também todas as derivações)
+visibility_fork_helper=(alterar este parâmetro irá alterar a visibilidade de todas as derivações)
 clone_helper=Precisa de ajuda para clonar? Visite a <a target="_blank" rel="noopener noreferrer" href="%s">Ajuda</a>.
 fork_repo=Derivar repositório
 fork_from=Derivar de
@@ -1054,7 +1060,7 @@ default_branch_label=predefinido
 default_branch_helper=O ramo principal é o ramo base para pedidos de integração e cometimentos.
 mirror_prune=Podar
 mirror_prune_desc=Remover referências obsoletas de seguimento remoto
-mirror_interval=Intervalo entre sincronizações (as unidades de tempo válidas são 'h', 'm' e 's'). O valor zero desabilita a sincronização periódica. (Intervalo mínimo: %s)
+mirror_interval=Intervalo entre sincronizações (as unidades de tempo válidas são "h", "m" e "s"). O valor zero desabilita a sincronização periódica. (Intervalo mínimo: %s)
 mirror_interval_invalid=O intervalo entre sincronizações não é válido.
 mirror_sync=sincronizado
 mirror_sync_on_commit=Sincronizar quando forem enviados cometimentos
@@ -1078,7 +1084,7 @@ reactions_more=e mais %d
 unit_disabled=O administrador desabilitou esta secção do repositório.
 language_other=Outros
 adopt_search=Insira o nome de utilizador para procurar repositórios adoptados... (deixe em branco para encontrar todos)
-adopt_preexisting_label=Adoptar ficheiros
+adopt_preexisting_label=Usar ficheiros
 adopt_preexisting=Adoptar ficheiros pré-existentes
 adopt_preexisting_content=Criar repositório a partir de %s
 adopt_preexisting_success=Ficheiros adoptados e repositório criado a partir de %s
@@ -1112,7 +1118,7 @@ desc.sha256=SHA256
 template.items=Itens do modelo
 template.git_content=Conteúdo Git (ramo principal)
 template.git_hooks=Automatismos do Git
-template.git_hooks_tooltip=Neste momento não pode modificar ou remover Automatismos do Git depois de adicionados. Escolha esta opção somente se confiar no repositório modelo.
+template.git_hooks_tooltip=Neste momento não pode modificar ou remover automatismos do Git depois de adicionados. Escolha esta opção somente se confiar no repositório modelo.
 template.webhooks=Automatismos web
 template.topics=Tópicos
 template.avatar=Avatar
@@ -2021,7 +2027,7 @@ activity.title.unresolved_conv_n=%d diálogos não concluídos
 activity.unresolved_conv_desc=Estas questões e estes pedidos de integração, que foram modificados recentemente, ainda não foram concluídos.
 activity.unresolved_conv_label=Em aberto
 activity.title.releases_1=%d lançamento
-activity.title.releases_n=%d Lançamentos
+activity.title.releases_n=%d lançamentos
 activity.title.releases_published_by=%s publicado por %s
 activity.published_release_label=Publicado
 activity.no_git_activity=Não houve quaisquer cometimentos feitos durante este período.
@@ -2102,10 +2108,10 @@ settings.sync_mirror=Sincronizar agora
 settings.pull_mirror_sync_in_progress=Puxando modificações a partir do repositório remoto %s, neste momento.
 settings.push_mirror_sync_in_progress=Enviando modificações para o repositório remoto %s, neste momento.
 settings.site=Sítio web
-settings.update_settings=Modificar configurações
+settings.update_settings=Guardar configurações
 settings.update_mirror_settings=Modificar configurações da réplica
 settings.branches.switch_default_branch=Trocar o ramo principal
-settings.branches.update_default_branch=Definir o ramo principal
+settings.branches.update_default_branch=Modificar o ramo principal
 settings.branches.add_new_rule=Adicionar nova regra
 settings.advanced_settings=Configurações avançadas
 settings.wiki_desc=Habilitar wiki do repositório
@@ -2143,8 +2149,8 @@ settings.projects_desc=Habilitar planeamentos no repositório
 settings.actions_desc=Habilitar operações no repositório (Forgejo Actions)
 settings.admin_settings=Configurações do administrador
 settings.admin_enable_health_check=Habilitar verificações de integridade (git fsck) no repositório
-settings.admin_code_indexer=Indexador de código
-settings.admin_stats_indexer=Indexador de estatísticas de código
+settings.admin_code_indexer=Indexador de código-fonte
+settings.admin_stats_indexer=Indexador de estatísticas de código-fonte
 settings.admin_indexer_commit_sha=Último SHA indexado
 settings.admin_indexer_unindexed=Não indexado
 settings.reindex_button=Adicionar à fila de reindexação
@@ -2243,7 +2249,7 @@ settings.webhook.body=Corpo
 settings.webhook.replay.description=Voltar a executar este automatismo web.
 settings.webhook.replay.description_disabled=Para reexecutar este automatismo web, habilite-o.
 settings.webhook.delivery.success=Foi adicionado um evento à fila de entrega. Pode demorar alguns segundos a aparecer no histórico de entregas.
-settings.githooks_desc=Os Automatismos do Git são executados pelo próprio Git. Pode editar os ficheiros de automatismo abaixo para configurar operações personalizadas.
+settings.githooks_desc=Os automatismos do Git são executados pelo próprio Git. Pode editar os ficheiros de automatismo abaixo para configurar operações personalizadas.
 settings.githook_edit_desc=Se o automatismo estiver desligado, será apresentado um conteúdo de teste. Deixar o conteúdo em branco irá desabilitar este automatismo.
 settings.githook_name=Nome do automatismo
 settings.githook_content=Conteúdo do automatismo
@@ -2311,7 +2317,7 @@ settings.event_package=Pacote
 settings.event_package_desc=Pacote criado ou eliminado num repositório.
 settings.branch_filter=Filtro de ramos
 settings.branch_filter_desc=Lista dos ramos a serem considerados nos eventos de envio e de criação e eliminação de ramos, especificada como um padrão glob. Se estiver em branco ou for <code>*</code>, serão reportados eventos para todos os ramos. Veja a documentação <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> para ver os detalhes da sintaxe. Exemplos: <code>trunk</code>, <code>{trunk,release*}</code>.
-settings.authorization_header=Cabeçalho de Autorização
+settings.authorization_header=Cabeçalho de autorização
 settings.authorization_header_desc=Será incluído como cabeçalho de autorização para pedidos, quando estiver presente. Exemplos: %s.
 settings.active=Em funcionamento
 settings.active_helper=Será enviada informação sobre os eventos despoletadores para o URL deste automatismo web.
@@ -2363,7 +2369,7 @@ settings.protected_branch.delete_rule=Eliminar regra
 settings.protected_branch_can_push=Permitir envios?
 settings.protected_branch_can_push_yes=Pode enviar
 settings.protected_branch_can_push_no=Não pode enviar
-settings.branch_protection=Salvaguarda do ramo '<b>%s</b>'
+settings.branch_protection=Regras de salvaguarda do ramo '<b>%s</b>'
 settings.protect_this_branch=Habilitar salvaguarda do ramo
 settings.protect_this_branch_desc=Impede a eliminação e restringe envios e integrações do Git no ramo.
 settings.protect_disable_push=Desabilitar envios
@@ -2406,10 +2412,10 @@ settings.require_signed_commits_desc=Rejeitar envios para este ramo que não est
 settings.protect_branch_name_pattern=Padrão do nome do ramo protegido
 settings.protect_branch_name_pattern_desc=Padrões de nomes de ramos protegidos. Consulte <a href="https://github.com/gobwas/glob">a documentação</a> para ver a sintaxe dos padrões. Exemplos: main, release/**
 settings.protect_patterns=Padrões
-settings.protect_protected_file_patterns=Padrões de ficheiros protegidos (separados com ponto e vírgula ';'):
-settings.protect_protected_file_patterns_desc=Ficheiros protegidos não podem ser modificados imediatamente, mesmo que o utilizador tenha direitos para adicionar, editar ou eliminar ficheiros neste ramo. Múltiplos padrões podem ser separados com ponto e vírgula (';'). Veja a documentação em <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> para ver a sintaxe. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
-settings.protect_unprotected_file_patterns=Padrões de ficheiros desprotegidos (separados com ponto e vírgula ';'):
-settings.protect_unprotected_file_patterns_desc=Ficheiros desprotegidos que podem ser modificados imediatamente se o utilizador tiver direitos de escrita, contornando a restrição no envio. Múltiplos padrões podem ser separados com ponto e vírgula (';'). Veja a documentação em <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> para ver a sintaxe. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
+settings.protect_protected_file_patterns=Padrões de ficheiros protegidos (separados com ponto e vírgula ";"):
+settings.protect_protected_file_patterns_desc=Ficheiros protegidos não podem ser modificados imediatamente, mesmo que o utilizador tenha direitos para adicionar, editar ou eliminar ficheiros neste ramo. Múltiplos padrões podem ser separados com ponto e vírgula (";"). Veja a documentação em <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> para ver a sintaxe. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
+settings.protect_unprotected_file_patterns=Padrões de ficheiros desprotegidos (separados com ponto e vírgula ";"):
+settings.protect_unprotected_file_patterns_desc=Ficheiros desprotegidos que podem ser modificados imediatamente se o utilizador tiver direitos de escrita, contornando a restrição no envio. Padrões múltiplos podem ser separados com ponto e vírgula (";"). Veja a documentação em <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> para ver a sintaxe. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
 settings.add_protected_branch=Habilitar salvaguarda
 settings.delete_protected_branch=Desabilitar salvaguarda
 settings.update_protect_branch_success=A salvaguarda do ramo "%s" foi modificada.
@@ -2425,12 +2431,12 @@ settings.block_outdated_branch=Bloquear integração se o pedido de integração
 settings.block_outdated_branch_desc=A integração não será possível quando o ramo de topo estiver abaixo do ramo base.
 settings.default_branch_desc=Escolha um ramo do repositório como sendo o predefinido para pedidos de integração e cometimentos:
 settings.merge_style_desc=Estilos de integração
-settings.default_merge_style_desc=Tipo de integração predefinido para pedidos de integração:
+settings.default_merge_style_desc=Tipo de integração predefinido
 settings.choose_branch=Escolha um ramo…
 settings.no_protected_branch=Não existem ramos protegidos.
 settings.edit_protected_branch=Editar
 settings.protected_branch_required_rule_name=Nome de regra obrigatório
-settings.protected_branch_duplicate_rule_name=Nome de regra duplicado
+settings.protected_branch_duplicate_rule_name=Já existe uma regra para este conjunto de ramos
 settings.protected_branch_required_approvals_min=O número mínimo exigido de aprovações não pode ser negativo.
 settings.tags=Etiquetas
 settings.tags.protection=Proteger etiquetas
@@ -2439,7 +2445,7 @@ settings.tags.protection.allowed=Com permissão
 settings.tags.protection.allowed.users=Utilizadores com permissão
 settings.tags.protection.allowed.teams=Equipas com permissão
 settings.tags.protection.allowed.noone=Ninguém
-settings.tags.protection.create=Proteger etiqueta
+settings.tags.protection.create=Adicionar regra
 settings.tags.protection.none=Não há etiquetas protegidas.
 settings.tags.protection.pattern.description=Pode usar um só nome ou um padrão glob ou uma expressão regular para corresponder a várias etiquetas. Para mais informações leia o <a target="_blank" rel="noopener" href="https://forgejo.org/docs/latest/user/protection/#protected-tags">guia das etiquetas protegidas</a>.
 settings.bot_token=Código do bot
@@ -2470,7 +2476,7 @@ settings.lfs_findcommits=Procurar cometimentos
 settings.lfs_lfs_file_no_commits=Não foram encontrados quaisquer cometimentos para este ficheiro LFS
 settings.lfs_noattribute=Esta localização não tem o atributo bloqueável no ramo principal
 settings.lfs_delete=Eliminar ficheiro LFS com o OID %s
-settings.lfs_delete_warning=Eliminar um ficheiro LFS pode causar erros do tipo 'elemento não existe' no checkout. Tem a certeza?
+settings.lfs_delete_warning=Eliminar um ficheiro LFS pode causar erros do tipo "elemento não existe" no checkout. Tem a certeza?
 settings.lfs_findpointerfiles=Procurar ficheiros apontadores
 settings.lfs_locks=Bloqueios
 settings.lfs_invalid_locking_path=Localização inválida: %s
@@ -2535,7 +2541,7 @@ diff.comment.add_single_comment=Adicionar um único comentário
 diff.comment.add_review_comment=Adicionar comentário
 diff.comment.start_review=Iniciar revisão
 diff.comment.reply=Responder
-diff.review=Revisão
+diff.review=Terminar revisão
 diff.review.header=Submeter revisão
 diff.review.placeholder=Comentário da revisão
 diff.review.comment=Comentar
@@ -2603,7 +2609,7 @@ release.tags_for=Etiquetas para %s
 branch.name=Nome do ramo
 branch.already_exists=Já existe um ramo com o nome "%s".
 branch.delete_head=Eliminar
-branch.delete=`Eliminar o ramo "%s"`
+branch.delete=Eliminar o ramo "%s"
 branch.delete_html=Eliminar ramo
 branch.delete_desc=Eliminar um ramo é algo permanente. Embora o ramo eliminado possa continuar a existir por um breve período de tempo antes de ser realmente removido, a operação NÃO PODERÁ ser desfeita na maioria dos casos. Quer continuar?
 branch.deletion_success=O ramo "%s" foi eliminado.
@@ -2620,9 +2626,9 @@ branch.restore_success=O ramo "%s" foi restaurado.
 branch.restore_failed=Falhou a restauração do ramo "%s".
 branch.protected_deletion_failed=O ramo "%s" está protegido, não pode ser eliminado.
 branch.default_deletion_failed=O ramo "%s" é o ramo principal, não pode ser eliminado.
-branch.restore=`Restaurar o ramo "%s"`
-branch.download=`Descarregar o ramo "%s"`
-branch.rename=`Renomear ramo "%s"`
+branch.restore=Restaurar o ramo "%s"
+branch.download=Descarregar o ramo "%s"
+branch.rename=Renomear o ramo "%s"
 branch.search=Pesquisar ramo
 branch.included_desc=Este ramo faz parte do ramo principal
 branch.included=Incluído
@@ -2646,7 +2652,7 @@ tag.create_success=A etiqueta "%s" foi criada.
 topic.manage_topics=Gerir tópicos
 topic.done=Concluído
 topic.count_prompt=Não pode escolher mais do que 25 tópicos
-topic.format_prompt=Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') ou pontos ('.') e podem ter até 35 caracteres. As letras têm que ser minúsculas.
+topic.format_prompt=Os tópicos devem começar com uma letra ou um número, podem incluir traços ("-") ou pontos (".") e podem ter até 35 caracteres. As letras têm que ser minúsculas.
 
 find_file.go_to_file=Ir para o ficheiro
 find_file.no_matching=Não foi encontrado qualquer ficheiro correspondente
@@ -2664,7 +2670,7 @@ admin.update_flags = Modificar marcadores
 admin.flags_replaced = Os marcadores do repositório foram substituídos
 commits.browse_further = Explorar mais um pouco
 commits.renamed_from = Renomeado de %s
-size_format = %[1]s: %[2]s, %[3]s: %[4]s
+size_format = %[1]s: %[2]s; %[3]s: %[4]s
 issues.archived_label_description = (arquivado) %s
 admin.failed_to_replace_flags = Falhou a reposição dos marcadores do repositório
 open_with_editor = Abrir com %s
@@ -2680,6 +2686,55 @@ migrate.forgejo.description = Migrar dados de codeberg.org ou de outras instânc
 n_commit_one = %s cometimento
 editor.commit_id_not_matching = O ID de cometimento não corresponde ao que estava a editar. Cometa para um ramo novo e depois integre.
 commits.search_branch = Este ramo
+pulls.title_desc_one = quer integrar %[1]d cometimento do ramo <code>%[2]s</code> no ramo <code id="branch_target">%[3]s</code>
+pulls.reopen_failed.base_branch = O pedido de integração não pode ser reaberto porque o ramo base já não existe.
+activity.navbar.code_frequency = Frequência de programação
+settings.units.add_more = Adicionar mais...
+settings.wiki_rename_branch_main_desc = Renomear o ramo usado internamente pelo Wiki para "%s". Esta operação é permanente e não poderá ser revertida.
+settings.add_collaborator_blocked_our = Não foi possível adicionar o/a colaborador/a porque o/a proprietário/a do repositório bloqueou-os.
+settings.add_webhook.invalid_path = A localização não pode conter "." ou ".." ou ficar em branco. Não pode começar ou terminar com uma barra.
+settings.graphql_url = URL do GraphQL
+pulls.commit_ref_at = `referiu este pedido de integração a partir de um cometimento <a id="%[1]s" href="#%[1]s">%[2]s</a>`
+settings.confirm_wiki_branch_rename = Renomear o ramo do wiki
+settings.wiki_branch_rename_success = O nome do ramo do wiki do repositório foi normalizado com sucesso.
+settings.wiki_branch_rename_failure = Falhou a normalização do nome do ramo do wiki do repositório.
+settings.add_collaborator_blocked_them = Não foi possível adicionar o colaborador porque bloquearam o/a proprietário/a do repositório.
+pulls.made_using_agit =AGit
+settings.confirmation_string = Texto de confirmação
+settings.event_pull_request_enforcement = Execução
+pulls.blocked_by_user = Não pode criar um pedido de integração neste repositório porque foi bloqueado/a pelo/a proprietário/a do repositório.
+pulls.reopen_failed.head_branch = O pedido de integração não pode ser reaberto porque o ramo de topo já não existe.
+wiki.cancel = Cancelar
+settings.wiki_rename_branch_main = Normalizar o nome do ramo do Wiki
+settings.enforce_on_admins = Impor esta regra nos administradores de repositórios
+settings.enforce_on_admins_desc = Os administradores de repositórios não podem contornar esta regra.
+release.download_count_one = %s descarga
+release.download_count_few = %s descargas
+release.system_generated = Este anexo é gerado automaticamente.
+pulls.ready_for_review = Pronto/a para rever?
+settings.units.units = Unidades do repositório
+error.broken_git_hook = Os automatismos git deste repositório parecem estar danificados. Consulte a <a target="_blank" rel="noreferrer" href="%s">documentação</a> sobre como os consertar e depois envie alguns cometimentos para refrescar o estado.
+settings.rename_branch_failed_protected = Não é possível renomear o ramo %s porque é um ramo protegido.
+settings.units.overview = Visão geral
+activity.navbar.recent_commits = Cometimentos recentes
+settings.wiki_globally_editable = Permitir que qualquer pessoa edite o wiki
+settings.wiki_rename_branch_main_notices_1 = Esta operação <strong>NÃO</strong> pode ser revertida.
+settings.wiki_rename_branch_main_notices_2 = Isto irá renomear permanentemente o ramo interno do wiki do repositório %s. Os checkouts existentes terão de ser refrescados.
+settings.sourcehut_builds.manifest_path = Localização do manifesto da construção
+settings.sourcehut_builds.visibility = Visibilidade do trabalho
+settings.sourcehut_builds.secrets = Segredos
+settings.matrix.room_id_helper = O ID da Sala pode ser obtido no cliente web Element > Configurações da sala > Avançado > ID interno da sala. Exemplo: %s.
+settings.web_hook_name_sourcehut_builds = Construções do SourceHut
+settings.enter_repo_name = Insira o nome do/a proprietário/a e do repositório tal como é apresentado:
+issues.comment.blocked_by_user = Não pode criar um comentário nesta questão porque foi bloqueado/a pelo/a proprietário/a ou pelo remetente da questão.
+pulls.merged_title_desc_one = integrou %[1]d cometimento do ramo <code>%[2]s</code> no ramo <code>%[3]s</code> %[4]s
+pulls.agit_explanation = Criado usando a sequência de trabalho AGit. AGit deixa os contribuidores proporem alterações usando "git push" sem criar uma derivação ou um ramo novo.
+settings.new_owner_blocked_doer = O/A novo/a proprietário/a bloqueou-o/a.
+settings.matrix.access_token_helper = É recomendado criar uma conta Matrix só para isto. O código de acesso pode ser obtido a partir do cliente web Element (num separador privado/incógnito) > Menu de utilizador (canto superior esquerdo) > Todas as configurações > Ajuda e sobre > Avançado > Código de acesso (logo abaixo do URL do servidor caseiro). Feche o separador privado/incógnito (terminar a sessão iria invalidar o código).
+settings.sourcehut_builds.secrets_helper = Dar, ao trabalho, acesso aos segredos da construção (requer a permissão SECRETS:RO)
+settings.sourcehut_builds.access_token_helper = Código de acesso que tem a permissão JOBS:RW. Gera um <a target="_blank" rel="noopener noreferrer" href="%s">código builds.sr.ht</a> ou um <a target="_blank" rel="noopener noreferrer" href="%s">código builds.sr.ht com acesso aos segredos</a> em meta.sr.ht.
+release.hide_archive_links = Esconder arquivos gerados automaticamente
+release.hide_archive_links_helper = Esconder arquivos de código-fonte gerados automaticamente para este lançamento. Por exemplo, se estiver a carregar o seu próprio.
 
 [graphs]
 component_loading=A carregar %s...
@@ -2687,6 +2742,8 @@ component_loading_failed=Não foi possível carregar %s
 component_loading_info=Isto pode demorar um pouco…
 component_failed_to_load=Ocorreu um erro inesperado.
 contributors.what=contribuições
+code_frequency.what = frequência de programação
+recent_commits.what = cometimentos recentes
 
 [org]
 org_name_holder=Nome da organização
@@ -2727,7 +2784,7 @@ settings.visibility=Visibilidade
 settings.visibility.public=Público
 settings.visibility.limited=Limitada (visível apenas para utilizadores autenticados)
 settings.visibility.limited_shortname=Limitada
-settings.visibility.private=Privada (visível apenas para os membros da organização)
+settings.visibility.private=Privada (visível apenas para membros da organização)
 settings.visibility.private_shortname=Privado
 
 settings.update_settings=Modificar configurações
@@ -2811,6 +2868,7 @@ teams.all_repositories_admin_permission_desc=Esta equipa atribui o acesso de <st
 teams.invite.title=Foi-lhe feito um convite para se juntar à equipa <strong>%s</strong> na organização<strong>%s</strong>.
 teams.invite.by=Convidado(a) por %s
 teams.invite.description=Clique no botão abaixo para se juntar à equipa.
+follow_blocked_user = Não pode seguir esta organização porque esta organização bloqueou-o/a.
 
 [admin]
 dashboard=Painel de controlo
@@ -2818,7 +2876,7 @@ self_check=Auto-verificação
 identity_access=Identidade e acesso
 users=Contas de utilizador
 organizations=Organizações
-assets=Recursos de código
+assets=Recursos do código-fonte
 repositories=Repositórios
 hooks=Automatismos web
 integrations=Integrações
@@ -2859,8 +2917,8 @@ dashboard.delete_repo_archives.started=Foi iniciada a tarefa de eliminação de
 dashboard.delete_missing_repos=Eliminar todos os repositórios que não tenham os seus ficheiros Git
 dashboard.delete_missing_repos.started=Foi iniciada a tarefa de eliminação de todos os repositórios que não têm ficheiros git.
 dashboard.delete_generated_repository_avatars=Eliminar avatares gerados do repositório
-dashboard.sync_repo_branches=Sincronizar ramos perdidos de dados do git para bases de dados
-dashboard.sync_repo_tags=Sincronizar etiquetas dos dados do git para a base de dados
+dashboard.sync_repo_branches=Sincronizar ramos perdidos de dados do Git para a base de dados
+dashboard.sync_repo_tags=Sincronizar etiquetas dos dados do Git para a base de dados
 dashboard.update_mirrors=Sincronizar réplicas
 dashboard.repo_health_check=Verificar a saúde de todos os repositórios
 dashboard.check_repo_stats=Verificar as estatísticas de todos os repositórios
@@ -2868,9 +2926,9 @@ dashboard.archive_cleanup=Eliminar arquivos de repositórios antigos
 dashboard.deleted_branches_cleanup=Limpar ramos eliminados
 dashboard.update_migration_poster_id=Sincronizar os IDs do remetente da migração
 dashboard.git_gc_repos=Fazer a recolha do lixo em todos os repositórios
-dashboard.resync_all_sshkeys=Sincronizar o ficheiro '.ssh/authorized_keys' com as chaves SSH do Forgejo.
-dashboard.resync_all_sshprincipals=Modificar o ficheiro '.ssh/authorized_principals' com os protagonistas SSH do Forgejo.
-dashboard.resync_all_hooks=Voltar a sincronizar automatismos de pré-acolhimento, modificação e pós-acolhimento de todos os repositórios.
+dashboard.resync_all_sshkeys=Sincronizar o ficheiro ".ssh/authorized_keys" com as chaves SSH do Forgejo.
+dashboard.resync_all_sshprincipals=Modificar o ficheiro ".ssh/authorized_principals" com os protagonistas SSH do Forgejo.
+dashboard.resync_all_hooks=Voltar a sincronizar automatismos de pré-acolhimento, modificação e pós-acolhimento de todos os repositórios
 dashboard.reinit_missing_repos=Reinicializar todos os repositórios Git em falta para os quais existam registos
 dashboard.sync_external_users=Sincronizar dados externos do utilizador
 dashboard.cleanup_hook_task_table=Limpar tabela hook_task
@@ -2897,14 +2955,14 @@ dashboard.mspan_structures_obtained=Estruturas MSpan obtidas
 dashboard.mcache_structures_usage=Uso das estruturas MCache
 dashboard.mcache_structures_obtained=Estruturas MCache obtidas
 dashboard.profiling_bucket_hash_table_obtained=Perfil obtido da tabela de hash do balde
-dashboard.gc_metadata_obtained=Metadados da recolha de lixo obtidos
+dashboard.gc_metadata_obtained=Metadados obtidos da recolha de lixo
 dashboard.other_system_allocation_obtained=Outras alocações de sistema obtidas
 dashboard.next_gc_recycle=Próxima reciclagem da recolha de lixo
 dashboard.last_gc_time=Tempo decorrido desde a última recolha de lixo
 dashboard.total_gc_time=Pausa total da recolha de lixo
 dashboard.total_gc_pause=Pausa total da recolha de lixo
 dashboard.last_gc_pause=Última pausa da recolha de lixo
-dashboard.gc_times=Tempos da recolha de lixo
+dashboard.gc_times=N.º de recolhas de lixo
 dashboard.delete_old_actions=Eliminar todas as operações antigas da base de dados
 dashboard.delete_old_actions.started=Foi iniciado o processo de eliminação de todas as operações antigas da base de dados.
 dashboard.update_checker=Verificador de novas versões
@@ -2918,7 +2976,7 @@ dashboard.sync_branch.started=Sincronização de ramos iniciada
 dashboard.sync_tag.started=Sincronização de etiquetas iniciada
 dashboard.rebuild_issue_indexer=Reconstruir indexador de questões
 
-users.user_manage_panel=Gestão das contas de utilizadores
+users.user_manage_panel=Gerir contas de utilizador
 users.new_account=Criar conta de utilizador
 users.name=Nome de utilizador
 users.full_name=Nome completo
@@ -2946,10 +3004,10 @@ users.max_repo_creation=Número máximo de repositórios
 users.max_repo_creation_desc=(insira -1 para usar o limite predefinido a nível global)
 users.is_activated=A conta de utilizador está em funcionamento
 users.prohibit_login=Desabilitar início de sessão
-users.is_admin=É administrador(a)
-users.is_restricted=É restrito
+users.is_admin=É administrador/a
+users.is_restricted=A conta é restrita
 users.allow_git_hook=Pode criar automatismos do Git
-users.allow_git_hook_tooltip=Os Automatismos do Git são executados em nome do utilizador do sistema operativo que corre o Forgejo e têm o mesmo nível de acesso ao servidor. Por causa disso, utilizadores com este privilégio especial de Automatismo do Git podem aceder e modificar todos os repositórios do Forgejo, assim como a base de dados usada pelo Forgejo. Consequentemente, também podem ganhar privilégios de administrador do Forgejo.
+users.allow_git_hook_tooltip=Os automatismos do Git são executados em nome do utilizador do sistema operativo que corre o Forgejo e têm o mesmo nível de acesso ao servidor. Por causa disso, utilizadores com este privilégio especial de automatismo do Git podem aceder e modificar todos os repositórios do Forgejo, assim como a base de dados usada pelo Forgejo. Consequentemente, também podem ganhar privilégios de administrador do Forgejo.
 users.allow_import_local=Pode importar repositórios locais
 users.allow_create_organization=Pode criar organizações
 users.update_profile=Modificar conta do utilizador
@@ -2958,7 +3016,7 @@ users.cannot_delete_self=Não se pode eliminar a si próprio
 users.still_own_repo=Este utilizador ainda possui um ou mais repositórios. Elimine ou transfira esses repositórios primeiro.
 users.still_has_org=Este utilizador é membro de uma organização. Remova, primeiro, o utilizador de todas as organizações.
 users.purge=Eliminar utilizador
-users.purge_help=Eliminar o utilizador à força, juntamente com todos os seus repositórios, organizações e pacotes. Também serão eliminados todos os seus comentários.
+users.purge_help=Eliminar o utilizador à força, juntamente com todos os seus repositórios, organizações e pacotes. Também serão eliminados todos os comentários e questões criados por este utilizador.
 users.still_own_packages=Este utilizador ainda possui um ou mais pacotes, elimine esses pacotes primeiro.
 users.deletion_success=A conta de utilizador foi eliminada.
 users.reset_2fa=Reinicializar a autenticação em dois passos
@@ -2969,14 +3027,14 @@ users.list_status_filter.not_active=Desligado
 users.list_status_filter.is_admin=Administrador
 users.list_status_filter.not_admin=Não Administrador
 users.list_status_filter.is_restricted=Restrito
-users.list_status_filter.not_restricted=Não restrito
+users.list_status_filter.not_restricted=Não restrito/a
 users.list_status_filter.is_prohibit_login=Proibir início de sessão
 users.list_status_filter.not_prohibit_login=Permitir início de sessão
 users.list_status_filter.is_2fa_enabled=Autenticação em dois passos habilitada
 users.list_status_filter.not_2fa_enabled=Autenticação em dois passos desabilitada
 users.details=Detalhes do utilizador
 
-emails.email_manage_panel=Gestão de endereços de email do utilizador
+emails.email_manage_panel=Gerir endereços de email do utilizador
 emails.primary=Principal
 emails.activated=Em uso
 emails.filter_sort.email=Email
@@ -2989,13 +3047,13 @@ emails.duplicate_active=Este endereço de email já está a ser usado por outro
 emails.change_email_header=Modificar propriedades do email
 emails.change_email_text=Tem a certeza que quer modificar este endereço de email?
 
-orgs.org_manage_panel=Gestão das organizações
+orgs.org_manage_panel=Gerir organizações
 orgs.name=Nome
 orgs.teams=Equipas
 orgs.members=Membros
 orgs.new_orga=Nova organização
 
-repos.repo_manage_panel=Gestão dos repositórios
+repos.repo_manage_panel=Gerir repositórios
 repos.unadopted=Repositórios não adoptados
 repos.unadopted.no_more=Não foram encontrados mais repositórios não adoptados
 repos.owner=Proprietário(a)
@@ -3008,7 +3066,7 @@ repos.issues=Questões
 repos.size=Tamanho
 repos.lfs_size=Tamanho do LFS
 
-packages.package_manage_panel=Gestão de pacotes
+packages.package_manage_panel=Gerir pacotes
 packages.total_size=Tamanho total: %s
 packages.unreferenced_size=Tamanho não referenciado: %s
 packages.cleanup=Limpar dados expirados
@@ -3032,7 +3090,7 @@ systemhooks.desc=Os automatismos web fazem pedidos HTTP POST automaticamente a u
 systemhooks.add_webhook=Adicionar automatismo web do sistema
 systemhooks.update_webhook=Modificar automatismo web do sistema
 
-auths.auth_manage_panel=Gestão das fontes de autenticação
+auths.auth_manage_panel=Gerir fontes de autenticação
 auths.new=Adicionar fonte de autenticação
 auths.name=Nome
 auths.type=Tipo
@@ -3052,18 +3110,18 @@ auths.user_dn=DN do utilizador
 auths.attribute_username=Atributo do nome de utilizador
 auths.attribute_username_placeholder=Deixe em branco para usar o nome de utilizador inserido no Forgejo.
 auths.attribute_name=Atributo do Primeiro Nome
-auths.attribute_surname=Atributo do Sobrenome
+auths.attribute_surname=Atributo do sobrenome
 auths.attribute_mail=Atributo do email
 auths.attribute_ssh_public_key=Atributo da chave pública SSH
 auths.attribute_avatar=Atributo do avatar
-auths.attributes_in_bind=Buscar os atributos no contexto de Bind DN
+auths.attributes_in_bind=Buscar atributos no contexto do Bind DN
 auths.allow_deactivate_all=Permitir que um resultado de pesquisa vazio desabilite todos os utilizadores
 auths.use_paged_search=Usar pesquisa paginada
 auths.search_page_size=Tamanho da página
 auths.filter=Filtro de utilizador
 auths.admin_filter=Filtro de administrador
 auths.restricted_filter=Filtro restrito
-auths.restricted_filter_helper=Deixe em branco para não definir quaisquer utilizadores como restritos. Use um asterisco ('*') para definir todos os utilizadores que não correspondam ao filtro de administrador como restritos.
+auths.restricted_filter_helper=Deixe em branco para não definir quaisquer utilizadores como restritos. Use um asterisco ("*") para definir todos os utilizadores que não correspondam ao filtro de administrador como restritos.
 auths.verify_group_membership=Verificar associação ao grupo no LDAP (deixe o filtro vazio para ignorar)
 auths.group_search_base=Base DN para a pesquisa de grupos
 auths.group_attribute_list_users=Atributo de grupo que contém a lista de utilizadores
@@ -3076,7 +3134,7 @@ auths.smtp_auth=Tipo de autenticação SMTP
 auths.smtphost=Servidor SMTP
 auths.smtpport=Porto do SMTP
 auths.allowed_domains=Domínios permitidos
-auths.allowed_domains_helper=Deixe em branco para permitir todos os domínios. Separe múltiplos domínios com uma vírgula (',').
+auths.allowed_domains_helper=Deixe em branco para permitir todos os domínios. Separe múltiplos domínios com uma vírgula (",").
 auths.skip_tls_verify=Ignorar validação TLS
 auths.force_smtps=Forçar SMTPS
 auths.force_smtps_helper=SMTPS é usado sempre no porto 465. Defina um valor para forçar o SMTPS a usar outros portos (caso contrário será usado STARTTLS noutros portos, se for suportado pelo servidor).
@@ -3099,13 +3157,13 @@ auths.skip_local_two_fa=Ignorar a autenticação em dois passos local
 auths.skip_local_two_fa_helper=Deixar esta opção desligada faz com que os utilizadores locais que tenham a autenticação em dois passos habilitada sejam obrigados a passar por ela para iniciar a sessão
 auths.oauth2_tenant=Locatário
 auths.oauth2_scopes=Âmbitos adicionais
-auths.oauth2_required_claim_name=Nome de Reivindicação obrigatório
+auths.oauth2_required_claim_name=Nome de reivindicação obrigatório
 auths.oauth2_required_claim_name_helper=Defina este nome para restringir o início de sessão desta fonte a utilizadores que tenham uma reivindicação com este nome
-auths.oauth2_required_claim_value=Valor de Reivindicação obrigatório
+auths.oauth2_required_claim_value=Valor de reivindicação obrigatório
 auths.oauth2_required_claim_value_helper=Defina este valor para restringir o início de sessão desta fonte a utilizadores que tenham uma reivindicação com este nome e este valor
 auths.oauth2_group_claim_name=Reivindicar nome que fornece nomes de grupo para esta fonte. (Opcional)
-auths.oauth2_admin_group=Valor da Reivindicação de Grupo para utilizadores administradores. (Opcional - exige a reivindicação de nome acima)
-auths.oauth2_restricted_group=Valor da Reivindicação de Grupo para utilizadores restritos. (Opcional - exige a reivindicação de nome acima)
+auths.oauth2_admin_group=Valor da reivindicação de grupo para utilizadores administradores (opcional — exige a reivindicação de nome acima).
+auths.oauth2_restricted_group=Valor da reivindicação de grupo para utilizadores restritos (opcional — exige a reivindicação de nome acima).
 auths.oauth2_map_group_to_team=Mapear grupos reclamados em equipas da organização (opcional — requer nome de reclamação acima).
 auths.oauth2_map_group_to_team_removal=Remover utilizadores das equipas sincronizadas se esses utilizadores não pertencerem ao grupo correspondente.
 auths.enable_auto_register=Habilitar o registo automático
@@ -3123,7 +3181,7 @@ auths.tips=Dicas
 auths.tips.oauth2.general=Autenticação OAuth2
 auths.tips.oauth2.general.tip=Ao registar uma nova autenticação OAuth2, o URL da ligação de retorno ou do reencaminhamento deve ser:
 auths.tip.oauth2_provider=Fornecedor OAuth2
-auths.tip.bitbucket=Registe um novo consumidor de OAuth em https://bitbucket.org/account/user/<o_seu_nome_de_utilizador>/oauth-consumers/new e adicione a permissão 'Account' - 'Read'
+auths.tip.bitbucket=Registe um novo consumidor de OAuth em https://bitbucket.org/account/user/<o_seu_nome_de_utilizador>/oauth-consumers/new e adicione a permissão "Account" - "Read"
 auths.tip.nextcloud=`Registe um novo consumidor OAuth na sua instância usando o seguinte menu "Configurações → Segurança → Cliente OAuth 2.0"`
 auths.tip.dropbox=Crie uma nova aplicação em https://www.dropbox.com/developers/apps
 auths.tip.facebook=`Registe uma nova aplicação em https://developers.facebook.com/apps e adicione o produto "Facebook Login"`
@@ -3152,14 +3210,14 @@ auths.unable_to_initialize_openid=Não é possível inicializar o Fornecedor de
 auths.invalid_openIdConnectAutoDiscoveryURL=URL de descoberta automática inválido (tem que ser um URL válido começando com http:// ou https://)
 
 config.server_config=Configuração do servidor
-config.app_name=Título do sítio
+config.app_name=Título da instância
 config.app_ver=Versão do Forgejo
-config.app_url=URL base do Forgejo
+config.app_url=URL base
 config.custom_conf=Localização do ficheiro de configuração
 config.custom_file_root_path=Localização dos ficheiros personalizados
 config.domain=Domínio do servidor
 config.offline_mode=Modo local
-config.disable_router_log=Desabilitar registos do encaminhador
+config.disable_router_log=Desabilitar registos do encaminhador (router)
 config.run_user=Executa com este nome de utilizador
 config.run_mode=Modo de execução
 config.git_version=Versão do Git
@@ -3168,7 +3226,7 @@ config.repo_root_path=Localização dos repositórios
 config.lfs_root_path=Localização dos LFS
 config.log_file_root_path=Localização dos registos
 config.script_type=Tipo de script
-config.reverse_auth_user=Utilizador de autenticação reversa
+config.reverse_auth_user=Utilizador de autenticação do reverse proxy
 
 config.ssh_config=Configuração SSH
 config.ssh_enabled=Habilitado
@@ -3178,7 +3236,7 @@ config.ssh_port=Porto
 config.ssh_listen_port=Porto de escuta
 config.ssh_root_path=Localização base
 config.ssh_key_test_path=Localização do teste das chaves
-config.ssh_keygen_path=Localização do gerador de chaves ('ssh-keygen')
+config.ssh_keygen_path=Localização do gerador de chaves ("ssh-keygen")
 config.ssh_minimum_key_size_check=Verificação de tamanho mínimo da chave
 config.ssh_minimum_key_sizes=Tamanhos mínimos da chave
 
@@ -3204,11 +3262,11 @@ config.allow_only_external_registration=Permitir a inscrição somente por meio
 config.enable_openid_signup=Habilitar a auto-inscrição com OpenID
 config.enable_openid_signin=Habilitar início de sessão com OpenID
 config.show_registration_button=Mostrar botão de registo
-config.require_sign_in_view=Exigir sessão iniciada para visualizar páginas
+config.require_sign_in_view=Exigir sessão iniciada para visualizar o conteúdo
 config.mail_notify=Habilitar notificações por email
 config.enable_captcha=Habilitar o CAPTCHA
-config.active_code_lives=Duração do código que está em uso
-config.reset_password_code_lives=Prazo do código de recuperação da conta
+config.active_code_lives=Prazo do código de habilitação
+config.reset_password_code_lives=Prazo do código de recuperação
 config.default_keep_email_private=Esconder, por norma, os endereços de email
 config.default_allow_create_organization=Permitir, por norma, a criação de organizações
 config.enable_timetracking=Habilitar a contagem de tempo
@@ -3219,7 +3277,7 @@ config.default_visibility_organization=Visibilidade predefinida para as novas or
 config.default_enable_dependencies=Habilitar, por norma, dependências nas questões
 
 config.webhook_config=Configuração do automatismo web
-config.queue_length=Tamanho da fila
+config.queue_length=Comprimento da fila
 config.deliver_timeout=Prazo da entrega
 config.skip_tls_verify=Ignorar validação TLS
 
@@ -3228,7 +3286,7 @@ config.mailer_enabled=Habilitado
 config.mailer_enable_helo=Habilitar HELO
 config.mailer_name=Nome
 config.mailer_protocol=Protocolo
-config.mailer_smtp_addr=Endereço SMTP
+config.mailer_smtp_addr=Anfitrião SMTP
 config.mailer_smtp_port=Porto do SMTP
 config.mailer_user=Utilizador
 config.mailer_use_sendmail=Usar o sendmail
@@ -3251,11 +3309,11 @@ config.cache_interval=Intervalo de cache
 config.cache_conn=Conexão de cache
 config.cache_item_ttl=TTL do item de cache
 
-config.session_config=Configuração de sessão
+config.session_config=Configuração da sessão
 config.session_provider=Fornecedor da sessão
 config.provider_config=Configuração do fornecedor
 config.cookie_name=Nome do cookie
-config.gc_interval_time=Intervalo da recolha do lixo
+config.gc_interval_time=Intervalo de tempo entre recolhas do lixo
 config.session_life_time=Tempo de vida da sessão
 config.https_only=Apenas HTTPS
 config.cookie_life_time=Tempo de vida do cookie
@@ -3265,14 +3323,14 @@ config.picture_service=Serviço de imagem
 config.disable_gravatar=Desabilitar o Gravatar
 config.enable_federated_avatar=Habilitar avatares federados
 
-config.git_config=Configuração Git
+config.git_config=Configuração do Git
 config.git_disable_diff_highlight=Desabilitar o realce de sintaxe no diff
-config.git_max_diff_lines=Número máximo de linhas diff (por ficheiro)
-config.git_max_diff_line_characters=Número máximos de caracteres diff (por linha)
+config.git_max_diff_lines=Número máximo de linhas diff por ficheiro
+config.git_max_diff_line_characters=Número máximos de caracteres diff por linha
 config.git_max_diff_files=Número máximo de ficheiros diff a serem apresentados
 config.git_gc_args=Argumentos da recolha de lixo
 config.git_migrate_timeout=Prazo da migração
-config.git_mirror_timeout=Prazo para sincronização da réplica
+config.git_mirror_timeout=Prazo para a sincronização da réplica
 config.git_clone_timeout=Prazo da operação de clonagem
 config.git_pull_timeout=Prazo da operação de puxar
 config.git_gc_timeout=Prazo da operação de recolha de lixo
@@ -3316,7 +3374,7 @@ monitor.queue.numberworkers=N.º de trabalhadores
 monitor.queue.activeworkers=Trabalhadores operantes
 monitor.queue.maxnumberworkers=N.º máximo de trabalhadores
 monitor.queue.numberinqueue=N.º na fila
-monitor.queue.review_add=Rever / Adicionar trabalhadores
+monitor.queue.review_add=Rever / adicionar trabalhadores
 monitor.queue.settings.title=Configurações do agregado
 monitor.queue.settings.desc=Agregados crescem dinamicamente em resposta aos bloqueios da sua fila de trabalhadores.
 monitor.queue.settings.maxnumberworkers=N.º máximo de trabalhadores
@@ -3328,7 +3386,7 @@ monitor.queue.settings.remove_all_items=Remover tudo
 monitor.queue.settings.remove_all_items_done=Todos os itens da fila foram removidos.
 
 notices.system_notice_list=Notificações do sistema
-notices.view_detail_header=Ver os detalhes da notificação
+notices.view_detail_header=Detalhes da notificação
 notices.operations=Operações
 notices.select_all=Marcar todas
 notices.deselect_all=Desmarcar todas
@@ -3347,6 +3405,12 @@ self_check.database_collation_mismatch=Supor que a base de dados usa a colação
 self_check.database_collation_case_insensitive=A base de dados está a usar a colação %s, que é insensível à diferença entre maiúsculas e minúsculas. Embora o Gitea possa trabalhar com ela, pode haver alguns casos raros que não funcionem como esperado.
 self_check.database_inconsistent_collation_columns=A base de dados está a usar a colação %s, mas estas colunas estão a usar colações diferentes. Isso poderá causar alguns problemas inesperados.
 self_check.database_fix_mysql=Para utilizadores do MySQL/MariaDB, pode usar o comando "gitea doctor convert" para resolver os problemas de colação. Também pode resolver o problema com comandos SQL "ALTER ... COLLATE ..." aplicados manualmente.
+config_summary = Resumo
+auths.tips.gmail_settings = Configurações do Gmail:
+config_settings = Configurações
+auths.tip.gitlab_new = Registe uma nova aplicação em https://gitlab.com/-/profile/applications
+config.open_with_editor_app_help = Os editores da opção "Abrir com" do menu da clonagem. Se for deixado em branco, será usado o valor predefinido. Expanda para ver o que está predefinido.
+config.allow_dots_in_usernames = Permitir que os utilizadores usem pontos no seu nome de utilizador. Não altera as contas existentes.
 
 [action]
 create_repo=criou o repositório <a href="%s">%s</a>
@@ -3458,9 +3522,9 @@ dependencies=Dependências
 keywords=Palavras-chave
 details=Detalhes
 details.author=Autor(a)
-details.project_site=Página web do projecto
-details.repository_site=Página web do repositório
-details.documentation_site=Página web da documentação
+details.project_site=Sítio web do projecto
+details.repository_site=Sítio web do repositório
+details.documentation_site=Sítio web da documentação
 details.license=Licença
 assets=Recursos
 versions=Versões
@@ -3568,7 +3632,7 @@ owner.settings.cargo.rebuild.success=O índice do Cargo foi reconstruído com su
 owner.settings.cleanuprules.title=Gerir regras de limpeza
 owner.settings.cleanuprules.add=Adicionar regra de limpeza
 owner.settings.cleanuprules.edit=Editar regra de limpeza
-owner.settings.cleanuprules.none=Nenhuma regra de limpeza disponível. Por favor, consulte a documentação.
+owner.settings.cleanuprules.none=Ainda não há quaisquer regras de limpeza.
 owner.settings.cleanuprules.preview=Previsão da regra de limpeza
 owner.settings.cleanuprules.preview.overview=%d pacotes estão agendados para serem removidos.
 owner.settings.cleanuprules.preview.none=A regra de limpeza não corresponde a nenhum pacote.
@@ -3588,6 +3652,7 @@ owner.settings.cleanuprules.success.delete=A regra de limpeza foi eliminada.
 owner.settings.chef.title=Registo do Chef
 owner.settings.chef.keypair=Gerar par de chaves
 owner.settings.chef.keypair.description=É necessário um par de chaves para autenticar no registro Chef. Se você gerou um par de chaves antes, gerar um novo par de chaves irá descartar o par de chaves antigo.
+owner.settings.cargo.rebuild.no_index = Não foi possível reconstruir, não há um índice inicializado.
 
 [secrets]
 secrets=Segredos
@@ -3602,7 +3667,7 @@ deletion=Remover segredo
 deletion.description=Remover um segredo é permanente e não pode ser revertido. Continuar?
 deletion.success=O segredo foi removido.
 deletion.failed=Falhou ao remover o segredo.
-management=Gestão de segredos
+management=Gerir segredos
 
 [actions]
 actions=Operações
@@ -3619,7 +3684,7 @@ status.skipped=Ignorado
 status.blocked=Bloqueado
 
 runners=Executores
-runners.runner_manage_panel=Gestão de executores
+runners.runner_manage_panel=Gerir executores
 runners.new=Criar um novo executor
 runners.new_notice=Como iniciar um executor
 runners.status=Estado
@@ -3671,15 +3736,15 @@ runs.no_runs=A sequência de trabalho ainda não foi executada.
 runs.empty_commit_message=(mensagem de cometimento vazia)
 
 workflow.disable=Desabilitar sequência de trabalho
-workflow.disable_success=A sequência de trabalho '%s' foi desabilitada com sucesso.
+workflow.disable_success=A sequência de trabalho "%s" foi desabilitada com sucesso.
 workflow.enable=Habilitar sequência de trabalho
-workflow.enable_success=A sequência de trabalho '%s' foi habilitada com sucesso.
+workflow.enable_success=A sequência de trabalho "%s" foi habilitada com sucesso.
 workflow.disabled=A sequência de trabalho está desabilitada.
 
 need_approval_desc=É necessária aprovação para executar sequências de trabalho para a derivação do pedido de integração.
 
 variables=Variáveis
-variables.management=Gestão de variáveis
+variables.management=Gerir variáveis
 variables.creation=Adicionar variável
 variables.none=Ainda não há variáveis.
 variables.deletion=Remover variável
@@ -3695,6 +3760,8 @@ variables.update.failed=Falha ao editar a variável.
 variables.update.success=A variável foi editada.
 runs.no_workflows.documentation = Para mais informação sobre o Forgejo Action, veja <a target="_blank" rel="noopener noreferrer" href="%s">a documentação</a>.
 runs.no_workflows.quick_start = Não sabe como começar com o Forgejo Action? Veja o <a target="_blank" rel="noopener noreferrer" href="%s">guia de iniciação rápida</a>.
+runs.no_job_without_needs = A sequência de trabalho tem de conter pelo menos um trabalho sem dependências.
+runs.workflow = Sequência de trabalho
 
 [projects]
 type-1.display_name=Planeamento individual
@@ -3715,7 +3782,7 @@ submodule=Submódulo
 [search]
 org_kind = Pesquisar organizações...
 keyword_search_unavailable = Pesquisar por palavra-chave não está disponível, neste momento. Entre em contacto com o administrador.
-code_search_by_git_grep = Os resultados da pesquisa no código-fonte neste momento são fornecidos pelo "git grep". Esses resultados podem ser melhores se o administrador habilitar o indexador do repositório.
+code_search_by_git_grep = Os resultados da pesquisa no código-fonte neste momento são fornecidos pelo "git grep". Esses resultados podem ser melhores se o administrador habilitar o indexador de código-fonte.
 no_results = Não foram encontrados resultados correspondentes.
 package_kind = Pesquisar pacotes...
 runner_kind = Pesquisar executores...
@@ -3732,4 +3799,18 @@ repo_kind = Pesquisar repositórios...
 user_kind = Pesquisar utilizadores...
 team_kind = Pesquisar equipas...
 code_kind = Pesquisar código...
-code_search_unavailable = A pesquisa de código não está disponível, neste momento. Entre em contacto com o administrador.
\ No newline at end of file
+code_search_unavailable = A pesquisa de código não está disponível, neste momento. Entre em contacto com o administrador.
+
+[munits.data]
+kib = KiB
+mib = MiB
+gib = GiB
+tib = TiB
+pib = PiB
+eib = EiB
+b = B
+
+[markup]
+filepreview.lines = Linhas %[1]d até %[2]d em %[3]s
+filepreview.line = Linha %[1]d em %[2]s
+filepreview.truncated = A previsão foi truncada
\ No newline at end of file
diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini
index 6017980183..7ba9719a05 100644
--- a/options/locale/locale_ru-RU.ini
+++ b/options/locale/locale_ru-RU.ini
@@ -157,6 +157,7 @@ filter.is_archived = Архивированные
 filter.not_mirror = Не зеркала
 more_items = Больше элементов
 invalid_data = Неверные данные: %v
+copy_generic = Копировать в буфер обмена
 
 [aria]
 navbar=Панель навигации
@@ -1174,7 +1175,7 @@ migrate.cancel_migrating_title=Отменить перенос
 migrate.cancel_migrating_confirm=Вы хотите отменить эту миграцию?
 
 mirror_from=зеркало из
-forked_from=ответвлено от
+forked_from=ответвлён от
 generated_from=создано из
 fork_from_self=Вы не можете создать ответвление собственного репозитория.
 fork_guest_user=Войдите, чтобы создать ответвление репозитория.
@@ -1784,10 +1785,10 @@ pulls.manually_merged=Слито вручную
 pulls.merged_info_text=Ветку %s теперь можно удалить.
 pulls.is_closed=Запрос на слияние закрыт.
 pulls.title_wip_desc=`<a href="#">Добавьте <strong>%s</strong> в начало заголовка</a> для защиты от случайного досрочного принятия запроса на слияние`
-pulls.cannot_merge_work_in_progress=Этот запрос на слияние помечен как в процессе работы.
+pulls.cannot_merge_work_in_progress=Этот запрос слияния помечен как черновик.
 pulls.still_in_progress=Всё ещё в процессе?
-pulls.add_prefix=Добавить <strong>%s</strong> префикс
-pulls.remove_prefix=Удалить <strong>%s</strong> префикс
+pulls.add_prefix=Добавить префикс <strong>%s</strong>
+pulls.remove_prefix=Удалить префикс <strong>%s</strong>
 pulls.data_broken=Содержимое этого слияния нарушено из-за удаления информации об ответвлении.
 pulls.files_conflicted=Этот запрос на слияние имеет изменения конфликтующие с целевой веткой.
 pulls.is_checking=Продолжается проверка конфликтов. Повторите попытку позже.
@@ -1813,7 +1814,7 @@ pulls.wrong_commit_id=id коммита должен быть ид коммит
 
 pulls.no_merge_desc=Запрос на слияние не может быть принят, так как отключены все настройки слияния.
 pulls.no_merge_helper=Включите опции слияния в настройках репозитория или совершите слияние этого запроса вручную.
-pulls.no_merge_wip=Данный запрос на слияние не может быть принят, поскольку он помечен как находящийся в разработке.
+pulls.no_merge_wip=Этот запрос слияния не может быть принят, поскольку он помечен как черновик.
 pulls.no_merge_not_ready=Этот запрос не готов к слиянию, обратите внимания на отзывы и проверки.
 pulls.no_merge_access=У вас нет права для слияния данного запроса.
 pulls.merge_pull_request=Создать коммит слияния
@@ -2486,7 +2487,7 @@ diff.comment.add_single_comment=Добавить простой коммента
 diff.comment.add_review_comment=Добавить комментарий
 diff.comment.start_review=Начать рецензию
 diff.comment.reply=Ответ
-diff.review=Рецензия
+diff.review=Завершить рецензию
 diff.review.header=Отправить рецензию
 diff.review.placeholder=Рецензионный комментарий
 diff.review.comment=Комментировать
@@ -2735,6 +2736,8 @@ settings.sourcehut_builds.access_token_helper = Токен builds.sr.ht с ра
 settings.matrix.room_id_helper = ID комнаты можно получить в веб-клиенте Element: Настройки комнаты > Подробности > Внутренний ID комнаты. Пример: %s.
 settings.matrix.access_token_helper = Рекомендуется создать отдельный аккаунт. Токен доступа можно получить в веб-клиенте Element (в приватной вкладке или режиме инкогнито): Пользовательское меню (сверху слева) > Все настройки > Помощь и о программе > Токен доступа (под ссылкой Homeserver). Закройте вкладку/окно, не выходя из Element. Выход аннулирует токен.
 settings.mirror_settings.pushed_repository = Удалённый репозиторий
+release.hide_archive_links = Скрыть автоматически генерируемые архивы
+release.hide_archive_links_helper = Скрыть автоматически добавляемые архивы исходного кода для этого релиза. Например, если вы загружаете свои архивы.
 
 [graphs]
 
@@ -3403,6 +3406,7 @@ config_settings = Настройки
 auths.tips.gmail_settings = Настройки Gmail:
 auths.tip.gitlab_new = Создайте новое приложение в https://gitlab.com/-/profile/applications
 monitor.queue.review_add = Подробности / добавить рабочих
+auths.default_domain_name = Домен по умолчанию для адресов эл. почты
 
 
 [action]
@@ -3687,42 +3691,42 @@ status.cancelled=Отменено
 status.skipped=Пропущено
 status.blocked=Заблокировано
 
-runners=Раннеры
-runners.runner_manage_panel=Управление раннерами
-runners.new=Создать новый раннер
-runners.new_notice=Как запустить раннер
-runners.status=Статус
+runners=Исполнители
+runners.runner_manage_panel=Управление исполнителями
+runners.new=Создать новый исполнитель
+runners.new_notice=Как запустить исполнитель
+runners.status=Состояние
 runners.id=ID
 runners.name=Название
 runners.owner_type=Тип
 runners.description=Описание
 runners.labels=Метки
-runners.last_online=Был онлайн
-runners.runner_title=Раннер
-runners.task_list=Недавние задания на раннере
+runners.last_online=Был в сети
+runners.runner_title=Исполнитель
+runners.task_list=Недавние задания исполнителя
 runners.task_list.no_tasks=Задания пока нет.
 runners.task_list.run=Запуск
-runners.task_list.status=Статус
+runners.task_list.status=Состояние
 runners.task_list.repository=Репозиторий
-runners.task_list.commit=коммит
+runners.task_list.commit=Коммит
 runners.task_list.done_at=Время завершения
-runners.edit_runner=Изменить раннер
+runners.edit_runner=Изменить исполнитель
 runners.update_runner=Обновить изменения
-runners.update_runner_success=Раннер успешно обновлён
-runners.update_runner_failed=Не удалось обновить раннер
-runners.delete_runner=Удалить этот раннер
-runners.delete_runner_success=Раннер успешно удалён
-runners.delete_runner_failed=Не удалось удалить раннер
-runners.delete_runner_header=Подтвердите удаление раннера
-runners.delete_runner_notice=Если на этом раннере выполняется задание, оно будет завершено и помечено как неудачное. Это может нарушить рабочий поток сборки.
-runners.none=Нет доступных раннеров
+runners.update_runner_success=Исполнитель успешно обновлён
+runners.update_runner_failed=Не удалось обновить исполнитель
+runners.delete_runner=Удалить этот исполнитель
+runners.delete_runner_success=Исполнитель успешно удалён
+runners.delete_runner_failed=Не удалось удалить исполнитель
+runners.delete_runner_header=Подтвердите удаление исполнителя
+runners.delete_runner_notice=Если на этом исполнителе выполняется задание, оно будет завершено и помечено как неудачное. Это может нарушить рабочий поток при сборке.
+runners.none=Нет доступных исполнителей
 runners.status.unspecified=Неизвестно
 runners.status.idle=Простаивает
 runners.status.active=Активный
 runners.status.offline=Недоступен
 runners.version=Версия
-runners.reset_registration_token=Сброс регистрационного токена
-runners.reset_registration_token_success=Токен регистрации раннера успешно сброшен
+runners.reset_registration_token=Сброс токена регистрации
+runners.reset_registration_token_success=Токен регистрации исполнителя успешно сброшен
 
 runs.all_workflows=Все рабочие потоки
 runs.commit=коммит
@@ -3764,7 +3768,7 @@ runs.no_workflows.quick_start = Не знаете, как начать испо
 runs.no_workflows.documentation = Чтобы узнать больше о Действиях Forgejo, читайте <a target="_blank" rel="noopener noreferrer" href="%s">документацию</a>.
 runs.workflow = Рабочий поток
 runs.status_no_select = Любой статус
-runs.no_matching_online_runner_helper = Нет работающего раннера с меткой: %s
+runs.no_matching_online_runner_helper = Нет работающего исполнителя с меткой: %s
 runs.no_job_without_needs = Рабочий процесс должен содержать хотя бы одну задачу без зависимостей.
 
 [projects]
@@ -3812,7 +3816,7 @@ no_results = По запросу ничего не найдено.
 keyword_search_unavailable = Поиск по ключевым словам недоступен. Уточните подробности у администратора.
 match_tooltip = Включать только результаты, точно соответствующие запросу
 code_search_unavailable = Поиск по коду сейчас недоступен. Уточните подробности у администратора.
-runner_kind = Поиск раннеров...
+runner_kind = Поиск исполнителей...
 code_search_by_git_grep = Эти результаты получены через «git grep». Результатов может быть больше, если администратор сервера включит индексатор кода.
 
 
diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini
index 153e40b113..c2c5385421 100644
--- a/options/locale/locale_tr-TR.ini
+++ b/options/locale/locale_tr-TR.ini
@@ -141,6 +141,21 @@ confirm_delete_selected=Tüm seçili öğeleri gerçekten silmek istiyor musunuz
 
 name=İsim
 value=Değer
+copy_generic = Kopyala
+filter = Filtrele
+filter.not_archived = Arşivlenmemiş
+filter.clear = Filtreleri Temizle
+filter.is_archived = Arşivlenmiş
+filter.is_mirror = Yansılaştırılmış
+filter.is_fork = Çatallanmış
+filter.not_fork = Çatallanmamış
+filter.not_mirror = Yansılanmamış
+filter.is_template = Şablon
+filter.not_template = Şablon değil
+filter.public = Herkese açık
+filter.private = Gizli
+more_items = Daha fazla öğe
+invalid_data = Geçersiz veri: %v
 
 [aria]
 navbar=Gezinti Çubuğu
diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index f1a4d28491..dab4287f06 100644
--- a/options/locale/locale_zh-CN.ini
+++ b/options/locale/locale_zh-CN.ini
@@ -90,7 +90,7 @@ add=添加
 add_all=添加所有
 remove=移除
 remove_all=移除所有
-remove_label_str=`删除标签 "%s"`
+remove_label_str=删除标签 "%s"
 edit=编辑
 view=查看
 
@@ -154,9 +154,10 @@ filter.is_template = 模板
 filter.not_template = 非模板
 filter.public = 公开
 filter.private = 私有
-toggle_menu = 菜单
-invalid_data = 无效数据: %v
+toggle_menu = 切换菜单
+invalid_data = 无效数据:%v
 more_items = 显示更多
+copy_generic = 复制到剪贴板
 
 [aria]
 navbar=导航栏
@@ -166,7 +167,7 @@ footer.links=链接
 
 [heatmap]
 number_of_contributions_in_the_last_12_months=一年内 %s 次贡献
-contributions_zero=目前还没有贡献。
+contributions_zero=目前还没有贡献
 less=更少的
 more=更多的
 contributions_format = {year}{month}{day} 当日有 {contributions}
@@ -217,7 +218,7 @@ license_desc=所有的代码都开源在 <a target="_blank" rel="noopener norefe
 install=安装页面
 title=初始配置
 docker_helper=如果您正在使用 Docker 容器运行 Forgejo,请务必先仔细阅读 <a target="_blank" rel="noopener noreferrer" href="%s">官方文档</a> 后再对本页面进行填写。
-require_db_desc=Forgejo 需要使用 MySQL、PostgreSQL、SQLite3 或 TiDB (MySQL协议) 等数据库
+require_db_desc=Forgejo 需要使用 MySQL、PostgreSQL、SQLite3 或 TiDB(MySQL 协议)等数据库。
 db_title=数据库设置
 db_type=数据库类型
 host=数据库主机
@@ -247,7 +248,7 @@ app_name=站点名称
 app_name_helper=您可以在此输入您公司的名称。
 repo_path=仓库根目录
 repo_path_helper=所有远程 Git 仓库将保存到此目录。
-lfs_path=LFS根目录
+lfs_path=LFS 根目录
 lfs_path_helper=存储为Git LFS的文件将被存储在此目录。留空禁用LFS
 run_user=以用户运行
 run_user_helper=输入 Forgejo 运行的操作系统用户名。请注意,此用户必须具有对仓库根路径的访问权限。
@@ -631,6 +632,12 @@ admin_cannot_delete_self=当您是管理员时,您不能删除自己。请先
 unsupported_login_type = 该账号使用的登录方式不支持删除此账户。
 unset_password = 当前登录用户尚未设置密码。
 required_prefix = 输入必须以“%s”开头
+FullName = 全名
+Website = 网站
+Location = 地区
+To = 分支名
+AccessToken = 访问令牌
+Description = 描述
 
 [user]
 change_avatar=修改头像
@@ -3817,4 +3824,5 @@ mib = MiB
 
 [markup]
 filepreview.line = %[2]s 中的第 %[1]d 行
-filepreview.lines = %[3]s 中的第 %[1]d 到 %[2]d 行
\ No newline at end of file
+filepreview.lines = %[3]s 中的第 %[1]d 到 %[2]d 行
+filepreview.truncated = 预览已被截断
\ No newline at end of file
diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini
index 9bca942df1..1d914e6957 100644
--- a/options/locale/locale_zh-HK.ini
+++ b/options/locale/locale_zh-HK.ini
@@ -61,6 +61,32 @@ concept_code_repository=儲存庫
 
 
 name=組織名稱
+sign_in_with_provider = 使用 %s 登入
+sign_up = 登記
+email = 電子信箱
+access_token = 訪問令牌
+powered_by = 由 %s 提供
+create_new = 建立…
+user_profile_and_more = 個人資料同埋設定…
+signed_in_as = 已經登入
+toc = 目錄
+licenses = 軟件授權
+return_to_gitea = 返來 Forgejo
+username = 用戶名
+captcha = 驗證碼
+toggle_menu = 切換選單
+webauthn_insert_key = 插入安全密鑰
+twofa = 兩步驟驗證
+webauthn_sign_in = 撳下安全密鑰嘅掣。如果安全密鑰冇掣,請再插入。
+webauthn_press_button = 請撳下安全密鑰嘅掣…
+more_items = 多啲嘢
+webauthn_use_twofa = 用手機嘅兩步驟驗證
+webauthn_error = 唔可以讀取安全密鑰。
+webauthn_unsupported_browser = 你嘅瀏覽器唔支援 WebAuthn。
+webauthn_error_unknown = 發生未知嘅錯誤,請再試下。
+webauthn_error_unable_to_process = 伺服器唔可以執行你嘅請求。
+logo = 標識
+enable_javascript = 本網站需要 JavaScript。
 
 [aria]
 
diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini
index 96d5d0b29c..e7334dbd16 100644
--- a/options/locale/locale_zh-TW.ini
+++ b/options/locale/locale_zh-TW.ini
@@ -28,7 +28,7 @@ return_to_gitea=返回 Forgejo
 username=帳號
 email=電子信箱
 password=密碼
-access_token=訪問令牌(Access Token)
+access_token=訪問符記
 re_type=確認密碼
 captcha=驗證碼
 twofa=兩步驟驗證
@@ -138,13 +138,13 @@ view = 查看
 filter = 篩選
 filter.clear = 清除篩選條件
 filter.is_archived = 已歸檔
-filter.not_archived = 未歸檔
+filter.not_archived = 未封存
 filter.is_fork = 已派生
-filter.not_fork = 未派生
+filter.not_fork = 不是分岔
 filter.is_mirror = 已鏡像
-filter.not_mirror = 未鏡像
+filter.not_mirror = 不是鏡像
 filter.is_template = 模板
-filter.not_template = 非模版
+filter.not_template = 不是範本
 filter.public = 公開
 filter.private = 私有
 artifacts = 製品
@@ -155,6 +155,9 @@ show_full_screen = 全屏顯示
 download_logs = 下載日誌
 confirm_delete_selected = 確認刪除所有選中專案?
 confirm_delete_artifact = 您確定要刪除製品“%s”嗎?
+more_items = 顯示更多
+invalid_data = 無效數據:%v
+copy_generic = 複製到剪貼簿
 
 [aria]
 navbar=導航列
@@ -167,6 +170,9 @@ number_of_contributions_in_the_last_12_months=過去十二個月內有 %s 個貢
 contributions_zero=沒有貢獻
 less=少
 more=多
+contributions_format = {year} {month} {day} 有 {contributions}
+contributions_one = 貢獻
+contributions_few = 項貢獻
 
 [editor]
 buttons.heading.tooltip=新增標題
@@ -209,7 +215,7 @@ license_desc=取得 <a target="_blank" rel="noopener noreferrer" href="https://f
 
 [install]
 install=安裝頁面
-title=初始組態
+title=初始化設定
 docker_helper=如果您在 Docker 中執行 Forgejo,請先閱讀<a target="_blank" rel="noopener noreferrer" href="%s">安裝指南</a>再來調整設定。
 require_db_desc=Forgejo 需要 MySQL、PostgreSQL、SQLite3、TiDB (MySQL 協定) 等其中一項。
 db_title=資料庫設定
@@ -237,27 +243,27 @@ err_admin_name_pattern_not_allowed=無效的管理員帳號,該帳號符合保
 err_admin_name_is_invalid=無效的管理員帳號
 
 general_title=一般設定
-app_name=網站標題
+app_name=站點標題
 app_name_helper=您可以在此輸入您的公司名稱。
 repo_path=儲存庫的根目錄
 repo_path_helper=所有遠端 Git 儲存庫會儲存到此目錄。
 lfs_path=Git LFS 根目錄
 lfs_path_helper=以 Git LFS 儲存檔案時會被儲存在此目錄中。請留空以停用 LFS 功能。
-run_user=以使用者名稱執行
+run_user=以…使用者名稱執行
 domain=伺服器域名
 domain_helper=伺服器的域名或主機位置。
 ssh_port=SSH 伺服器埠
-ssh_port_helper=SSH 伺服器使用的埠號,留空以停用此設定。
-http_port=Forgejo HTTP 埠
-http_port_helper=Forgejo 的網頁伺服器要接聽的埠號。
-app_url=Forgejo 基本 URL
+ssh_port_helper=SSH 伺服器使用的埠號,留空以停用 SSH 伺服器。
+http_port=HTTP 埠
+http_port_helper=Forgejo 的網頁伺服器所使用的埠號。
+app_url=基本 URL
 app_url_helper=用於 HTTP(S) Clone 和電子郵件通知的基本網址。
 log_root_path=日誌路徑
 log_root_path_helper=日誌檔將寫入此目錄。
 
 optional_title=可選設定
 email_title=電子郵件設定
-smtp_addr=SMTP 主機
+smtp_addr=SMTP 主機地址
 smtp_port=SMTP 連接埠
 smtp_from=電子郵件寄件者
 smtp_from_helper=Forgejo 將會使用的電子信箱,直接輸入電子信箱或使用「"名稱" <email@example.com>」的格式。
@@ -270,14 +276,14 @@ offline_mode=啟用本地模式
 offline_mode_popup=停用其他服務並在本地提供所有資源。
 disable_gravatar=停用 Gravatar
 disable_gravatar_popup=停用 Gravatar 和其他大頭貼服務。除非使用者在本地上傳大頭貼,否則將使用預設的大頭貼。
-federated_avatar_lookup=啟用 Federated Avatars
+federated_avatar_lookup=啟用 Federated 大頭貼
 federated_avatar_lookup_popup=使用 Libravatar 以啟用 Federated Avatar 查詢服務
-disable_registration=關閉註冊功能
+disable_registration=禁用自助註冊
 disable_registration_popup=關閉註冊功能,只有管理員可以新增帳戶。
 allow_only_external_registration_popup=只允許從外部服務註冊
 openid_signin=啟用 OpenID 登入
 openid_signin_popup=啟用 OpenID 登入
-openid_signup=啟用 OpenID 註冊
+openid_signup=啟用 OpenID 自助註冊
 openid_signup_popup=啟用基於 OpenID 的註冊
 enable_captcha=在註冊時啟用驗證碼
 enable_captcha_popup=要求在用戶註冊時輸入驗證碼
@@ -285,13 +291,13 @@ require_sign_in_view=需要登入才能瀏覽頁面
 require_sign_in_view_popup=限制已登入的使用者才能存取頁面。訪客只會看到登入和註冊頁面。
 admin_setting_desc=建立管理員帳戶是選用的。 第一個註冊的使用者將自動成為管理員。
 admin_title=管理員帳戶設定
-admin_name=管理員帳號
+admin_name=管理員使用者名稱
 admin_password=管理員密碼
 confirm_password=確認密碼
 admin_email=電子信箱
 install_btn_confirm=安裝 Forgejo
-test_git_failed=無法識別「git」命令:%v
-sqlite3_not_available=您目前的版本不支援 SQLite3,請從 %s 下載官方的預先編譯版本 (不是 gobuild 版本)。
+test_git_failed=無法識別「git」指令:%v
+sqlite3_not_available=這個 Forgejo 版本不支援 SQLite3,請從 %s 下載官方的預先編譯版本 (不是 「gobuild」 版本)。
 invalid_db_setting=資料庫設定不正確: %v
 invalid_db_table=資料庫的資料表「%s」無效: %v
 invalid_repo_path=儲存庫根目錄設定不正確:%v
@@ -313,8 +319,14 @@ no_reply_address_helper=作為隱藏電子信箱使用者的域名。例如,
 password_algorithm=密碼雜湊演算法
 invalid_password_algorithm=無效的密碼雜湊演算法
 password_algorithm_helper=設定密碼雜湊演算法。演算法有不同的需求與強度。argon2 演算法雖然較安全但會使用大量記憶體,可能不適用於小型系統。
-enable_update_checker=啟用更新檢查器
+enable_update_checker=啟用更新檢查
 run_user_helper = 輸入 Forgejo 執行的作業系統使用者名稱。請注意,此使用者必須具有對儲存庫根路徑的訪問許可權。
+env_config_keys_prompt = 以下的環境變數也會被套用於您的設定檔:
+env_config_keys = 環境設定
+smtp_from_invalid = 電子郵件寄件者地址無效
+config_location_hint = 這些設定將被儲存在:
+allow_dots_in_usernames = 允許使用者在使用者名稱中使用英文句點。不影響現有帳戶。
+enable_update_checker_helper_forgejo = 透過檢查 release.forgejo.org 的 DNS TXT 記錄來定期檢查新的 Forgejo 版本。
 
 [home]
 uname_holder=帳號或電子信箱
@@ -323,7 +335,7 @@ switch_dashboard_context=切換資訊主頁帳戶
 my_repos=儲存庫
 show_more_repos=顯示更多儲存庫...
 collaborative_repos=參與協作的儲存庫
-my_orgs=我的組織
+my_orgs=組織
 my_mirrors=我的鏡像
 view_home=訪問 %s
 search_repos=搜尋儲存庫...
@@ -363,6 +375,10 @@ code_search_results=「%s」的搜尋結果
 code_last_indexed_at=最後索引 %s
 relevant_repositories_tooltip=已隱藏缺少主題、圖示、說明、Fork 的儲存庫。
 relevant_repositories=只顯示相關的儲存庫,<a href="%s">顯示未篩選的結果</a>。
+stars_few = %d 個星星
+stars_one = %d 個星星
+forks_one = %d 個 fork
+forks_few = %d 個 fork
 
 [auth]
 create_new_account=註冊帳戶
@@ -426,11 +442,25 @@ authorize_title=授權「%s」存取您的帳戶?
 authorization_failed=授權失效
 sspi_auth_failed=SSPI 認證失敗
 password_pwned_err=無法完成對 HaveIBeenPwned 的請求。
+tab_signin = 登入
+change_unconfirmed_email_summary = 更改接收帳號啟用信的信箱地址。
+change_unconfirmed_email = 如果您在註冊帳號時寫錯了信箱地址,您可以在下面更改它。您會在這個新地址收到一封確認信。
+change_unconfirmed_email_error = 無法更改信箱地址:%v
+tab_signup = 註冊
+last_admin = 您無法刪除最後一個管理員。必須至少有一個管理員。
+prohibit_login_desc = 您的帳號被禁止登入,請連絡網站管理員。
+sign_up_successful = 已成功建立帳號。歡迎!
+invalid_code_forgot_password = 您的驗證碼無效或是已過期。點擊<a href="%s">這裡</a>來開始一個新的 session。
+reset_password_wrong_user = 您以 %s 登入,但是帳號復原連結是給 %s 的
+password_pwned = 該密碼出現在先前資料洩露的<a target="_blank" rel="noopener noreferrer" href="https://haveibeenpwned.com/Passwords">被盜密碼清單</a>中。請用一個不同的密碼再試一次,並考慮在其他地方也更換此密碼。
+authorization_failed_desc = 偵測到無效請求,授權失敗。請連絡您嘗試授權的應用的維護者。
+openid_signin_desc = 輸入您的 OpenID URI。例如:alice.openid.example.org 或是 https://openid.example.org/alice。
+remember_me.compromised = 此登入 token 已經無效,這可能是因為您的帳號被盜用了。請檢查您的帳號是否有異常活動。
 
 [mail]
 view_it_on=在 %s 上查看
 reply=或是直接回覆此電子郵件
-link_not_working_do_paste=無法開啟?請複製超連結到瀏覽器貼上。
+link_not_working_do_paste=無法開啟?試試複製超連結到瀏覽器貼上。
 hi_user_x=<b>%s</b> 您好,
 
 activate_account=請啟用您的帳戶
@@ -445,11 +475,11 @@ register_notify=歡迎來到 Forgejo
 register_notify.title=%[1]s,歡迎來到 %[2]s
 register_notify.text_1=這是您在 %s 的註冊確認信!
 register_notify.text_2=您現在可以用帳號 %s 登入。
-register_notify.text_3=如果這是由管理員為您建立的帳戶,請先<a href="%s">設定您的密碼</a>。
+register_notify.text_3=如果其他人為您建立了此帳號,請先<a href="%s">設定您的密碼</a>。
 
 reset_password=救援您的帳戶
-reset_password.title=%s,您已請求帳戶救援
-reset_password.text=請在 <b>%s</b>內點擊下列連結以救援您的帳戶:
+reset_password.title=%s,我們收到了您的帳號恢復請求
+reset_password.text=如果是您做出的請求,請在 <b>%s</b>內點擊下列連結以救援您的帳戶:
 
 register_success=註冊成功
 
@@ -491,6 +521,10 @@ team_invite.subject=%[1]s 邀請您加入組織 %[2]s
 team_invite.text_1=%[1]s 邀請您加入組織 %[3]s 中的 %[2]s 團隊
 team_invite.text_2=請點擊下方連結加入團隊:
 team_invite.text_3=備註: 這是寄給 %[1]s 的邀請。若您未預期收到此邀請,請忽略此郵件。
+activate_email.title = %s,請驗證你的信箱地址
+admin.new_user.subject = 新使用者 %s 剛剛完成註冊
+admin.new_user.user_info = 使用者資訊
+admin.new_user.text = 請點擊<a href="%s">這裡</a>以在管理員控制台管理此使用者。
 
 [modal]
 yes=是
@@ -523,8 +557,8 @@ SSPISeparatorReplacement=分隔符
 SSPIDefaultLanguage=預設語言
 
 require_error=` 不能為空。`
-alpha_dash_error=`應該只包含英文字母、數字、破折號 ("-")、和底線 ("_") 字元。`
-alpha_dash_dot_error=`應該只包含英文字母、數字、破折號 ("-")、下底線("_")和小數點 (".") 字元。`
+alpha_dash_error=`應該只包含英文字母、數字、破折號("-")和底線("_") 字元。`
+alpha_dash_dot_error=`應該只包含英文字母、數字、破折號 ("-")、下底線("_")和小數點(".")字元。`
 git_ref_name_error=` 必須是格式正確的 Git 參考名稱。`
 size_error=` 長度必須為 %s。`
 min_size_error=` 長度最小為 %s 個字元。`
@@ -569,7 +603,7 @@ enterred_invalid_owner_name=新的擁有者名稱無效。
 enterred_invalid_password=您輸入的密碼不正確。
 user_not_exist=該用戶名不存在
 team_not_exist=團隊不存在
-last_org_owner=你不能從「Owners」團隊中刪除最後一個使用者。每個組織中至少要有一個擁有者。
+last_org_owner=你不能從「所有者」團隊中刪除最後一個使用者。每個組織中至少要有一個擁有者。
 cannot_add_org_to_team=組織不能被新增為團隊成員。
 duplicate_invite_to_team=該使用者已經被邀請為團隊成員。
 organization_leave_success=您已成功離開組織 %s。
@@ -578,7 +612,7 @@ invalid_ssh_key=無法驗證您的 SSH 密鑰:%s
 invalid_gpg_key=無法驗證您的 GPG 密鑰:%s
 invalid_ssh_principal=無效的主體: %s
 must_use_public_key=您提供的金鑰是私有金鑰,請勿上傳您的私有金鑰到任何地方,請使用您的公鑰。
-unable_verify_ssh_key=無法驗證 SSH 金鑰,請再次檢查以避免錯誤。
+unable_verify_ssh_key=無法驗證 SSH 金鑰,請再次檢查是否有錯誤。
 auth_failed=授權認證失敗:%v
 
 still_own_repo=您的帳戶擁有一個以上的儲存庫,請先刪除或轉移它們。
@@ -588,6 +622,20 @@ org_still_own_repo=此組織仍然擁有一個以上的儲存庫,請先刪除
 org_still_own_packages=此組織仍然擁有一個以上的套件,請先刪除它們。
 
 target_branch_not_exist=目標分支不存在
+unset_password = 此使用者尚未設置密碼。
+unsupported_login_type = 該帳號的登入方式使它無法被刪除。
+To = 分支名稱
+FullName = 全名
+Description = 說明
+Pronouns = 代名詞
+Biography = 個人簡介
+Website = 網站
+Location = 地區
+AccessToken = 存取令牌
+username_has_not_been_changed = 帳號名稱未被更改
+admin_cannot_delete_self = 當您是管理員時,您不能刪除自己。請先移除您的管理員權限。
+username_error_no_dots = ` 只能包含英數字符("0-9","a-z","A-Z"),破折號("-")和底線("_")。只能以英數字元開頭或結尾,連續的非英數字元也不被允許。`
+required_prefix = 輸入文字必須以「%s」開頭
 
 
 [user]
@@ -595,7 +643,7 @@ change_avatar=更改大頭貼...
 repositories=儲存庫
 activity=公開動態
 followers_few=%d 追蹤者
-starred=已加星號
+starred=已加星號的儲存庫
 watched=關注的儲存庫
 code=程式碼
 projects=專案
@@ -610,7 +658,15 @@ email_visibility.private=只有您和系統管理員可以看到您的電子信
 
 form.name_reserved=「%s」是保留的帳號。
 form.name_pattern_not_allowed=帳號不可包含字元「%s」。
-form.name_chars_not_allowed=帳號「%s」包含無效字元。
+form.name_chars_not_allowed=使用者名稱「%s」包含無效字元。
+joined_on = 在 %s 註冊
+show_on_map = 在地圖上顯示這個地點
+settings = 使用者設定
+block_user = 封鎖使用者
+block_user.detail_1 = 該使用者已停止追踪您。
+block_user.detail_2 = 這個使用者無法對您的儲存庫、您提出的問題或發表的留言做出任何操作。
+followers_one = %d 個追踪者
+following_one = 追踪 %d 個人
 
 [settings]
 profile=個人資料
@@ -625,10 +681,10 @@ applications=應用程式
 orgs=管理組織
 repos=儲存庫
 delete=刪除帳戶
-twofa=兩步驟驗證
+twofa=兩步驟驗證 (TOTP)
 account_link=已連結帳號
 organization=組織
-webauthn=安全金鑰
+webauthn=兩步驟驗證(安全金鑰)
 
 public_profile=公開的個人資料
 password_username_disabled=非本地使用者不允許更改他們的帳號。詳細資訊請聯絡您的系統管理員。
@@ -666,7 +722,7 @@ privacy=隱私
 keep_activity_private_popup=讓動態只有你和管理員看得到
 
 lookup_avatar_by_mail=以電子信箱查詢大頭貼
-federated_avatar_lookup=Federated Avatar 查詢
+federated_avatar_lookup=Federated 大頭貼查詢
 enable_custom_avatar=使用自訂大頭貼
 choose_new_avatar=選擇新的大頭貼
 update_avatar=更新大頭貼
@@ -686,7 +742,7 @@ password_change_disabled=非本地帳戶無法透過 Forgejo 的網頁介面更
 emails=電子信箱
 manage_emails=管理電子信箱
 manage_themes=選擇預設佈景主題
-manage_openid=管理 OpenID 位址
+manage_openid=管理 OpenID 地址
 theme_desc=這將是您在整個網站上的預設佈景主題。
 primary=主要
 activated=已啟用
@@ -716,16 +772,16 @@ openid_desc=OpenID 讓你可以授權認證給外部服務。
 manage_ssh_keys=管理 SSH 金鑰
 manage_ssh_principals=管理 SSH 認證主體
 manage_gpg_keys=管理 GPG 金鑰
-add_key=增加金鑰
-ssh_desc=這些 SSH 公鑰已關聯至你的帳戶。持有相對應的私鑰將擁有完全控制你的儲存庫的權限。
+add_key=新增金鑰
+ssh_desc=這些 SSH 公鑰已連結至您的帳戶。持有相對應的私鑰將擁有完全控制你的儲存庫的權限。可以使用已驗證的 SSH 金鑰校驗 SSH 簽署的提交。
 principal_desc=這些 SSH 認證主體已關聯到您的帳戶並擁有完全存取您的儲存庫的權限。
-gpg_desc=這些 GPG 公鑰已經關聯到你的帳戶。請妥善保管你的私鑰因為他們將被用於認證提交。
+gpg_desc=這些 GPG 公鑰已經連結到您的帳戶,並被用於校驗您的提交。因為它們能用您的身分簽署提交,請妥善保管您的私鑰。
 ssh_helper=<strong>需要協助嗎?</strong>建議可看看 GitHub 的文件以<a href="%s">建立您的 SSH 金鑰</a>或解決您使用 SSH 時碰到的<a href="%s">常見問題</a>。
 gpg_helper=<strong>需要協助嗎?</strong>建議可看看 GitHub 的 <a href="%s">about GPG</a> 文件。
-add_new_key=增加 SSH 金鑰
+add_new_key=新增 SSH 金鑰
 add_new_gpg_key=新增 GPG 金鑰
-key_content_ssh_placeholder=以下列字段開頭:'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'
-key_content_gpg_placeholder=以 '-----BEGIN PGP PUBLIC KEY BLOCK-----' 開頭
+key_content_ssh_placeholder=以 「ssh-ed25519」、「ssh-rsa」、「ecdsa-sha2-nistp256」、「ecdsa-sha2-nistp384」、「ecdsa-sha2-nistp521」、「sk-ecdsa-sha2-nistp256@openssh.com」、或 「sk-ssh-ed25519@openssh.com」 開頭
+key_content_gpg_placeholder=以 「-----BEGIN PGP PUBLIC KEY BLOCK-----」 開頭
 add_new_principal=新增主體
 ssh_key_been_used=此 SSH 金鑰早已加入本伺服器。
 ssh_key_name_used=已有相同名稱的 SSH 金鑰存在於您的帳戶。
@@ -743,7 +799,7 @@ gpg_token=Token
 gpg_token_help=您可以使用以下方法產生簽署:
 gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
 gpg_token_signature=Armored GPG 簽署
-key_signature_gpg_placeholder=以「-----BEGIN PGP SIGNATURE-----」開頭
+key_signature_gpg_placeholder=以 「-----BEGIN PGP SIGNATURE-----」 開頭
 verify_gpg_key_success=已驗證 GPG 金鑰「%s」。
 ssh_key_verified=已驗證的金鑰
 ssh_key_verified_long=金鑰已被 Token 驗證且可用來驗證符合此使用者已啟用的電子信箱的提交。
@@ -801,7 +857,7 @@ access_token_deletion_cancel_action=取消
 access_token_deletion_confirm_action=刪除
 access_token_deletion_desc=刪除 Token 後,使用此 Token 的應用程式將無法再存取您的帳戶,此動作不可還原。是否繼續?
 delete_token_success=已刪除 Token。使用此 Token 的應用程式無法再存取您的帳戶。
-permission_no_access=沒有權限
+permission_no_access=沒有存取權
 permission_read=讀取
 
 manage_oauth2_applications=管理 OAuth2 應用程式
@@ -879,6 +935,37 @@ visibility.public=公開
 visibility.public_tooltip=所有人都可以看到
 visibility.limited=受限
 visibility.private=私人
+blocked_users_none = 您沒有封鎖任何使用者 。
+blocked_users = 封鎖的使用者
+hints = 提示
+update_hints = 更新提示
+update_hints_success = 提示已被更改。
+added_on = 於 %s 新增
+biography_placeholder = 和我們介紹一下您自己吧!(您可以使用 Markdown)
+location_placeholder = 和其他人分享您的地理位置
+profile_desc = 管理其他人如何看到您的個人資料。通知、密碼復原和網頁上的 Git 操作會使用您的主要電子信箱。
+hidden_comment_types.ref_tooltip = 註記哪些問題/提交/… 提及了此問題
+keep_activity_private = 隱藏個人頁面中的活動資料
+uploaded_avatar_is_too_big = 上傳檔案的大小 (%d KiB)超過了上限 (%d KiB )。
+select_permissions = 選擇權限
+permission_write = 讀寫
+permissions_list = 權限:
+add_email_confirmation_sent = 我們已發送一封確認信至 「%s」。請檢查您的信箱並在 %s 內確認註冊。
+repo_and_org_access = 儲存庫和組織存取權
+permissions_public_only = 僅公開
+permissions_access_all = 全部(公開、私有和受限)
+at_least_one_permission = 您必須至少選擇一個權限才能建立 token
+can_not_add_email_activations_pending = 已有一個待處理的啟用請求,如果您想要新增電子信箱,請稍等幾分鐘。
+uid = UID
+change_password = 更改密碼
+valid_until_date = 至 %s 有效
+social_desc = 這些社群帳號可以被用來登入您的帳號。請確保您認得每一個。
+unbind_success = 已成功移除該社群帳號。
+create_oauth2_application_success = 您已成功建立一個新的 OAuth2 應用程式。
+change_username_prompt = 註:更新您的使用者名稱將改變您的帳號 URL。
+change_username_redirect_prompt = 舊的使用者名稱在其他使用者認領之前將會轉址到新的使用者名稱。
+visibility.limited_tooltip = 只有已登入的使用者能看見
+visibility.private_tooltip = 只有您加入的組織之成員能看見
 
 [repo]
 owner=擁有者
@@ -893,7 +980,7 @@ template_description=儲存庫範本讓使用者可新增相同目錄結構、
 visibility=瀏覽權限
 visibility_description=只有組織擁有者或有權限的組織成員才能看到。
 visibility_helper_forced=您的網站管理員強制新的存儲庫必需設定為私有。
-visibility_fork_helper=(修改本值將會影響所有 fork 儲存庫)
+visibility_fork_helper=(修改本值將會影響所有 fork 儲存庫的能見度)
 clone_helper=需要有關 Clone 的協助嗎?查看<a target="_blank" rel="noopener noreferrer" href="%s">幫助</a> 。
 fork_repo=Fork 儲存庫
 fork_from=Fork 自
@@ -931,14 +1018,14 @@ default_branch=預設分支
 default_branch_helper=預設分支是合併請求和提交程式碼的基礎分支。
 mirror_prune=裁減
 mirror_prune_desc=刪除過時的遠端追蹤參考
-mirror_interval=鏡像間隔 (有效時間單位為 'h'、'm'、's'),設為 0 以停用定期同步。(最小間隔: %s)
+mirror_interval=鏡像週期(有效時間單位為「h」、「m」、「s」),設為 0 以停用定期同步。(最小值為:%s)
 mirror_interval_invalid=鏡像週期無效
 mirror_sync_on_commit=推送提交後進行同步
 mirror_address=從 URL Clone
 mirror_address_desc=在授權資訊中填入必要的資料。
 mirror_lfs=Large File Storage (LFS)
 mirror_lfs_desc=啟動 LFS 檔案的鏡像功能。
-mirror_lfs_endpoint=LFS 端點
+mirror_lfs_endpoint=LFS 終點
 mirror_lfs_endpoint_desc=同步將會嘗試使用 Clone URL 來<a target="_blank" rel="noopener noreferrer" href="%s">確認 LFS 伺服器</a>。如果存儲庫的 LFS 資料放在其他地方,您也可以指定自訂的端點。
 mirror_last_synced=上次同步
 mirror_password_placeholder=(未變更)
@@ -963,7 +1050,7 @@ blame_prior=檢視此變更前的 Blame
 author_search_tooltip=最多顯示 30 位使用者
 
 
-transfer.accept=同意轉移
+transfer.accept=接受轉移
 transfer.accept_desc=轉移到「%s」
 transfer.reject=拒絕轉移
 transfer.reject_desc=取消轉移到「%s」
@@ -975,9 +1062,9 @@ desc.internal=組織內部用
 desc.archived=已封存
 
 template.items=範本項目
-template.git_content=Git 內容(預設分支)
+template.git_content=Git 內容(預設分支)
 template.git_hooks=Git Hook
-template.git_hooks_tooltip=目前來說,一旦您加入了 Git Hook 就無法修改或移除。唯有您信任該儲存庫範本時才選取此項目。
+template.git_hooks_tooltip=目前來說,一旦您新增了 Git Hook 就無法修改或移除。唯有您信任該儲存庫範本時才選取此項目。
 template.webhooks=Webhook
 template.topics=主題
 template.avatar=大頭貼
@@ -1010,22 +1097,22 @@ migrate_items_merge_requests=合併請求
 migrate_items_releases=版本發布
 migrate_repo=遷移儲存庫
 migrate.clone_address=從 URL 遷移 / Clone
-migrate.clone_address_desc=現有存儲庫的 HTTP(S) 或 Git Clone URL
+migrate.clone_address_desc=現有儲存庫的 HTTP(S) 或 Git 「clone」 URL
 migrate.github_token_desc=由於 GitHub API 的速率限制,您可在此輸入一個或多個由半形逗號「,」分隔的 Token 來加快遷移速度。警告:濫用此功能可能會違反該服務提供者的政策並導致帳戶被封鎖。
 migrate.clone_local_path=或者是本地端伺服器路徑
 migrate.permission_denied=您並沒有導入本地儲存庫的權限。
 migrate.permission_denied_blocked=您無法從未允許的主機匯入,請聯絡管理員檢查以下設定值 ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS
-migrate.invalid_local_path=無效的本地路徑,該路徑不存在或不是一個目錄。
+migrate.invalid_local_path=無效的本地路徑。它不存在或不是一個資料夾。
 migrate.invalid_lfs_endpoint=該 LFS 端點無效。
 migrate.failed=遷移失敗:%v
-migrate.migrate_items_options=遷移其他項目需要 Access Token。
+migrate.migrate_items_options=遷移其他項目需要取用 Token
 migrated_from=已從 <a href="%[1]s">%[2]s</a> 遷移
 migrated_from_fake=已從 %[1]s 遷移
 migrate.migrate=從 %s 遷移
 migrate.migrating=正在從 <b>%s</b> 遷移...
 migrate.migrating_failed=從 <b>%s</b> 遷移失敗
 migrate.migrating_failed_no_addr=遷移失敗。
-migrate.github.description=從 github.com 或其他 GitHub 執行個體遷移資料。
+migrate.github.description=從 github.com 或 GitHub Enterprise 伺服器遷移資料。
 migrate.git.description=從任何 Git 服務遷移儲存庫。
 migrate.gitlab.description=從 gitlab.com 或其他 GitLab 執行個體遷移資料。
 migrate.gitea.description=從 gitea.com 或其他 Gitea/Forgejo 執行個體遷移資料。
@@ -1037,7 +1124,7 @@ migrate.migrating_git=正在遷移 Git 資料
 migrate.migrating_topics=正在遷移主題
 migrate.migrating_milestones=正在遷移里程碑
 migrate.migrating_labels=正在遷移標籤
-migrate.migrating_releases=正在遷移版本發布
+migrate.migrating_releases=正在遷移版本發佈
 migrate.migrating_issues=正在遷移問題
 migrate.migrating_pulls=正在遷移合併請求
 
@@ -1057,7 +1144,7 @@ download_archive=下載此儲存庫
 more_operations=更多操作
 
 no_desc=暫無描述
-quick_guide=快速幫助
+quick_guide=快速指南
 clone_this_repo=Clone 此儲存庫
 cite_this_repo=引用此儲存庫
 create_new_repo_command=從命令列建立新儲存庫。
@@ -1095,7 +1182,7 @@ file.title=%s 於 %s
 file_raw=原始文件
 file_history=歷史記錄
 file_view_source=檢視原始碼
-file_view_rendered=檢視渲染圖
+file_view_rendered=檢視渲染版本
 file_view_raw=查看原始文件
 file_permalink=永久連結
 file_too_large=檔案太大,無法顯示。
@@ -1105,10 +1192,10 @@ ambiguous_character=`%[1]c [U+%04[1]X] 容易與 %[2]c [U+%04[2]X] 混淆`
 
 escape_control_characters=Escape
 unescape_control_characters=Unescape
-file_copy_permalink=複製固定連結
+file_copy_permalink=複製永久連結
 view_git_blame=檢視 Git Blame
-video_not_supported_in_browser=您的瀏覽器不支援使用 HTML5 播放影片。
-audio_not_supported_in_browser=您的瀏覽器不支援 HTML5 的「audio」標籤
+video_not_supported_in_browser=您的瀏覽器不支援 HTML5 的「video」標籤。
+audio_not_supported_in_browser=您的瀏覽器不支援 HTML5 的「audio」標籤。
 stored_lfs=已使用 Git LFS 儲存
 symbolic_link=符號連結
 commit_graph=提交線圖
@@ -1123,8 +1210,8 @@ line=行
 lines=行
 from_comment=(留言)
 
-editor.add_file=加入檔案
-editor.new_file=新增檔案
+editor.add_file=新增檔案
+editor.new_file=建立新檔案
 editor.upload_file=上傳檔案
 editor.edit_file=編輯檔案
 editor.preview_changes=預覽更改
@@ -1138,7 +1225,7 @@ editor.delete_this_file=刪除檔案
 editor.must_have_write_access=您必須擁有寫入權限才能對此檔案進行修改或提出變更。
 editor.file_delete_success=已刪除文件「%s」。
 editor.name_your_file=命名您的檔案...
-editor.filename_help=輸入名稱和斜線 ('/') 以新增目錄。在文字框的開始輸入倒退鍵以移除目錄。
+editor.filename_help=輸入名稱和斜線("/") 以新增目錄。在文字框開始處輸入退格鍵以移除目錄。
 editor.or=或
 editor.cancel_lower=取消
 editor.commit_signed_changes=提交簽署過的變更
@@ -2358,6 +2445,45 @@ find_file.no_matching=找不到符合的檔案
 error.csv.too_large=無法渲染此檔案,因為它太大了。
 error.csv.unexpected=無法渲染此檔案,因為它包含了未預期的字元,於第 %d 行第 %d 列。
 error.csv.invalid_field_count=無法渲染此檔案,因為它第 %d 行的欄位數量有誤。
+fork_no_valid_owners = 因為此儲存庫沒有有效的所有者,它無法被 fork。
+fork_branch = 要複製到 fork 的分支
+commit.load_referencing_branches_and_tags = 載入引用這個提交的分支和標籤
+mirror_sync = 已同步
+commit.contained_in_default_branch = 這個提交是預設分支的一部分
+editor.invalid_commit_mail = 用於建立提交的信箱無效。
+admin.update_flags = 更新旗標
+admin.failed_to_replace_flags = 儲存庫旗標更新失敗
+admin.flags_replaced = 儲存庫旗標已被更換
+default_branch_label = 預設
+tree_path_not_found_tag = 路徑 %[1]s 不存在於標籤 %[2]s 中
+tree_path_not_found_commit = 路徑 %[1]s 不存在於提交 %[2]s 中
+tree_path_not_found_branch = 路徑 %[1]s 不存在於分支 %[2]s 中
+transfer.no_permission_to_accept = 您沒有權限接受這項轉讓。
+archive.title = 這個儲存庫被封存了。您可以檢視其中的檔案或是 Clone 它,但您無法推送提交,提出問題或合併請求。
+archive.title_date = 這個儲存庫在 %s 被封存了。您可以檢視其中的檔案或 clone 它,但您無法推送提交,提出問題或合併請求。
+migrate.forgejo.description = 從 codeberg.org 或其他 Forgejo 站點遷移資料。
+migrate.cancel_migrating_title = 取消遷移
+executable_file = 可執行檔
+all_branches = 所有分支
+object_format_helper = 儲存庫的物件格式。一旦設定便無法更改。SHA1 的相容性最好。
+stars_remove_warning = 這將會移除此儲存庫的所有星星。
+transfer.no_permission_to_reject = 您沒有權限拒絕這項轉讓。
+desc.sha256 = SHA256
+form.name_pattern_not_allowed = 您無法在儲存庫的名字中使用「%s」格式。
+admin.manage_flags = 管理旗標
+visibility_helper = 將儲存庫設為私有
+mirror_address_url_invalid = URL 無效。您必須將整個 URL 正確轉義(escape)。
+migrate.migrating_failed.error = 遷移失敗:%s
+migrate.cancel_migrating_confirm = 您確定要取消這次的遷移嗎?
+invisible_runes_header = `此檔案內含不可見的 Unicode 字元`
+ambiguous_runes_header = `這個檔案內含模棱兩可的 Unicode 字元`
+rss.must_be_on_branch = 您必須在一個分支上才能訂閱 RSS。
+admin.enabled_flags = 該儲存庫的旗標:
+mirror_address_protocol_invalid = 輸入的 URL 無效。只有 https(s):// 或 git:// 連結可以被設定為鏡像來源。
+ambiguous_runes_description = `這個檔案內含容易造成混淆的 Unicode 字元。如果您覺得這是檔案作者的本意,您可以安全的忽略這則訊息。按下 Escape 可以顯示這些字元。`
+commit.contained_in = 這個提交存在於:
+settings.archive.mirrors_unavailable = 不能鏡像已封存的儲存庫。
+settings.mirror_settings.push_mirror.edit_sync_time = 編輯鏡像同步週期
 
 [graphs]
 
@@ -2408,14 +2534,14 @@ settings.update_avatar_success=已更新組織的大頭貼。
 settings.delete=刪除組織
 settings.delete_account=刪除這個組織
 settings.delete_prompt=該組織將被永久刪除。此動作<strong>不可</strong>還原!
-settings.confirm_delete_account=確認刪除組織
+settings.confirm_delete_account=確認刪除
 settings.delete_org_title=刪除組織
 settings.delete_org_desc=即將永久刪除這個組織,是否繼續?
 settings.hooks_desc=此組織下的<strong>所有存儲庫</strong>都會觸發在此新增的 Webhook。
 
 settings.labels_desc=在此處新增的標籤可用於此組織下的<strong>所有儲存庫</strong>。
 
-members.membership_visibility=成員可見性:
+members.membership_visibility=成員能見度:
 members.public=可見
 members.public_helper=隱藏
 members.private=隱藏
@@ -2451,7 +2577,7 @@ teams.owners_permission_desc=擁有者對 <strong>所有儲存庫</strong> 具
 teams.members=團隊成員
 teams.update_settings=更新設定
 teams.delete_team=刪除團隊
-teams.add_team_member=增加團隊成員
+teams.add_team_member=新增團隊成員
 teams.invite_team_member=邀請至 %s
 teams.invite_team_member.list=待處理的邀請
 teams.delete_team_title=刪除團隊
@@ -2516,7 +2642,7 @@ dashboard.cron.error=Cron 中的錯誤: %s: %[3]s
 dashboard.cron.finished=Cron: %[1]s 已完成
 dashboard.delete_inactive_accounts=刪除所有未啟用帳戶
 dashboard.delete_inactive_accounts.started=刪除所有未啟用帳戶的任務已啟動。
-dashboard.delete_repo_archives=刪除所有儲存庫存檔 (ZIP, TAR.GZ, etc..)
+dashboard.delete_repo_archives=刪除所有儲存庫存檔 (ZIP、TAR.GZ 等)
 dashboard.delete_repo_archives.started=刪除所有儲存庫存檔的任務已啟動。
 dashboard.delete_missing_repos=刪除所有遺失 Git 檔案的儲存庫
 dashboard.delete_missing_repos.started=刪除所有遺失 Git 檔案的儲存庫的任務已啟動。
@@ -2529,35 +2655,35 @@ dashboard.deleted_branches_cleanup=清理已刪除的分支
 dashboard.update_migration_poster_id=更新遷移發布者 ID
 dashboard.git_gc_repos=對所有儲存庫進行垃圾回收
 dashboard.resync_all_sshkeys=使用 Forgejo 的 SSH 金鑰更新「.ssh/authorized_keys」檔案。
-dashboard.resync_all_sshprincipals=使用 Forgejo 的 SSH 主體更新「.ssh/authorized_principals」檔案。
-dashboard.resync_all_hooks=重新同步所有儲存庫的 pre-receive、update 和 post-receive Hook。
+dashboard.resync_all_sshprincipals=使用 Forgejo 的 SSH 規則更新「.ssh/authorized_principals」檔案。
+dashboard.resync_all_hooks=重新同步所有儲存庫的 pre-receive、update 和 post-receive Hook
 dashboard.reinit_missing_repos=重新初始化所有記錄存在但遺失的 Git 儲存庫
 dashboard.sync_external_users=同步外部使用者資料
 dashboard.cleanup_hook_task_table=清理 hook_task 資料表
 dashboard.cleanup_packages=清理已過期的套件
-dashboard.server_uptime=服務執行時間
+dashboard.server_uptime=伺服器運作時間
 dashboard.current_goroutine=目前的 Goroutines 數量
 dashboard.current_memory_usage=目前記憶體使用量
 dashboard.total_memory_allocated=所有被分配的記憶體
-dashboard.memory_obtained=記憶體佔用量
-dashboard.pointer_lookup_times=指針查找次數
+dashboard.memory_obtained=獲得的記憶體
+dashboard.pointer_lookup_times=指標尋找次數
 dashboard.memory_allocate_times=記憶體分配次數
 dashboard.memory_free_times=記憶體釋放次數
 dashboard.current_heap_usage=目前 Heap 記憶體使用量
-dashboard.heap_memory_obtained=Heap 記憶體佔用量
+dashboard.heap_memory_obtained=獲得的 Heap 記憶體
 dashboard.heap_memory_idle=Heap 記憶體閒置量
 dashboard.heap_memory_in_use=正在使用的 Heap 記憶體
 dashboard.heap_memory_released=被釋放的 Heap 記憶體
 dashboard.heap_objects=Heap 物件數量
 dashboard.bootstrap_stack_usage=啟動 Stack 使用量
-dashboard.stack_memory_obtained=被分配的 Stack 記憶體
+dashboard.stack_memory_obtained=獲得的 Stack 記憶體
 dashboard.mspan_structures_usage=MSpan 結構使用量
-dashboard.mspan_structures_obtained=被分配的 MSpan 結構
-dashboard.mcache_structures_usage=MCache 結構記使用量
-dashboard.mcache_structures_obtained=被分配的 MCache 結構
+dashboard.mspan_structures_obtained=獲得的 MSpan 結構
+dashboard.mcache_structures_usage=MCache 結構使用量
+dashboard.mcache_structures_obtained=獲得的 MCache 結構
 dashboard.profiling_bucket_hash_table_obtained=被分配的剖析雜湊表
 dashboard.gc_metadata_obtained=被分配 GC Metadata
-dashboard.other_system_allocation_obtained=其他被分配的系統記憶體
+dashboard.other_system_allocation_obtained=其他獲得的系統記憶體
 dashboard.next_gc_recycle=下次 GC 記憶體回收量
 dashboard.last_gc_time=距離上次 GC 時間
 dashboard.total_gc_time=總 GC 暫停時間
@@ -2597,7 +2723,7 @@ users.edit_account=編輯使用者帳戶
 users.max_repo_creation=最大儲存庫數量
 users.max_repo_creation_desc=(設定 -1 使用全域預設限制)
 users.is_activated=使用者帳戶已啟用
-users.prohibit_login=停用登入
+users.prohibit_login=禁止登入
 users.is_admin=是管理員
 users.is_restricted=受限制的
 users.allow_git_hook=可以建立 Git Hook
@@ -2621,7 +2747,7 @@ users.list_status_filter.not_active=未啟用
 users.list_status_filter.is_admin=管理員
 users.list_status_filter.not_admin=非管理員
 users.list_status_filter.is_restricted=受限
-users.list_status_filter.not_restricted=未受限
+users.list_status_filter.not_restricted=未受限制
 users.list_status_filter.is_prohibit_login=禁止登入
 users.list_status_filter.not_prohibit_login=允許登入
 users.list_status_filter.is_2fa_enabled=已啟用兩步驟驗證
@@ -2633,7 +2759,7 @@ emails.activated=已啟用
 emails.filter_sort.email=電子信箱
 emails.filter_sort.email_reverse=電子信箱(倒序)
 emails.filter_sort.name=使用者名稱
-emails.filter_sort.name_reverse=使用者名稱(倒序)
+emails.filter_sort.name_reverse=使用者名稱(倒序)
 emails.updated=信箱已更新
 emails.not_updated=電子信箱更新失敗: %v
 emails.duplicate_active=此信箱已被其他使用者使用
@@ -2794,11 +2920,11 @@ auths.login_source_of_type_exist=已經有相同類型的認證來源。
 auths.unable_to_initialize_openid=無法初始化 OpenID 連接提供者: %s
 auths.invalid_openIdConnectAutoDiscoveryURL=自動探索 URL 無效 (它必須是以 http:// 或 https:// 開頭的有效 URL)
 
-config.server_config=伺服器組態
+config.server_config=伺服器設定
 config.app_name=網站標題
 config.app_ver=Forgejo 版本
 config.app_url=Forgejo 基本 URL
-config.custom_conf=設定檔案路徑
+config.custom_conf=設定檔路徑
 config.custom_file_root_path=自訂檔案根目錄
 config.domain=伺服器域名
 config.offline_mode=本地模式
@@ -2812,7 +2938,7 @@ config.log_file_root_path=日誌路徑
 config.script_type=腳本類型
 config.reverse_auth_user=反向代理認證
 
-config.ssh_config=SSH 組態
+config.ssh_config=SSH 設定
 config.ssh_enabled=已啟用
 config.ssh_start_builtin_server=使用內建的伺服器
 config.ssh_domain=SSH 伺服器域名
@@ -2824,12 +2950,12 @@ config.ssh_keygen_path=金鑰產生 (' ssh-keygen ') 路徑
 config.ssh_minimum_key_size_check=金鑰最小大小檢查
 config.ssh_minimum_key_sizes=金鑰最小大小
 
-config.lfs_config=LFS 組態
+config.lfs_config=LFS 設定
 config.lfs_enabled=已啟用
 config.lfs_content_path=LFS 內容路徑
 config.lfs_http_auth_expiry=LFS HTTP 驗證有效時間
 
-config.db_config=資料庫組態
+config.db_config=資料庫設定
 config.db_type=資料庫類型
 config.db_host=主機地址
 config.db_name=名稱
@@ -2838,7 +2964,7 @@ config.db_schema=結構描述
 config.db_ssl_mode=SSL
 config.db_path=資料庫路徑
 
-config.service_config=服務組態
+config.service_config=服務設定
 config.register_email_confirm=要求註冊時確認電子郵件
 config.disable_register=關閉註冊功能
 config.allow_only_internal_registration=只允許從 Forgejo 註冊
@@ -2857,15 +2983,15 @@ config.enable_timetracking=啟用時間追蹤
 config.default_enable_timetracking=預設啟用時間追蹤
 config.default_allow_only_contributors_to_track_time=只讓貢獻者追蹤時間
 config.no_reply_address=隱藏電子信箱域名
-config.default_visibility_organization=新組織的預設瀏覽權限
+config.default_visibility_organization=新組織的預設能見度
 config.default_enable_dependencies=預設啟用問題的先決條件
 
-config.webhook_config=Webhook 組態
+config.webhook_config=Webhook 設定
 config.queue_length=佇列長度
 config.deliver_timeout=傳送逾時
 config.skip_tls_verify=略過 TLS 驗證
 
-config.mailer_config=郵件程式組態
+config.mailer_config=郵件程式設定
 config.mailer_enabled=啟用服務
 config.mailer_enable_helo=啟用 HELO
 config.mailer_name=發送者名稱
@@ -2879,20 +3005,20 @@ config.mailer_sendmail_args=Sendmail 參數
 config.mailer_sendmail_timeout=Sendmail 逾時
 config.mailer_use_dummy=Dummy
 config.test_email_placeholder=電子信箱 (例:test@example.com)
-config.send_test_mail=傳送測試郵件
-config.test_mail_failed=傳送測試郵件到「%s」時失敗: %v
-config.test_mail_sent=測試郵件已傳送到「%s」。
+config.send_test_mail=寄送測試郵件
+config.test_mail_failed=傳送測試郵件至「%s」時失敗: %v
+config.test_mail_sent=測試郵件已傳送至「%s」。
 
-config.oauth_config=OAuth 組態
+config.oauth_config=OAuth 設定
 config.oauth_enabled=啟用服務
 
-config.cache_config=Cache 組態
-config.cache_adapter=Cache 適配器
-config.cache_interval=Cache 週期
+config.cache_config=快取設定
+config.cache_adapter=快取轉接器(adapter)
+config.cache_interval=快取週期
 config.cache_conn=Cache 連接字符串
 config.cache_item_ttl=快取項目 TTL
 
-config.session_config=Session 組態
+config.session_config=Session 設定
 config.session_provider=Session 提供者
 config.provider_config=提供者設定
 config.cookie_name=Cookie 名稱
@@ -2901,24 +3027,24 @@ config.session_life_time=Session 生命週期
 config.https_only=僅限 HTTPS
 config.cookie_life_time=Cookie 生命週期
 
-config.picture_config=圖片和大頭貼組態
+config.picture_config=圖片和大頭貼設定
 config.picture_service=圖片服務
 config.disable_gravatar=停用 Gravatar
-config.enable_federated_avatar=啟用 Federated Avatars
+config.enable_federated_avatar=啟用 Federated 大頭貼
 
-config.git_config=Git 組態
+config.git_config=Git 設定
 config.git_disable_diff_highlight=停用比較語法高亮
 config.git_max_diff_lines=差異比較時顯示的最多行數 (單檔)
 config.git_max_diff_line_characters=差異比較時顯示的最多字元數 (單行)
 config.git_max_diff_files=差異比較時顯示的最多檔案數
 config.git_gc_args=GC 參數
 config.git_migrate_timeout=遷移逾時
-config.git_mirror_timeout=鏡像更新超時
+config.git_mirror_timeout=鏡像更新逾時
 config.git_clone_timeout=Clone 作業逾時
 config.git_pull_timeout=Pull 作業逾時
 config.git_gc_timeout=GC 作業逾時
 
-config.log_config=日誌組態
+config.log_config=日誌設定
 config.disabled_logger=已停用
 config.access_log_mode=存取日誌模式
 config.xorm_log_sql=記錄 SQL
@@ -2961,10 +3087,10 @@ monitor.queue.settings.changed=已更新設定
 notices.system_notice_list=系統提示
 notices.view_detail_header=查看提示細節
 notices.operations=操作
-notices.select_all=選取全部
+notices.select_all=全部選取
 notices.deselect_all=取消所有選取
 notices.inverse_selection=反向選取
-notices.delete_selected=刪除選取項
+notices.delete_selected=刪除所選項目
 notices.delete_all=刪除所有提示
 notices.type=類型
 notices.type_1=儲存庫
@@ -2972,6 +3098,29 @@ notices.type_2=任務
 notices.desc=描述
 notices.op=操作
 notices.delete_success=已刪除系統提示。
+settings = 管理員設定
+emails.change_email_text = 您確定要更新這個電子信箱地址嗎?
+monitor.download_diagnosis_report = 下載診斷報告
+dashboard.task.cancelled = 作業:%[1]s 已被取消:%[3]s
+dashboard.cron.cancelled = 定時作業:%[1]s 已被取消:%[3]s
+dashboard.cleanup_actions = 清除過期的 Action 日誌和物件
+users.bot = 機器人
+users.remote = 遠端
+monitor.queue.settings.remove_all_items_done = 已移除佇列中所有項目。
+config.access_log_template = 存取日誌範本
+monitor.stats = 統計資料
+self_check.no_problem_found = 未發現任何問題。
+config.send_test_mail_submit = 寄送
+users.details = 使用者詳細資訊
+assets = 程式碼資料
+dashboard.sync_branch.started = 已開始同步分支
+dashboard.rebuild_issue_indexer = 重建問題索引
+repos.lfs_size = LFS 大小
+packages.cleanup = 清除過期資料
+packages.cleanup.success = 已成功清除過期資料
+monitor.processes_count = %d 個程序
+monitor.queue.settings.remove_all_items = 全部移除
+identity_access = 身分和存取權限
 
 
 [action]
@@ -3052,8 +3201,8 @@ default_key=使用預設金鑰簽署
 error.extract_sign=無法提取簽署
 error.generate_hash=無法產生提交的雜湊值
 error.no_committer_account=提交者的電子信箱沒有連結到任何帳戶
-error.no_gpg_keys_found=沒有發現已知的金鑰在資料庫的簽署中
-error.not_signed_commit=未簽名的提交
+error.no_gpg_keys_found=資料庫中找不到此簽署所對應的金鑰
+error.not_signed_commit=未簽署的提交
 error.failed_retrieval_gpg_keys=找不到任何與該提交者帳戶相關的金鑰
 error.probable_bad_signature=警告!雖然資料庫中有此 ID 的金鑰,但此提交未通過它的驗證!此提交是有疑慮的。
 error.probable_bad_default_signature=警告!雖然預設金鑰擁有此 ID,但此提交未通過它的驗證!此提交是有疑慮的。
@@ -3183,7 +3332,7 @@ owner.settings.cargo.rebuild.success=成功重建了 Cargo 索引。
 owner.settings.cleanuprules.title=管理清理規則
 owner.settings.cleanuprules.add=加入清理規則
 owner.settings.cleanuprules.edit=編輯清理規則
-owner.settings.cleanuprules.preview=清理規則預覽
+owner.settings.cleanuprules.preview=預覽清理規則
 owner.settings.cleanuprules.preview.overview=已排定要移除 %d 個套件。
 owner.settings.cleanuprules.preview.none=清理規則不符合任何套件。
 owner.settings.cleanuprules.enabled=已啟用
@@ -3201,6 +3350,9 @@ owner.settings.cleanuprules.success.update=已更新清理規則。
 owner.settings.cleanuprules.success.delete=已刪除清理規則。
 owner.settings.chef.title=Chef Registry
 owner.settings.chef.keypair=產生密鑰組
+debian.repository.components = 元件
+go.install = 從指令列安裝套件:
+owner.settings.cleanuprules.none = 目前沒有任何清理規則。
 
 [secrets]
 secrets=Secret
@@ -3221,9 +3373,9 @@ actions=Actions
 
 unit.desc=管理 Actions
 
-status.unknown=未知
-status.waiting=正在等候
-status.running=正在執行
+status.unknown=未知的
+status.waiting=等待中
+status.running=執行中
 status.success=成功
 status.failure=失敗
 status.skipped=已略過
@@ -3276,11 +3428,65 @@ workflow.enable=啟用工作流程
 workflow.enable_success=已成功啟用工作流程「%s」。
 
 need_approval_desc=來自 Frok 儲存庫的合併請求需要核可才能執行工作流程。
+variables.edit = 編輯變數
+variables = 變數
+variables.management = 變數管理
+variables.id_not_exist = ID 為 %d 的變數不存在。
+variables.description = 變數會被傳給特定的 Actions,除此之外它們無法被讀取。
+variables.creation.failed = 變數新增失敗。
+variables.update.success = 該變數已被編輯。
+variables.deletion.failed = 變數刪除失敗。
+variables.deletion.success = 該變數已被刪除。
+variables.creation = 新增變數
+variables.none = 目前沒有變數。
+variables.deletion = 刪除變數
+variables.deletion.description = 刪除變數是永久且不可取消的。要繼續嗎?
+variables.creation.success = 變數 「%s」已成功被新增。
+variables.update.failed = 編輯變數失敗。
+runs.no_results = 沒有相符的結果。
+runs.no_workflows = 目前沒有任何工作流程。
+runs.pushed_by = 推送者
+runs.status_no_select = 所有狀態
+runs.scheduled = 已排程
+runs.empty_commit_message = (空白的提交訊息)
+runners.task_list.no_tasks = 目前沒有任何工作。
+workflow.disabled = 工作流程已被停用。
+status.cancelled = 已取消
 
 
 [projects]
+type-2.display_name = 儲存庫專案
+type-1.display_name = 個人專案
+type-3.display_name = 組織專案
 
 [git.filemode]
 ; Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", …
 symbolic_link=符號連結
+changed_filemode = %[1]s → %[2]s
+submodule = 子模組
+normal_file = 一般檔案
+executable_file = 可執行檔
 
+
+
+[search]
+package_kind = 搜尋套件…
+search = 搜尋…
+type_tooltip = 搜尋類型
+match_tooltip = 僅包含與搜尋字詞完全相符的結果
+repo_kind = 搜尋儲存庫…
+fuzzy = 模糊
+fuzzy_tooltip = 讓搜尋結果也包含與搜尋詞相近的的項目
+match = 相符
+user_kind = 搜尋使用者…
+org_kind = 搜尋組織…
+team_kind = 搜尋團隊…
+code_kind = 搜尋程式碼
+code_search_unavailable = 程式碼搜尋目前無法使用。請連絡網站管理員。
+no_results = 沒有找到相符的結果。
+keyword_search_unavailable = 關鍵字搜尋目前無法使用。請連絡網站管理員。
+runner_kind = 搜尋 Runners …
+project_kind = 搜尋專案…
+branch_kind = 搜尋分支…
+commit_kind = 搜尋提交…
+code_search_by_git_grep = 目前搜尋結果由「git grep」提供。如果網站管理員啟程式碼索引,可能會有更好的結果。
\ No newline at end of file

From df8b1b5dd202822f216d270c461f350dbc99e885 Mon Sep 17 00:00:00 2001
From: Robin Kloppe <git@mainboarder.de>
Date: Sat, 27 Apr 2024 20:17:33 +0200
Subject: [PATCH 026/107] RELEASE Version Link

---
 RELEASE-NOTES.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 9980338629..844c0621fe 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -51,7 +51,7 @@ $ git -C forgejo log --oneline --no-merges origin/v1.21/forgejo..origin/v7.0/for
   Note that the modifications related to CSS, templates or assets (images, fonts, etc.) are not documented here.
   Although they can be extracted and modified, Forgejo does not provide any guarantee that such changes
   will be portable from one version to another (even a patch version). See also
-  [the developer documentation about interface customization](https://forgejo.org/docs/v1.21/developer/customization/).
+  [the developer documentation about interface customization](https://forgejo.org/docs/v7.0/developer/customization/).
   * [Update checker setting might change](https://codeberg.org/forgejo/forgejo/pulls/2925). The documentation was listing it as enabled by default, however, for a while it was disabled unless it was explicitly specified in the config or on the installation page. Instances migrated from Gitea also had it disabled due to different default value. Since then Forgejo got a privacy-friendly DNS-based update checking mechanism which is now being enabled by default unless explicitly specified [in the config](https://forgejo.org/docs/v7.0/admin/config-cheat-sheet/#cron---check-for-new-forgejo-versions-cronupdate_checker).
   * Language statistics for repositories that use `linguist` attributes in `.gitattributes` *may* show different statistics than previously, because Forgejo recognizes more [linguist attributes](https://forgejo.org/docs/v7.0/user/language-detection/) now.
   * It is [no longer possible to replace the default web editor](https://codeberg.org/forgejo/forgejo/pulls/2916) used to write comments or issues and pull requests with the EasyMDE editor. It is however still available as an alternative to edit releases and wiki pages.

From 5c76c37a50c337d34ab9b75dbbb8104eb2bb11e0 Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Sat, 27 Apr 2024 22:03:25 +0200
Subject: [PATCH 027/107] Release notes for Limit database max connections by
 default

---
 release-notes/8.0.0/3383.md | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 release-notes/8.0.0/3383.md

diff --git a/release-notes/8.0.0/3383.md b/release-notes/8.0.0/3383.md
new file mode 100644
index 0000000000..9832030aa3
--- /dev/null
+++ b/release-notes/8.0.0/3383.md
@@ -0,0 +1 @@
+The default config for `database.MAX_OPEN_CONNS` changed from 0 (unlimited) to 100 to avoid problems if it exceeds the limit by the database server. If you require high concurrency, try to increase this value for both Forgejo **and your database server**. [`Limit database max connections by default`](https://codeberg.org/forgejo/forgejo/pulls/3383)

From 10cd0f3992dde47187a84268ba87875d743b655f Mon Sep 17 00:00:00 2001
From: Robin Kloppe <git@mainboarder.de>
Date: Sat, 27 Apr 2024 23:27:57 +0200
Subject: [PATCH 028/107] replaced link to gitea docu

---
 templates/repo/settings/options.tmpl | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl
index aeb61d9eb3..31d5ae77f2 100644
--- a/templates/repo/settings/options.tmpl
+++ b/templates/repo/settings/options.tmpl
@@ -86,18 +86,18 @@
 				{{else}}
 					{{if $newMirrorsEntirelyEnabled}}
 						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs"}}
-						<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/usage/repo-mirror#pushing-to-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_title"}}</a><br><br>
+						<a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/docs/latest/user/repo-mirror#pushing-to-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_title"}}</a><br><br>
 						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.pull_mirror_instructions"}}
-						<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/usage/repo-mirror#pulling-from-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_pull_section"}}</a><br>
+						<a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/docs/latest/user/repo-mirror#pulling-from-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_pull_section"}}</a><br>
 					{{else if $onlyNewPushMirrorsEnabled}}
 						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.disabled_pull_mirror.instructions"}}
 						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.more_information_if_disabled"}}
-						<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/usage/repo-mirror#pulling-from-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_title"}}</a><br>
+						<a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/docs/latest/user/repo-mirror#pulling-from-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_title"}}</a><br>
 					{{else if $onlyNewPullMirrorsEnabled}}
 						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.disabled_push_mirror.instructions"}}
 						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning"}}
 						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.more_information_if_disabled"}}
-						<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/usage/repo-mirror#pulling-from-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_title"}}</a><br><br>
+						<a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/docs/latest/user/repo-mirror#pulling-from-a-remote-repository">{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.doc_link_title"}}</a><br><br>
 						{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.disabled_push_mirror.info"}}
 						{{if $existingPushMirror}}
 							{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.can_still_use"}}

From 162b8401008b9a9a111a32187a20f494858ab84b Mon Sep 17 00:00:00 2001
From: Beowulf <beowulf@beocode.eu>
Date: Sun, 28 Apr 2024 00:33:03 +0200
Subject: [PATCH 029/107] Add inline attachments to comments If incoming email
 is configured and an email is sent, inline attachments are currently not
 added to the comment if it has the `Content-Disposition: inline` instead of
 `Content-Disposition: attachment` as e.g. with Apple Mail.

This adds inline attachments (`Content-Disposition: inline`) that have a
filename as attachment to the comment.

Fixes #3496
---
 services/mailer/incoming/incoming.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/services/mailer/incoming/incoming.go b/services/mailer/incoming/incoming.go
index eade0cf271..a2352773ae 100644
--- a/services/mailer/incoming/incoming.go
+++ b/services/mailer/incoming/incoming.go
@@ -367,6 +367,14 @@ func getContentFromMailReader(env *enmime.Envelope) *MailContent {
 			Content: attachment.Content,
 		})
 	}
+	for _, inline := range env.Inlines {
+		if inline.FileName != "" {
+			attachments = append(attachments, &Attachment{
+				Name:    inline.FileName,
+				Content: inline.Content,
+			})
+		}
+	}
 
 	return &MailContent{
 		Content:     reply.FromText(env.Text),

From 95f8b1bbc59ce92322730c2a81b01f6155643c5c Mon Sep 17 00:00:00 2001
From: Renovate Bot <forgejo-renovate-action@forgejo.org>
Date: Sun, 28 Apr 2024 00:05:53 +0000
Subject: [PATCH 030/107] Update module github.com/urfave/cli/v2 to v2.27.2

---
 go.mod |  6 +++---
 go.sum | 12 ++++++------
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/go.mod b/go.mod
index f32e871921..a7ca6c202c 100644
--- a/go.mod
+++ b/go.mod
@@ -92,7 +92,7 @@ require (
 	github.com/stretchr/testify v1.9.0
 	github.com/syndtr/goleveldb v1.0.0
 	github.com/ulikunitz/xz v0.5.11
-	github.com/urfave/cli/v2 v2.27.1
+	github.com/urfave/cli/v2 v2.27.2
 	github.com/xanzy/go-gitlab v0.96.0
 	github.com/yohcop/openid-go v1.0.1
 	github.com/yuin/goldmark v1.7.0
@@ -161,7 +161,7 @@ require (
 	github.com/couchbase/go-couchbase v0.1.1 // indirect
 	github.com/couchbase/gomemcached v0.3.0 // indirect
 	github.com/couchbase/goutils v0.1.2 // indirect
-	github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
+	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
 	github.com/cyphar/filepath-securejoin v0.2.4 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/davidmz/go-pageant v1.0.2 // indirect
@@ -269,7 +269,7 @@ require (
 	github.com/x448/float16 v0.8.4 // indirect
 	github.com/xanzy/ssh-agent v0.3.3 // indirect
 	github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
-	github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
+	github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
 	github.com/zeebo/blake3 v0.2.3 // indirect
 	go.etcd.io/bbolt v1.3.9 // indirect
 	go.mongodb.org/mongo-driver v1.13.1 // indirect
diff --git a/go.sum b/go.sum
index 95e400e40e..79d348cec5 100644
--- a/go.sum
+++ b/go.sum
@@ -203,8 +203,8 @@ github.com/couchbase/gomemcached v0.3.0 h1:XkMDdP6w7rtvLijDE0/RhcccX+XvAk5cboyBv
 github.com/couchbase/gomemcached v0.3.0/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo=
 github.com/couchbase/goutils v0.1.2 h1:gWr8B6XNWPIhfalHNog3qQKfGiYyh4K4VhO3P2o9BCs=
 github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE=
-github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
-github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
+github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
 github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
@@ -800,8 +800,8 @@ github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o
 github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
 github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
 github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
-github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
+github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
+github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
@@ -823,8 +823,8 @@ github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgk
 github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
 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/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI=
-github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
+github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
+github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
 github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5 h1:3seWKGVhGoc66Ht5QlhQsr4xT2caDnFegsnh2NqvENU=
 github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
 github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=

From 4b8f0d09726a49439c74efefe70045e419ce9130 Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Sun, 28 Apr 2024 12:48:17 +0200
Subject: [PATCH 031/107] Revert "Unify watching template"

This reverts commit 47ef51d51ee46976171bab80314baa3798e45d13.
---
 .../repo/issue/view_content/sidebar/watch.tmpl      | 13 +------------
 templates/repo/issue/view_content/watching.tmpl     | 12 ++++++++++++
 2 files changed, 13 insertions(+), 12 deletions(-)
 create mode 100644 templates/repo/issue/view_content/watching.tmpl

diff --git a/templates/repo/issue/view_content/sidebar/watch.tmpl b/templates/repo/issue/view_content/sidebar/watch.tmpl
index 6c74b140c8..852738a706 100644
--- a/templates/repo/issue/view_content/sidebar/watch.tmpl
+++ b/templates/repo/issue/view_content/sidebar/watch.tmpl
@@ -1,17 +1,6 @@
 <div class="ui watching">
 	<span class="text"><strong>{{ctx.Locale.Tr "notification.notifications"}}</strong></span>
 	<div class="tw-mt-2">
-		<form hx-boost="true" hx-sync="this:replace" hx-target="this" method="post" action="{{.Issue.Link}}/watch">
-			<input type="hidden" name="watch" value="{{if $.IssueWatch.IsWatching}}0{{else}}1{{end}}">
-			<button class="fluid ui button">
-				{{if $.IssueWatch.IsWatching}}
-					{{svg "octicon-mute" 16 "tw-mr-2"}}
-					{{ctx.Locale.Tr "repo.issues.unsubscribe"}}
-				{{else}}
-					{{svg "octicon-unmute" 16 "tw-mr-2"}}
-					{{ctx.Locale.Tr "repo.issues.subscribe"}}
-				{{end}}
-			</button>
-		</form>
+		{{template "repo/issue/view_content/watching" .}}
 	</div>
 </div>
diff --git a/templates/repo/issue/view_content/watching.tmpl b/templates/repo/issue/view_content/watching.tmpl
new file mode 100644
index 0000000000..05936d090b
--- /dev/null
+++ b/templates/repo/issue/view_content/watching.tmpl
@@ -0,0 +1,12 @@
+<form hx-boost="true" hx-sync="this:replace" hx-target="this" method="post" action="{{.Issue.Link}}/watch">
+	<input type="hidden" name="watch" value="{{if $.IssueWatch.IsWatching}}0{{else}}1{{end}}">
+	<button class="fluid ui button">
+		{{if $.IssueWatch.IsWatching}}
+			{{svg "octicon-mute" 16 "tw-mr-2"}}
+			{{ctx.Locale.Tr "repo.issues.unsubscribe"}}
+		{{else}}
+			{{svg "octicon-unmute" 16 "tw-mr-2"}}
+			{{ctx.Locale.Tr "repo.issues.subscribe"}}
+		{{end}}
+	</button>
+</form>

From bc8860ce32a8f83b32630d38904843f1ff9453c0 Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Sun, 28 Apr 2024 12:56:28 +0200
Subject: [PATCH 032/107] Move watching sub-template to sidebar folder

---
 routers/web/repo/issue_watch.go                               | 2 +-
 templates/repo/issue/view_content/sidebar/watch.tmpl          | 2 +-
 templates/repo/issue/view_content/{ => sidebar}/watching.tmpl | 0
 3 files changed, 2 insertions(+), 2 deletions(-)
 rename templates/repo/issue/view_content/{ => sidebar}/watching.tmpl (100%)

diff --git a/routers/web/repo/issue_watch.go b/routers/web/repo/issue_watch.go
index 8b033f3b17..c8d7187b8e 100644
--- a/routers/web/repo/issue_watch.go
+++ b/routers/web/repo/issue_watch.go
@@ -14,7 +14,7 @@ import (
 )
 
 const (
-	tplWatching base.TplName = "repo/issue/view_content/watching"
+	tplWatching base.TplName = "repo/issue/view_content/sidebar/watching"
 )
 
 // IssueWatch sets issue watching
diff --git a/templates/repo/issue/view_content/sidebar/watch.tmpl b/templates/repo/issue/view_content/sidebar/watch.tmpl
index 852738a706..ee14168070 100644
--- a/templates/repo/issue/view_content/sidebar/watch.tmpl
+++ b/templates/repo/issue/view_content/sidebar/watch.tmpl
@@ -1,6 +1,6 @@
 <div class="ui watching">
 	<span class="text"><strong>{{ctx.Locale.Tr "notification.notifications"}}</strong></span>
 	<div class="tw-mt-2">
-		{{template "repo/issue/view_content/watching" .}}
+		{{template "repo/issue/view_content/sidebar/watching" .}}
 	</div>
 </div>
diff --git a/templates/repo/issue/view_content/watching.tmpl b/templates/repo/issue/view_content/sidebar/watching.tmpl
similarity index 100%
rename from templates/repo/issue/view_content/watching.tmpl
rename to templates/repo/issue/view_content/sidebar/watching.tmpl

From b796694cd5df688105b490edd43aa923743b3113 Mon Sep 17 00:00:00 2001
From: Beowulf <beowulf@beocode.eu>
Date: Sun, 28 Apr 2024 14:11:17 +0200
Subject: [PATCH 033/107] Skip already handled incoming emails It seems like
 (at least on my machine) that every mail is processed twice. Added a check if
 the email is already handled and if so, skip it.

---
 services/mailer/incoming/incoming.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/services/mailer/incoming/incoming.go b/services/mailer/incoming/incoming.go
index a2352773ae..555cdfee8b 100644
--- a/services/mailer/incoming/incoming.go
+++ b/services/mailer/incoming/incoming.go
@@ -219,6 +219,11 @@ loop:
 			}
 
 			err := func() error {
+				if handledSet.Contains(msg.SeqNum) {
+					log.Debug("Skipping already handled message")
+					return nil
+				}
+
 				r := msg.GetBody(section)
 				if r == nil {
 					return fmt.Errorf("could not get body from message: %w", err)

From 2ec0c5e28454413856a6de48d197fa29170dd79b Mon Sep 17 00:00:00 2001
From: Cheng <36215014+ChengenH@users.noreply.github.com>
Date: Mon, 22 Apr 2024 03:44:03 +0800
Subject: [PATCH 034/107] chore: use errors.New to replace fmt.Errorf with no
 parameters will much better (#30621)

use errors.New to replace fmt.Errorf with no parameters will much better

(cherry picked from commit 9de443ced2c328d9b58a5e144a765f402aab859d)
---
 cmd/admin_auth.go                       | 3 ++-
 cmd/admin_auth_oauth.go                 | 3 ++-
 cmd/admin_auth_stmp.go                  | 3 +--
 cmd/admin_user_delete.go                | 3 ++-
 cmd/admin_user_generate_access_token.go | 5 +++--
 cmd/embedded.go                         | 6 +++---
 cmd/manager_logging.go                  | 3 ++-
 models/auth/oauth2.go                   | 3 ++-
 models/git/lfs_lock.go                  | 4 ++--
 models/repo_transfer.go                 | 3 ++-
 10 files changed, 21 insertions(+), 15 deletions(-)

diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go
index ec92e342d4..4777a92908 100644
--- a/cmd/admin_auth.go
+++ b/cmd/admin_auth.go
@@ -4,6 +4,7 @@
 package cmd
 
 import (
+	"errors"
 	"fmt"
 	"os"
 	"text/tabwriter"
@@ -91,7 +92,7 @@ func runListAuth(c *cli.Context) error {
 
 func runDeleteAuth(c *cli.Context) error {
 	if !c.IsSet("id") {
-		return fmt.Errorf("--id flag is missing")
+		return errors.New("--id flag is missing")
 	}
 
 	ctx, cancel := installSignals()
diff --git a/cmd/admin_auth_oauth.go b/cmd/admin_auth_oauth.go
index c151c0af27..8e6239ac33 100644
--- a/cmd/admin_auth_oauth.go
+++ b/cmd/admin_auth_oauth.go
@@ -4,6 +4,7 @@
 package cmd
 
 import (
+	"errors"
 	"fmt"
 	"net/url"
 
@@ -193,7 +194,7 @@ func runAddOauth(c *cli.Context) error {
 
 func runUpdateOauth(c *cli.Context) error {
 	if !c.IsSet("id") {
-		return fmt.Errorf("--id flag is missing")
+		return errors.New("--id flag is missing")
 	}
 
 	ctx, cancel := installSignals()
diff --git a/cmd/admin_auth_stmp.go b/cmd/admin_auth_stmp.go
index 58a6e2ac22..d724746905 100644
--- a/cmd/admin_auth_stmp.go
+++ b/cmd/admin_auth_stmp.go
@@ -5,7 +5,6 @@ package cmd
 
 import (
 	"errors"
-	"fmt"
 	"strings"
 
 	auth_model "code.gitea.io/gitea/models/auth"
@@ -166,7 +165,7 @@ func runAddSMTP(c *cli.Context) error {
 
 func runUpdateSMTP(c *cli.Context) error {
 	if !c.IsSet("id") {
-		return fmt.Errorf("--id flag is missing")
+		return errors.New("--id flag is missing")
 	}
 
 	ctx, cancel := installSignals()
diff --git a/cmd/admin_user_delete.go b/cmd/admin_user_delete.go
index 1cbc6f7527..520557554a 100644
--- a/cmd/admin_user_delete.go
+++ b/cmd/admin_user_delete.go
@@ -4,6 +4,7 @@
 package cmd
 
 import (
+	"errors"
 	"fmt"
 	"strings"
 
@@ -42,7 +43,7 @@ var microcmdUserDelete = &cli.Command{
 
 func runDeleteUser(c *cli.Context) error {
 	if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
-		return fmt.Errorf("You must provide the id, username or email of a user to delete")
+		return errors.New("You must provide the id, username or email of a user to delete")
 	}
 
 	ctx, cancel := installSignals()
diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go
index 6e78939680..6c2c10494e 100644
--- a/cmd/admin_user_generate_access_token.go
+++ b/cmd/admin_user_generate_access_token.go
@@ -4,6 +4,7 @@
 package cmd
 
 import (
+	"errors"
 	"fmt"
 
 	auth_model "code.gitea.io/gitea/models/auth"
@@ -42,7 +43,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{
 
 func runGenerateAccessToken(c *cli.Context) error {
 	if !c.IsSet("username") {
-		return fmt.Errorf("You must provide a username to generate a token for")
+		return errors.New("You must provide a username to generate a token for")
 	}
 
 	ctx, cancel := installSignals()
@@ -68,7 +69,7 @@ func runGenerateAccessToken(c *cli.Context) error {
 		return err
 	}
 	if exist {
-		return fmt.Errorf("access token name has been used already")
+		return errors.New("access token name has been used already")
 	}
 
 	// make sure the scopes are valid
diff --git a/cmd/embedded.go b/cmd/embedded.go
index 71d483d11c..9f03f7be7c 100644
--- a/cmd/embedded.go
+++ b/cmd/embedded.go
@@ -157,9 +157,9 @@ func runViewDo(c *cli.Context) error {
 	}
 
 	if len(matchedAssetFiles) == 0 {
-		return fmt.Errorf("no files matched the given pattern")
+		return errors.New("no files matched the given pattern")
 	} else if len(matchedAssetFiles) > 1 {
-		return fmt.Errorf("too many files matched the given pattern, try to be more specific")
+		return errors.New("too many files matched the given pattern, try to be more specific")
 	}
 
 	data, err := matchedAssetFiles[0].fs.ReadFile(matchedAssetFiles[0].name)
@@ -180,7 +180,7 @@ func runExtractDo(c *cli.Context) error {
 	}
 
 	if c.NArg() == 0 {
-		return fmt.Errorf("a list of pattern of files to extract is mandatory (e.g. '**' for all)")
+		return errors.New("a list of pattern of files to extract is mandatory (e.g. '**' for all)")
 	}
 
 	destdir := "."
diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go
index 2c701f2672..6049b00d5e 100644
--- a/cmd/manager_logging.go
+++ b/cmd/manager_logging.go
@@ -4,6 +4,7 @@
 package cmd
 
 import (
+	"errors"
 	"fmt"
 	"os"
 
@@ -249,7 +250,7 @@ func runAddFileLogger(c *cli.Context) error {
 	if c.IsSet("filename") {
 		vals["filename"] = c.String("filename")
 	} else {
-		return fmt.Errorf("filename must be set when creating a file logger")
+		return errors.New("filename must be set when creating a file logger")
 	}
 	if c.IsSet("rotate") {
 		vals["rotate"] = c.Bool("rotate")
diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go
index 5eabc7d9b4..125d64b36f 100644
--- a/models/auth/oauth2.go
+++ b/models/auth/oauth2.go
@@ -8,6 +8,7 @@ import (
 	"crypto/sha256"
 	"encoding/base32"
 	"encoding/base64"
+	"errors"
 	"fmt"
 	"net"
 	"net/url"
@@ -301,7 +302,7 @@ func UpdateOAuth2Application(ctx context.Context, opts UpdateOAuth2ApplicationOp
 		return nil, err
 	}
 	if app.UID != opts.UserID {
-		return nil, fmt.Errorf("UID mismatch")
+		return nil, errors.New("UID mismatch")
 	}
 	builtinApps := BuiltinApplications()
 	if _, builtin := builtinApps[app.ClientID]; builtin {
diff --git a/models/git/lfs_lock.go b/models/git/lfs_lock.go
index 261c73032a..2f65833fe3 100644
--- a/models/git/lfs_lock.go
+++ b/models/git/lfs_lock.go
@@ -5,7 +5,7 @@ package git
 
 import (
 	"context"
-	"fmt"
+	"errors"
 	"strings"
 	"time"
 
@@ -148,7 +148,7 @@ func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repositor
 	}
 
 	if !force && u.ID != lock.OwnerID {
-		return nil, fmt.Errorf("user doesn't own lock and force flag is not set")
+		return nil, errors.New("user doesn't own lock and force flag is not set")
 	}
 
 	if _, err := db.GetEngine(dbCtx).ID(id).Delete(new(LFSLock)); err != nil {
diff --git a/models/repo_transfer.go b/models/repo_transfer.go
index f20c5bcdc0..0c23d759f9 100644
--- a/models/repo_transfer.go
+++ b/models/repo_transfer.go
@@ -5,6 +5,7 @@ package models
 
 import (
 	"context"
+	"errors"
 	"fmt"
 
 	"code.gitea.io/gitea/models/db"
@@ -120,7 +121,7 @@ func DeleteRepositoryTransfer(ctx context.Context, repoID int64) error {
 func TestRepositoryReadyForTransfer(status repo_model.RepositoryStatus) error {
 	switch status {
 	case repo_model.RepositoryBeingMigrated:
-		return fmt.Errorf("repo is not ready, currently migrating")
+		return errors.New("repo is not ready, currently migrating")
 	case repo_model.RepositoryPendingTransfer:
 		return ErrRepoTransferInProgress{}
 	}

From e64e8d24ace06910aab574eb073b429f3717cc0a Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Sun, 21 Apr 2024 22:24:56 +0200
Subject: [PATCH 035/107] Fix flash on dashboard (#30572)

Fixes https://github.com/go-gitea/gitea/issues/30566, regression from
https://github.com/go-gitea/gitea/pull/30214.

(cherry picked from commit 1b1b8500aea0a17e999093e65b573ce54ae080ae)

Conflicts:
	web_src/css/base.css
	the conflict is because
	https://codeberg.org/forgejo/forgejo/pulls/3350
	skipped Remove fomantic menu module (gitea#30325)
	and it was not ported.
---
 templates/user/dashboard/dashboard.tmpl | 2 +-
 web_src/css/base.css                    | 6 ++++++
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/templates/user/dashboard/dashboard.tmpl b/templates/user/dashboard/dashboard.tmpl
index 030fd49940..415423d436 100644
--- a/templates/user/dashboard/dashboard.tmpl
+++ b/templates/user/dashboard/dashboard.tmpl
@@ -1,8 +1,8 @@
 {{template "base/head" .}}
 <div role="main" aria-label="{{.Title}}" class="page-content dashboard feeds">
 	{{template "user/dashboard/navbar" .}}
+	{{template "base/alert" .}}
 	<div class="ui container flex-container">
-		{{template "base/alert" .}}
 		<div class="flex-container-main">
 			{{template "user/heatmap" .}}
 			{{template "user/dashboard/feeds" .}}
diff --git a/web_src/css/base.css b/web_src/css/base.css
index c571280ee0..ce0b0f569a 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -636,6 +636,12 @@ img.ui.avatar,
   background: var(--color-active);
 }
 
+/* add horizontal margin to elements that are outside top-level of .flex-container or .ui.container */
+.page-content > .flash-message {
+  margin-left: var(--page-margin-x);
+  margin-right: var(--page-margin-x);
+}
+
 .ui.form .fields.error .field textarea,
 .ui.form .fields.error .field select,
 .ui.form .fields.error .field input:not([type]),

From 4e2de8bdc62b3bb8c0a63358573985ec2ab87b5e Mon Sep 17 00:00:00 2001
From: Bo-Yi Wu <appleboy.tw@gmail.com>
Date: Mon, 22 Apr 2024 06:19:59 +0800
Subject: [PATCH 036/107] fix(api): refactor branch and tag existence checks
 (#30618)

- Update branch existence check to also include tag existence check
- Adjust error message for branch/tag existence check

ref: https://github.com/go-gitea/gitea/pull/30349

---------

Signed-off-by: appleboy <appleboy.tw@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
(cherry picked from commit 6459c50278906893f3cbc2bf3e52eff65e739b37)
---
 routers/api/v1/repo/pull.go | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index eec3c49bc4..e982b5a450 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -1079,11 +1079,10 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
 	}
 
 	ctx.Repo.PullRequest.SameRepo = isSameRepo
-	log.Info("Base branch: %s", baseBranch)
-	log.Info("Repo path: %s", ctx.Repo.GitRepo.Path)
+	log.Trace("Repo path: %q, base branch: %q, head branch: %q", ctx.Repo.GitRepo.Path, baseBranch, headBranch)
 	// Check if base branch is valid.
-	if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) {
-		ctx.NotFound("IsBranchExist")
+	if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) && !ctx.Repo.GitRepo.IsTagExist(baseBranch) {
+		ctx.NotFound("BaseNotExist")
 		return nil, nil, nil, nil, "", ""
 	}
 
@@ -1146,7 +1145,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
 	}
 
 	// Check if head branch is valid.
-	if !headGitRepo.IsBranchExist(headBranch) {
+	if !headGitRepo.IsBranchExist(headBranch) && !headGitRepo.IsTagExist(headBranch) {
 		headGitRepo.Close()
 		ctx.NotFound()
 		return nil, nil, nil, nil, "", ""

From 2b45aa42f564d25d952ecc9b517a2b8713a6d5ff Mon Sep 17 00:00:00 2001
From: GiteaBot <teabot@gitea.io>
Date: Mon, 22 Apr 2024 00:25:56 +0000
Subject: [PATCH 037/107] [skip ci] Updated licenses and gitignores

(cherry picked from commit 31386dc2bb94346b5a1039f009021a4e2f5eb166)
---
 options/license/HPND-UC-export-US | 10 ++++++++++
 options/license/NCL               | 32 +++++++++++++++++++++++++++++++
 2 files changed, 42 insertions(+)
 create mode 100644 options/license/HPND-UC-export-US
 create mode 100644 options/license/NCL

diff --git a/options/license/HPND-UC-export-US b/options/license/HPND-UC-export-US
new file mode 100644
index 0000000000..015556c5f9
--- /dev/null
+++ b/options/license/HPND-UC-export-US
@@ -0,0 +1,10 @@
+Copyright (C) 1985, 1990 Regents of the University of California.
+
+Permission to use, copy, modify, and distribute this
+software and its documentation for any purpose and without
+fee is hereby granted, provided that the above copyright
+notice appear in all copies.  The University of California
+makes no representations about the suitability of this
+software for any purpose.  It is provided "as is" without
+express or implied warranty.  Export of this software outside
+of the United States of America may require an export license.
diff --git a/options/license/NCL b/options/license/NCL
new file mode 100644
index 0000000000..3bfb658c26
--- /dev/null
+++ b/options/license/NCL
@@ -0,0 +1,32 @@
+Copyright (c) 2004 the University Corporation for Atmospheric
+Research ("UCAR"). All rights reserved. Developed by NCAR's
+Computational and Information Systems Laboratory, UCAR,
+www.cisl.ucar.edu.
+
+Redistribution and use of the Software in source and binary forms,
+with or without modification, is permitted provided that the
+following conditions are met:
+
+- Neither the names of NCAR's Computational and Information Systems
+Laboratory, the University Corporation for Atmospheric Research,
+nor the names of its sponsors or contributors may be used to
+endorse or promote products derived from this Software without
+specific prior written permission.
+
+- Redistributions of source code must retain the above copyright
+notices, this list of conditions, and the disclaimer below.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions, and the disclaimer below in the
+documentation and/or other materials provided with the
+distribution.
+
+THIS 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 CONTRIBUTORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL 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 WITH THE
+SOFTWARE.

From 02316e1e408bdfcf95a0c5ae0d174e8241c17e8a Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Mon, 22 Apr 2024 12:48:14 +0200
Subject: [PATCH 038/107] Hide diff stats on empty PRs (#30629)

When a PR is empty, e.g. has neither additions nor deletions, we don't
need to show this:

<img width="125" alt="Screenshot 2024-04-21 at 23 25 38"
src="https://github.com/go-gitea/gitea/assets/115237/0b987eb5-66f5-4b9b-b5aa-7e9e267e9b52">

(cherry picked from commit 0386a42f70d1026c50697b12378f5026a63182b9)
---
 templates/repo/pulls/tab_menu.tmpl | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/templates/repo/pulls/tab_menu.tmpl b/templates/repo/pulls/tab_menu.tmpl
index c0e48928f9..a6d058f160 100644
--- a/templates/repo/pulls/tab_menu.tmpl
+++ b/templates/repo/pulls/tab_menu.tmpl
@@ -15,12 +15,14 @@
 			{{ctx.Locale.Tr "repo.pulls.tab_files"}}
 			<span class="ui small label">{{if .NumFiles}}{{.NumFiles}}{{else}}-{{end}}</span>
 		</a>
+		{{if or .Diff.TotalAddition .Diff.TotalDeletion}}
 		<span class="item tw-ml-auto tw-pr-0 tw-font-bold tw-flex tw-items-center tw-gap-2">
 			<span><span class="text green">{{if .Diff.TotalAddition}}+{{.Diff.TotalAddition}}{{end}}</span> <span class="text red">{{if .Diff.TotalDeletion}}-{{.Diff.TotalDeletion}}{{end}}</span></span>
 			<span class="diff-stats-bar">
 				<div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .Diff.TotalAddition "/" "(" .Diff.TotalAddition "+" .Diff.TotalDeletion "+" 0.0 ")"}}%"></div>
 			</span>
 		</span>
+		{{end}}
 	</div>
 	<div class="ui tabs divider"></div>
 </div>

From 31b608a1e9c0e1602b7b0242e0c35b89b8921a57 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Mon, 22 Apr 2024 13:21:06 +0200
Subject: [PATCH 039/107] Remove obsolete CSS text classes (#30576)

- `.text-thin` and `.text-italic` are not present in CSS so were doing nothing and I removed them.
- `.text.middle` was unused so I removed it.
- `.text.italic` is replaced with `tw-italic`.
- `.text.normal` had exactly one use and it wasn't even needed.
- add a `muted` class to the link to `org_profile_avatar.tmpl`.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
(cherry picked from commit aff7b7bdd285cc1fcabea774f153886e11ae9f5d)
---
 templates/org/team/members.tmpl               |  2 +-
 templates/org/team/repositories.tmpl          |  2 +-
 templates/org/team/sidebar.tmpl               |  2 +-
 templates/repo/create.tmpl                    |  6 +++---
 templates/repo/file_info.tmpl                 |  2 +-
 templates/repo/settings/collaboration.tmpl    |  4 ++--
 templates/repo/settings/options.tmpl          |  2 +-
 templates/shared/user/org_profile_avatar.tmpl |  2 +-
 web_src/css/base.css                          | 16 ----------------
 9 files changed, 11 insertions(+), 27 deletions(-)

diff --git a/templates/org/team/members.tmpl b/templates/org/team/members.tmpl
index 5719328a27..7e9a59a6bf 100644
--- a/templates/org/team/members.tmpl
+++ b/templates/org/team/members.tmpl
@@ -46,7 +46,7 @@
 							</div>
 						{{else}}
 							<div class="flex-item">
-								<span class="text grey italic">{{ctx.Locale.Tr "org.teams.members.none"}}</span>
+								<span class="text grey tw-italic">{{ctx.Locale.Tr "org.teams.members.none"}}</span>
 							</div>
 						{{end}}
 					</div>
diff --git a/templates/org/team/repositories.tmpl b/templates/org/team/repositories.tmpl
index 98b4854eb8..f5d68ce416 100644
--- a/templates/org/team/repositories.tmpl
+++ b/templates/org/team/repositories.tmpl
@@ -48,7 +48,7 @@
 							</div>
 						{{else}}
 							<div class="flex-item">
-								<span class="text grey italic">{{ctx.Locale.Tr "org.teams.repos.none"}}</span>
+								<span class="text grey tw-italic">{{ctx.Locale.Tr "org.teams.repos.none"}}</span>
 							</div>
 						{{end}}
 					</div>
diff --git a/templates/org/team/sidebar.tmpl b/templates/org/team/sidebar.tmpl
index 9311a46e38..c9f80259e2 100644
--- a/templates/org/team/sidebar.tmpl
+++ b/templates/org/team/sidebar.tmpl
@@ -22,7 +22,7 @@
 			{{if .Team.Description}}
 				{{.Team.Description}}
 			{{else}}
-				<span class="text grey italic">{{ctx.Locale.Tr "org.teams.no_desc"}}</span>
+				<span class="text grey tw-italic">{{ctx.Locale.Tr "org.teams.no_desc"}}</span>
 			{{end}}
 		</div>
 		{{if eq .Team.LowerName "owners"}}
diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl
index bcd3c16b6a..c1c8c2185e 100644
--- a/templates/repo/create.tmpl
+++ b/templates/repo/create.tmpl
@@ -65,7 +65,7 @@
 					</div>
 					<div class="inline field">
 						<label>{{ctx.Locale.Tr "repo.template"}}</label>
-						<div id="repo_template_search" class="ui search normal selection dropdown">
+						<div id="repo_template_search" class="ui search selection dropdown">
 							<input type="hidden" id="repo_template" name="repo_template" value="{{.repo_template}}">
 							<div class="default text">{{.repo_template_name}}</div>
 							<div class="menu">
@@ -119,7 +119,7 @@
 					<div id="non_template">
 						<div class="inline field">
 							<label>{{ctx.Locale.Tr "repo.issue_labels"}}</label>
-							<div class="ui search normal selection dropdown">
+							<div class="ui search selection dropdown">
 								<input type="hidden" name="issue_labels" value="{{.issueLabels}}">
 								<div class="default text">{{ctx.Locale.Tr "repo.issue_labels_helper"}}</div>
 								<div class="menu">
@@ -135,7 +135,7 @@
 
 						<div class="inline field">
 							<label>.gitignore</label>
-							<div class="ui multiple search normal selection dropdown">
+							<div class="ui multiple search selection dropdown">
 								<input type="hidden" name="gitignores" value="{{.gitignores}}">
 								<div class="default text">{{ctx.Locale.Tr "repo.repo_gitignore_helper"}}</div>
 								<div class="menu">
diff --git a/templates/repo/file_info.tmpl b/templates/repo/file_info.tmpl
index 5527991644..61cb9f4b8a 100644
--- a/templates/repo/file_info.tmpl
+++ b/templates/repo/file_info.tmpl
@@ -1,4 +1,4 @@
-<div class="file-info text grey normal tw-font-mono">
+<div class="file-info tw-font-mono">
 	{{if .FileIsSymlink}}
 		<div class="file-info-entry">
 			{{ctx.Locale.Tr "repo.symbolic_link"}}
diff --git a/templates/repo/settings/collaboration.tmpl b/templates/repo/settings/collaboration.tmpl
index 2a4ec577e7..ed4d5e7eb3 100644
--- a/templates/repo/settings/collaboration.tmpl
+++ b/templates/repo/settings/collaboration.tmpl
@@ -29,7 +29,7 @@
 									</div>
 								</div>
 							</div>
-							<button class="ui red tiny button inline text-thin delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
+							<button class="ui red tiny button inline delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
 								{{ctx.Locale.Tr "repo.settings.delete_collaborator"}}
 							</button>
 						</div>
@@ -75,7 +75,7 @@
 						</div>
 						{{if $allowedToChangeTeams}}
 							<div class="flex-item-trailing" {{if .IncludesAllRepositories}} data-tooltip-content="{{ctx.Locale.Tr "repo.settings.delete_team_tip"}}"{{end}}>
-								<button class="ui red tiny button inline text-thin delete-button {{if .IncludesAllRepositories}}disabled{{end}}" data-url="{{$.Link}}/team/delete" data-id="{{.ID}}">
+								<button class="ui red tiny button inline delete-button {{if .IncludesAllRepositories}}disabled{{end}}" data-url="{{$.Link}}/team/delete" data-id="{{.ID}}">
 										{{ctx.Locale.Tr "repo.settings.delete_collaborator"}}
 								</button>
 							</div>
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl
index aeb61d9eb3..d5a0cff342 100644
--- a/templates/repo/settings/options.tmpl
+++ b/templates/repo/settings/options.tmpl
@@ -135,7 +135,7 @@
 									<form method="post" class="tw-inline-block">
 										{{.CsrfTokenHtml}}
 										<input type="hidden" name="action" value="mirror-sync">
-										<button class="ui primary tiny button inline text-thin">{{ctx.Locale.Tr "repo.settings.sync_mirror"}}</button>
+										<button class="ui primary tiny button inline">{{ctx.Locale.Tr "repo.settings.sync_mirror"}}</button>
 									</form>
 								</td>
 							</tr>
diff --git a/templates/shared/user/org_profile_avatar.tmpl b/templates/shared/user/org_profile_avatar.tmpl
index 2ff1e40ca8..d67f133abf 100644
--- a/templates/shared/user/org_profile_avatar.tmpl
+++ b/templates/shared/user/org_profile_avatar.tmpl
@@ -4,7 +4,7 @@
 			<div class="column">
 				<div class="ui header tw-flex tw-items-center gt-word-break">
 					{{ctx.AvatarUtils.Avatar . 100}}
-					<span class="text thin grey"><a href="{{.HomeLink}}">{{.DisplayName}}</a></span>
+					<span class="text grey"><a class="muted" href="{{.HomeLink}}">{{.DisplayName}}</a></span>
 					<span class="org-visibility">
 						{{if .Visibility.IsLimited}}<div class="ui medium basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.limited_shortname"}}</div>{{end}}
 						{{if .Visibility.IsPrivate}}<div class="ui medium basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.private_shortname"}}</div>{{end}}
diff --git a/web_src/css/base.css b/web_src/css/base.css
index ce0b0f569a..4f62dd103c 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -863,14 +863,6 @@ input:-webkit-autofill:active,
   text-align: right !important;
 }
 
-.ui .text.normal {
-  font-weight: var(--font-weight-normal);
-}
-
-.ui .text.italic {
-  font-style: italic;
-}
-
 .ui .text.truncate {
   overflow-x: hidden;
   text-overflow: ellipsis;
@@ -878,14 +870,6 @@ input:-webkit-autofill:active,
   display: inline-block;
 }
 
-.ui .text.thin {
-  font-weight: var(--font-weight-normal);
-}
-
-.ui .text.middle {
-  vertical-align: middle;
-}
-
 .ui .message.flash-message {
   text-align: center;
 }

From 12b199c5e59dbb5a38ed368528c6b7b16412762b Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Mon, 22 Apr 2024 13:48:42 +0200
Subject: [PATCH 040/107] Enable more `revive` linter rules (#30608)

Noteable additions:

- `redefines-builtin-id` forbid variable names that shadow go builtins
- `empty-lines` remove unnecessary empty lines that `gofumpt` does not
remove for some reason
- `superfluous-else` eliminate more superfluous `else` branches

Rules are also sorted alphabetically and I cleaned up various parts of
`.golangci.yml`.

(cherry picked from commit 74f0c84fa4245a20ce6fb87dac1faf2aeeded2a2)

Conflicts:
	.golangci.yml
	apply the linter recommendations to Forgejo code as well
---
 .golangci.yml                                 | 59 +++++++++++--------
 cmd/hook.go                                   |  2 +-
 models/asymkey/gpg_key_object_verification.go |  1 -
 models/db/engine.go                           |  1 -
 models/issues/review.go                       |  2 -
 models/migrations/base/db.go                  |  2 -
 models/migrations/v1_11/v111.go               |  2 -
 models/migrations/v1_20/v250.go               |  4 +-
 models/migrations/v1_6/v71.go                 |  1 -
 models/migrations/v1_9/v85.go                 |  1 -
 models/organization/team.go                   |  3 +-
 models/project/board.go                       |  2 -
 models/repo/user_repo.go                      |  1 -
 models/user/user.go                           |  3 +-
 modules/auth/password/password.go             | 12 ++--
 modules/git/batch_reader.go                   |  6 +-
 modules/git/commit_reader.go                  |  3 +-
 modules/git/pipeline/lfs_nogogit.go           |  1 -
 modules/git/repo_commit.go                    |  8 +--
 modules/git/submodule.go                      |  1 -
 modules/indexer/code/bleve/bleve.go           |  2 -
 .../issues/elasticsearch/elasticsearch.go     |  1 -
 modules/log/event_format.go                   |  1 -
 modules/markup/markdown/markdown_test.go      |  2 -
 modules/markup/orgmode/orgmode.go             |  1 -
 modules/packages/rubygems/marshal.go          | 32 +++++-----
 modules/process/manager_stacktraces.go        |  1 -
 modules/repository/temp.go                    |  1 -
 modules/setting/time.go                       |  3 +-
 modules/templates/htmlrenderer.go             |  5 +-
 modules/templates/mailer.go                   |  3 +-
 modules/util/util_test.go                     |  6 +-
 routers/api/actions/artifacts.go              |  1 -
 routers/api/packages/alpine/alpine.go         |  4 +-
 routers/api/packages/conan/conan.go           |  4 +-
 routers/api/packages/conda/conda.go           |  4 +-
 routers/api/packages/container/container.go   |  6 +-
 routers/api/packages/cran/cran.go             |  4 +-
 routers/api/packages/debian/debian.go         |  4 +-
 routers/api/packages/generic/generic.go       |  4 +-
 routers/api/packages/goproxy/goproxy.go       |  4 +-
 routers/api/packages/nuget/nuget.go           |  4 +-
 routers/api/packages/rpm/rpm.go               |  4 +-
 routers/api/packages/rubygems/rubygems.go     |  4 +-
 routers/api/v1/repo/issue.go                  |  1 -
 routers/api/v1/repo/mirror.go                 |  1 -
 routers/api/v1/repo/pull.go                   |  2 -
 routers/api/v1/repo/pull_review.go            |  1 -
 routers/api/v1/repo/repo.go                   |  1 -
 routers/api/v1/repo/wiki.go                   |  1 -
 routers/private/hook_pre_receive.go           |  1 -
 routers/web/repo/actions/view.go              |  1 -
 routers/web/repo/issue.go                     |  4 --
 routers/web/repo/pull.go                      |  3 -
 routers/web/repo/pull_review.go               |  1 -
 routers/web/repo/view.go                      |  1 -
 routers/web/webfinger.go                      | 12 ----
 services/actions/notifier_helper.go           |  8 ++-
 services/auth/source/ldap/source_sync.go      |  1 -
 services/context/repo.go                      |  2 -
 services/doctor/packages_nuget.go             |  1 -
 services/gitdiff/gitdiff.go                   |  4 +-
 services/issue/commit.go                      |  9 ++-
 services/migrations/gitea_downloader.go       |  1 -
 services/migrations/gitlab.go                 |  1 -
 services/mirror/mirror_pull.go                |  7 +--
 services/pull/merge.go                        |  6 +-
 services/pull/pull.go                         |  1 -
 services/repository/adopt.go                  |  1 -
 services/repository/contributors_graph.go     |  1 -
 services/repository/files/update.go           |  2 -
 services/user/delete.go                       |  1 -
 services/user/update_test.go                  |  6 +-
 services/webhook/discord.go                   |  2 -
 services/webhook/matrix.go                    |  1 -
 tests/e2e/e2e_test.go                         |  6 +-
 tests/integration/api_notification_test.go    | 10 ++--
 tests/integration/api_packages_alpine_test.go |  1 -
 tests/integration/pull_status_test.go         |  1 -
 79 files changed, 130 insertions(+), 193 deletions(-)

diff --git a/.golangci.yml b/.golangci.yml
index 6d52f99401..6d835909fc 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,13 +1,14 @@
 linters:
+  enable-all: false
+  disable-all: true
+  fast: false
   enable:
     - bidichk
-    # - deadcode # deprecated - https://github.com/golangci/golangci-lint/issues/1841
     - depguard
     - dupl
     - errcheck
     - forbidigo
     - gocritic
-    # - gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time.
     - gofmt
     - gofumpt
     - gosimple
@@ -17,16 +18,11 @@ linters:
     - nolintlint
     - revive
     - staticcheck
-    # - structcheck # deprecated - https://github.com/golangci/golangci-lint/issues/1841
     - stylecheck
     - typecheck
     - unconvert
     - unused
-    # - varcheck # deprecated - https://github.com/golangci/golangci-lint/issues/1841
     - wastedassign
-  enable-all: false
-  disable-all: true
-  fast: false
 
 run:
   timeout: 10m
@@ -35,6 +31,9 @@ run:
     - public
     - web_src
 
+output:
+  sort-results: true
+
 linters-settings:
   stylecheck:
     checks: ["all", "-ST1005", "-ST1003"]
@@ -51,27 +50,37 @@ linters-settings:
     errorCode: 1
     warningCode: 1
     rules:
+      - name: atomic
+      - name: bare-return
       - name: blank-imports
+      - name: constant-logical-expr
       - name: context-as-argument
       - name: context-keys-type
       - name: dot-imports
+      - name: duplicated-imports
+      - name: empty-lines
+      - name: error-naming
       - name: error-return
       - name: error-strings
-      - name: error-naming
+      - name: errorf
       - name: exported
+      - name: identical-branches
       - name: if-return
       - name: increment-decrement
-      - name: var-naming
-      - name: var-declaration
+      - name: indent-error-flow
+      - name: modifies-value-receiver
       - name: package-comments
       - name: range
       - name: receiver-naming
+      - name: redefines-builtin-id
+      - name: string-of-int
+      - name: superfluous-else
       - name: time-naming
+      - name: unconditional-recursion
       - name: unexported-return
-      - name: indent-error-flow
-      - name: errorf
-      - name: duplicated-imports
-      - name: modifies-value-receiver
+      - name: unreachable-code
+      - name: var-declaration
+      - name: var-naming
   gofumpt:
     extra-rules: true
   depguard:
@@ -96,8 +105,12 @@ linters-settings:
 issues:
   max-issues-per-linter: 0
   max-same-issues: 0
+  exclude-dirs: [node_modules, public, web_src]
+  exclude-case-sensitive: true
   exclude-rules:
-    # Exclude some linters from running on tests files.
+    - path: models/db/sql_postgres_with_schema.go
+      linters:
+        - nolintlint
     - path: _test\.go
       linters:
         - gocyclo
@@ -115,19 +128,19 @@ issues:
     - path: cmd
       linters:
         - forbidigo
-    - linters:
+    - text: "webhook"
+      linters:
         - dupl
-      text: "webhook"
-    - linters:
+    - text: "`ID' should not be capitalized"
+      linters:
         - gocritic
-      text: "`ID' should not be capitalized"
-    - linters:
+    - text: "swagger"
+      linters:
         - unused
         - deadcode
-      text: "swagger"
-    - linters:
+    - text: "argument x is overwritten before first use"
+      linters:
         - staticcheck
-      text: "argument x is overwritten before first use"
     - text: "commentFormatting: put a space between `//` and comment text"
       linters:
         - gocritic
diff --git a/cmd/hook.go b/cmd/hook.go
index 81955e753b..ff3059f9df 100644
--- a/cmd/hook.go
+++ b/cmd/hook.go
@@ -482,7 +482,7 @@ func hookPrintResults(results []private.HookPostReceiveBranchResult) {
 			fmt.Fprintf(os.Stderr, "  %s\n", res.URL)
 		}
 		fmt.Fprintln(os.Stderr, "")
-		os.Stderr.Sync()
+		_ = os.Stderr.Sync()
 	}
 }
 
diff --git a/models/asymkey/gpg_key_object_verification.go b/models/asymkey/gpg_key_object_verification.go
index 67764ffc58..e5c31a74a7 100644
--- a/models/asymkey/gpg_key_object_verification.go
+++ b/models/asymkey/gpg_key_object_verification.go
@@ -94,7 +94,6 @@ func ParseObjectWithSignature(ctx context.Context, c *GitObject) *ObjectVerifica
 					Reason:         "gpg.error.no_committer_account",
 				}
 			}
-
 		}
 	}
 
diff --git a/models/db/engine.go b/models/db/engine.go
index 1f0cd1a5dd..b7146e87f2 100755
--- a/models/db/engine.go
+++ b/models/db/engine.go
@@ -236,7 +236,6 @@ func NamesToBean(names ...string) ([]any, error) {
 	// Need to map provided names to beans...
 	beanMap := make(map[string]any)
 	for _, bean := range tables {
-
 		beanMap[strings.ToLower(reflect.Indirect(reflect.ValueOf(bean)).Type().Name())] = bean
 		beanMap[strings.ToLower(x.TableName(bean))] = bean
 		beanMap[strings.ToLower(x.TableName(bean, true))] = bean
diff --git a/models/issues/review.go b/models/issues/review.go
index 92764db4d1..3c6934b060 100644
--- a/models/issues/review.go
+++ b/models/issues/review.go
@@ -345,11 +345,9 @@ func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error
 				return nil, err
 			}
 		}
-
 	} else if opts.ReviewerTeam != nil {
 		review.Type = ReviewTypeRequest
 		review.ReviewerTeamID = opts.ReviewerTeam.ID
-
 	} else {
 		return nil, fmt.Errorf("provide either reviewer or reviewer team")
 	}
diff --git a/models/migrations/base/db.go b/models/migrations/base/db.go
index a76ce29deb..e584793385 100644
--- a/models/migrations/base/db.go
+++ b/models/migrations/base/db.go
@@ -214,7 +214,6 @@ func RecreateTable(sess *xorm.Session, bean any) error {
 				return err
 			}
 			sequenceMap[sequence] = sequenceData
-
 		}
 
 		// CASCADE causes postgres to drop all the constraints on the old table
@@ -279,7 +278,6 @@ func RecreateTable(sess *xorm.Session, bean any) error {
 					return err
 				}
 			}
-
 		}
 
 	default:
diff --git a/models/migrations/v1_11/v111.go b/models/migrations/v1_11/v111.go
index c18d7adae1..cc3dc0d545 100644
--- a/models/migrations/v1_11/v111.go
+++ b/models/migrations/v1_11/v111.go
@@ -263,7 +263,6 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
 		for _, u := range units {
 			var found bool
 			for _, team := range teams {
-
 				var teamU []*TeamUnit
 				var unitEnabled bool
 				err = sess.Where("team_id = ?", team.ID).Find(&teamU)
@@ -332,7 +331,6 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
 		}
 
 		if !protectedBranch.EnableApprovalsWhitelist {
-
 			perm, err := getUserRepoPermission(sess, baseRepo, reviewer)
 			if err != nil {
 				return false, err
diff --git a/models/migrations/v1_20/v250.go b/models/migrations/v1_20/v250.go
index a09957b291..86388ef0b8 100644
--- a/models/migrations/v1_20/v250.go
+++ b/models/migrations/v1_20/v250.go
@@ -104,7 +104,7 @@ func ChangeContainerMetadataMultiArch(x *xorm.Engine) error {
 
 		// Convert to new metadata format
 
-		new := &MetadataNew{
+		newMetadata := &MetadataNew{
 			Type:             old.Type,
 			IsTagged:         old.IsTagged,
 			Platform:         old.Platform,
@@ -119,7 +119,7 @@ func ChangeContainerMetadataMultiArch(x *xorm.Engine) error {
 			Manifests:        manifests,
 		}
 
-		metadataJSON, err := json.Marshal(new)
+		metadataJSON, err := json.Marshal(newMetadata)
 		if err != nil {
 			return err
 		}
diff --git a/models/migrations/v1_6/v71.go b/models/migrations/v1_6/v71.go
index 4e50ca9219..586187228b 100644
--- a/models/migrations/v1_6/v71.go
+++ b/models/migrations/v1_6/v71.go
@@ -61,7 +61,6 @@ func AddScratchHash(x *xorm.Engine) error {
 			if _, err := sess.ID(tfa.ID).Cols("scratch_salt, scratch_hash").Update(tfa); err != nil {
 				return fmt.Errorf("couldn't add in scratch_hash and scratch_salt: %w", err)
 			}
-
 		}
 	}
 
diff --git a/models/migrations/v1_9/v85.go b/models/migrations/v1_9/v85.go
index 9419ee1aae..a23d7c5d6e 100644
--- a/models/migrations/v1_9/v85.go
+++ b/models/migrations/v1_9/v85.go
@@ -81,7 +81,6 @@ func HashAppToken(x *xorm.Engine) error {
 			if _, err := sess.ID(token.ID).Cols("token_hash, token_salt, token_last_eight, sha1").Update(token); err != nil {
 				return fmt.Errorf("couldn't add in sha1, token_hash, token_salt and token_last_eight: %w", err)
 			}
-
 		}
 	}
 
diff --git a/models/organization/team.go b/models/organization/team.go
index 17db11c42d..1b737c2d3d 100644
--- a/models/organization/team.go
+++ b/models/organization/team.go
@@ -222,9 +222,8 @@ func GetTeamIDsByNames(ctx context.Context, orgID int64, names []string, ignoreN
 		if err != nil {
 			if ignoreNonExistent {
 				continue
-			} else {
-				return nil, err
 			}
+			return nil, err
 		}
 		ids = append(ids, u.ID)
 	}
diff --git a/models/project/board.go b/models/project/board.go
index 5f142a356c..7faabc52c5 100644
--- a/models/project/board.go
+++ b/models/project/board.go
@@ -110,13 +110,11 @@ func createBoardsForProjectsType(ctx context.Context, project *Project) error {
 	var items []string
 
 	switch project.BoardType {
-
 	case BoardTypeBugTriage:
 		items = setting.Project.ProjectBoardBugTriageType
 
 	case BoardTypeBasicKanban:
 		items = setting.Project.ProjectBoardBasicKanbanType
-
 	case BoardTypeNone:
 		fallthrough
 	default:
diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go
index 219f4ff25d..d3fbd961bd 100644
--- a/models/repo/user_repo.go
+++ b/models/repo/user_repo.go
@@ -135,7 +135,6 @@ func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64)
 			// the owner of a private repo needs to be explicitly added.
 			cond = cond.Or(builder.Eq{"`user`.id": repo.Owner.ID})
 		}
-
 	} else {
 		// This is a "public" repository:
 		// Any user that has read access, is a watcher or organization member can be requested to review
diff --git a/models/user/user.go b/models/user/user.go
index df129a5efe..10c4915f5e 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -1007,9 +1007,8 @@ func GetUserIDsByNames(ctx context.Context, names []string, ignoreNonExistent bo
 		if err != nil {
 			if ignoreNonExistent {
 				continue
-			} else {
-				return nil, err
 			}
+			return nil, err
 		}
 		ids = append(ids, u.ID)
 	}
diff --git a/modules/auth/password/password.go b/modules/auth/password/password.go
index 27074358a9..85f9780709 100644
--- a/modules/auth/password/password.go
+++ b/modules/auth/password/password.go
@@ -63,16 +63,16 @@ func NewComplexity() {
 func setupComplexity(values []string) {
 	if len(values) != 1 || values[0] != "off" {
 		for _, val := range values {
-			if complex, ok := charComplexities[val]; ok {
-				validChars += complex.ValidChars
-				requiredList = append(requiredList, complex)
+			if complexity, ok := charComplexities[val]; ok {
+				validChars += complexity.ValidChars
+				requiredList = append(requiredList, complexity)
 			}
 		}
 		if len(requiredList) == 0 {
 			// No valid character classes found; use all classes as default
-			for _, complex := range charComplexities {
-				validChars += complex.ValidChars
-				requiredList = append(requiredList, complex)
+			for _, complexity := range charComplexities {
+				validChars += complexity.ValidChars
+				requiredList = append(requiredList, complexity)
 			}
 		}
 	}
diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go
index 043dbb44bd..c988d6ab86 100644
--- a/modules/git/batch_reader.go
+++ b/modules/git/batch_reader.go
@@ -307,10 +307,10 @@ func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBu
 
 	// Deal with the binary hash
 	idx = 0
-	len := objectFormat.FullLength() / 2
-	for idx < len {
+	length := objectFormat.FullLength() / 2
+	for idx < length {
 		var read int
-		read, err = rd.Read(shaBuf[idx:len])
+		read, err = rd.Read(shaBuf[idx:length])
 		n += read
 		if err != nil {
 			return mode, fname, sha, n, err
diff --git a/modules/git/commit_reader.go b/modules/git/commit_reader.go
index 49159cc418..8e2523d7fb 100644
--- a/modules/git/commit_reader.go
+++ b/modules/git/commit_reader.go
@@ -49,9 +49,8 @@ readLoop:
 			if len(line) > 0 && line[0] == ' ' {
 				_, _ = signatureSB.Write(line[1:])
 				continue
-			} else {
-				pgpsig = false
 			}
+			pgpsig = false
 		}
 
 		if !message {
diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go
index a3ee883968..349cfbd9ce 100644
--- a/modules/git/pipeline/lfs_nogogit.go
+++ b/modules/git/pipeline/lfs_nogogit.go
@@ -213,7 +213,6 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
 				errChan <- err
 				break
 			}
-
 		}
 	}()
 
diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go
index 44273d2253..f9168bef7e 100644
--- a/modules/git/repo_commit.go
+++ b/modules/git/repo_commit.go
@@ -251,18 +251,18 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
 		return nil, err
 	}
 
-	len := objectFormat.FullLength()
+	length := objectFormat.FullLength()
 	commits := []*Commit{}
-	shaline := make([]byte, len+1)
+	shaline := make([]byte, length+1)
 	for {
 		n, err := io.ReadFull(stdoutReader, shaline)
-		if err != nil || n < len {
+		if err != nil || n < length {
 			if err == io.EOF {
 				err = nil
 			}
 			return commits, err
 		}
-		objectID, err := NewIDFromString(string(shaline[0:len]))
+		objectID, err := NewIDFromString(string(shaline[0:length]))
 		if err != nil {
 			return nil, err
 		}
diff --git a/modules/git/submodule.go b/modules/git/submodule.go
index 37813ea4c7..b99c81582b 100644
--- a/modules/git/submodule.go
+++ b/modules/git/submodule.go
@@ -64,7 +64,6 @@ func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string {
 		// ex: git@try.gitea.io:go-gitea/gitea
 		match := scpSyntax.FindAllStringSubmatch(refURI, -1)
 		if len(match) > 0 {
-
 			m := match[0]
 			refHostname := m[2]
 			pth := m[3]
diff --git a/modules/indexer/code/bleve/bleve.go b/modules/indexer/code/bleve/bleve.go
index ff0e37ca29..66724a3445 100644
--- a/modules/indexer/code/bleve/bleve.go
+++ b/modules/indexer/code/bleve/bleve.go
@@ -193,7 +193,6 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository, batch
 func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
 	batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize)
 	if len(changes.Updates) > 0 {
-
 		// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
 		if err := git.EnsureValidGitRepository(ctx, repo.RepoPath()); err != nil {
 			log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err)
@@ -337,7 +336,6 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
 		if result, err = b.inner.Indexer.Search(facetRequest); err != nil {
 			return 0, nil, nil, err
 		}
-
 	}
 	languagesFacet := result.Facets["languages"]
 	for _, term := range languagesFacet.Terms.Terms() {
diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go
index 53b383c8d5..c7cb59f2cf 100644
--- a/modules/indexer/issues/elasticsearch/elasticsearch.go
+++ b/modules/indexer/issues/elasticsearch/elasticsearch.go
@@ -145,7 +145,6 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
 	query := elastic.NewBoolQuery()
 
 	if options.Keyword != "" {
-
 		searchType := esMultiMatchTypePhrasePrefix
 		if options.IsFuzzyKeyword {
 			searchType = esMultiMatchTypeBestFields
diff --git a/modules/log/event_format.go b/modules/log/event_format.go
index 524ca3dd87..d9dbebf831 100644
--- a/modules/log/event_format.go
+++ b/modules/log/event_format.go
@@ -125,7 +125,6 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms
 		if mode.Colorize {
 			buf = append(buf, resetBytes...)
 		}
-
 	}
 	if flags&(Lshortfile|Llongfile) != 0 {
 		if mode.Colorize {
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index 3e1d75b291..278494d95c 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -460,7 +460,6 @@ func TestColorPreview(t *testing.T) {
 		res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
 		assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
 		assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
-
 	}
 
 	negativeTests := []string{
@@ -555,7 +554,6 @@ func TestMathBlock(t *testing.T) {
 		res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
 		assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
 		assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
-
 	}
 }
 
diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go
index 8efe4e395d..391ee6c12b 100644
--- a/modules/markup/orgmode/orgmode.go
+++ b/modules/markup/orgmode/orgmode.go
@@ -147,7 +147,6 @@ func (r *Writer) resolveLink(node org.Node) string {
 	}
 	if len(link) > 0 && !markup.IsLinkStr(link) &&
 		link[0] != '#' && !strings.HasPrefix(link, mailto) {
-
 		var base string
 		if r.Ctx.IsWiki {
 			base = r.Ctx.Links.WikiLink()
diff --git a/modules/packages/rubygems/marshal.go b/modules/packages/rubygems/marshal.go
index 8878dcf973..4e6a5fc5f8 100644
--- a/modules/packages/rubygems/marshal.go
+++ b/modules/packages/rubygems/marshal.go
@@ -147,35 +147,35 @@ func (e *MarshalEncoder) marshalIntInternal(i int64) error {
 		return e.w.WriteByte(byte(i - 5))
 	}
 
-	var len int
+	var length int
 	if 122 < i && i <= 0xff {
-		len = 1
+		length = 1
 	} else if 0xff < i && i <= 0xffff {
-		len = 2
+		length = 2
 	} else if 0xffff < i && i <= 0xffffff {
-		len = 3
+		length = 3
 	} else if 0xffffff < i && i <= 0x3fffffff {
-		len = 4
+		length = 4
 	} else if -0x100 <= i && i < -123 {
-		len = -1
+		length = -1
 	} else if -0x10000 <= i && i < -0x100 {
-		len = -2
+		length = -2
 	} else if -0x1000000 <= i && i < -0x100000 {
-		len = -3
+		length = -3
 	} else if -0x40000000 <= i && i < -0x1000000 {
-		len = -4
+		length = -4
 	} else {
 		return ErrInvalidIntRange
 	}
 
-	if err := e.w.WriteByte(byte(len)); err != nil {
+	if err := e.w.WriteByte(byte(length)); err != nil {
 		return err
 	}
-	if len < 0 {
-		len = -len
+	if length < 0 {
+		length = -length
 	}
 
-	for c := 0; c < len; c++ {
+	for c := 0; c < length; c++ {
 		if err := e.w.WriteByte(byte(i >> uint(8*c) & 0xff)); err != nil {
 			return err
 		}
@@ -244,13 +244,13 @@ func (e *MarshalEncoder) marshalArray(arr reflect.Value) error {
 		return err
 	}
 
-	len := arr.Len()
+	length := arr.Len()
 
-	if err := e.marshalIntInternal(int64(len)); err != nil {
+	if err := e.marshalIntInternal(int64(length)); err != nil {
 		return err
 	}
 
-	for i := 0; i < len; i++ {
+	for i := 0; i < length; i++ {
 		if err := e.marshal(arr.Index(i).Interface()); err != nil {
 			return err
 		}
diff --git a/modules/process/manager_stacktraces.go b/modules/process/manager_stacktraces.go
index 49bd5071f6..e260893113 100644
--- a/modules/process/manager_stacktraces.go
+++ b/modules/process/manager_stacktraces.go
@@ -339,7 +339,6 @@ func (pm *Manager) ProcessStacktraces(flat, noSystem bool) ([]*Process, int, int
 	}
 	sort.Slice(processes, after(processes))
 	if !flat {
-
 		var sortChildren func(process *Process)
 
 		sortChildren = func(process *Process) {
diff --git a/modules/repository/temp.go b/modules/repository/temp.go
index 53646718e0..04faa9db3d 100644
--- a/modules/repository/temp.go
+++ b/modules/repository/temp.go
@@ -32,7 +32,6 @@ func CreateTemporaryPath(prefix string) (string, error) {
 	if err != nil {
 		log.Error("Unable to create temporary directory: %s-*.git (%v)", prefix, err)
 		return "", fmt.Errorf("Failed to create dir %s-*.git: %w", prefix, err)
-
 	}
 	return basePath, nil
 }
diff --git a/modules/setting/time.go b/modules/setting/time.go
index 6d2aa80f5b..39acba12ef 100644
--- a/modules/setting/time.go
+++ b/modules/setting/time.go
@@ -19,9 +19,8 @@ func loadTimeFrom(rootCfg ConfigProvider) {
 		DefaultUILocation, err = time.LoadLocation(zone)
 		if err != nil {
 			log.Fatal("Load time zone failed: %v", err)
-		} else {
-			log.Info("Default UI Location is %v", zone)
 		}
+		log.Info("Default UI Location is %v", zone)
 	}
 	if DefaultUILocation == nil {
 		DefaultUILocation = time.Local
diff --git a/modules/templates/htmlrenderer.go b/modules/templates/htmlrenderer.go
index 8661653adf..55a55dd7f4 100644
--- a/modules/templates/htmlrenderer.go
+++ b/modules/templates/htmlrenderer.go
@@ -138,10 +138,9 @@ func wrapTmplErrMsg(msg string) {
 	if setting.IsProd {
 		// in prod mode, Forgejo must have correct templates to run
 		log.Fatal("Forgejo can't run with template errors: %s", msg)
-	} else {
-		// in dev mode, do not need to really exit, because the template errors could be fixed by developer soon and the templates get reloaded
-		log.Error("There are template errors but Forgejo continues to run in dev mode: %s", msg)
 	}
+	// in dev mode, do not need to really exit, because the template errors could be fixed by developer soon and the templates get reloaded
+	log.Error("There are template errors but Forgejo continues to run in dev mode: %s", msg)
 }
 
 type templateErrorPrettier struct {
diff --git a/modules/templates/mailer.go b/modules/templates/mailer.go
index f1832cba0e..7c97e1ea89 100644
--- a/modules/templates/mailer.go
+++ b/modules/templates/mailer.go
@@ -84,9 +84,8 @@ func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) {
 			if err = buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, tmplName, content); err != nil {
 				if firstRun {
 					log.Fatal("Failed to parse mail template, err: %v", err)
-				} else {
-					log.Error("Failed to parse mail template, err: %v", err)
 				}
+				log.Error("Failed to parse mail template, err: %v", err)
 			}
 		}
 	}
diff --git a/modules/util/util_test.go b/modules/util/util_test.go
index 5c5b13d04b..de8f065cad 100644
--- a/modules/util/util_test.go
+++ b/modules/util/util_test.go
@@ -121,9 +121,9 @@ func Test_NormalizeEOL(t *testing.T) {
 }
 
 func Test_RandomInt(t *testing.T) {
-	int, err := CryptoRandomInt(255)
-	assert.True(t, int >= 0)
-	assert.True(t, int <= 255)
+	randInt, err := CryptoRandomInt(255)
+	assert.True(t, randInt >= 0)
+	assert.True(t, randInt <= 255)
 	assert.NoError(t, err)
 }
 
diff --git a/routers/api/actions/artifacts.go b/routers/api/actions/artifacts.go
index 2f8b3d0210..72ff704687 100644
--- a/routers/api/actions/artifacts.go
+++ b/routers/api/actions/artifacts.go
@@ -144,7 +144,6 @@ func ArtifactContexter() func(next http.Handler) http.Handler {
 
 			var task *actions.ActionTask
 			if err == nil {
-
 				task, err = actions.GetTaskByID(req.Context(), tID)
 				if err != nil {
 					log.Error("Error runner api getting task by ID: %v", err)
diff --git a/routers/api/packages/alpine/alpine.go b/routers/api/packages/alpine/alpine.go
index cf0fe6c07c..481cf70d33 100644
--- a/routers/api/packages/alpine/alpine.go
+++ b/routers/api/packages/alpine/alpine.go
@@ -144,12 +144,12 @@ func UploadPackageFile(ctx *context.Context) {
 		return
 	}
 
-	upload, close, err := ctx.UploadStream()
+	upload, needToClose, err := ctx.UploadStream()
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
 	}
-	if close {
+	if needToClose {
 		defer upload.Close()
 	}
 
diff --git a/routers/api/packages/conan/conan.go b/routers/api/packages/conan/conan.go
index c45e085a4d..07ea3eda34 100644
--- a/routers/api/packages/conan/conan.go
+++ b/routers/api/packages/conan/conan.go
@@ -310,12 +310,12 @@ func uploadFile(ctx *context.Context, fileFilter container.Set[string], fileKey
 		return
 	}
 
-	upload, close, err := ctx.UploadStream()
+	upload, needToClose, err := ctx.UploadStream()
 	if err != nil {
 		apiError(ctx, http.StatusBadRequest, err)
 		return
 	}
-	if close {
+	if needToClose {
 		defer upload.Close()
 	}
 
diff --git a/routers/api/packages/conda/conda.go b/routers/api/packages/conda/conda.go
index 30c80fc15e..c7e4544d52 100644
--- a/routers/api/packages/conda/conda.go
+++ b/routers/api/packages/conda/conda.go
@@ -174,12 +174,12 @@ func EnumeratePackages(ctx *context.Context) {
 }
 
 func UploadPackageFile(ctx *context.Context) {
-	upload, close, err := ctx.UploadStream()
+	upload, needToClose, err := ctx.UploadStream()
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
 	}
-	if close {
+	if needToClose {
 		defer upload.Close()
 	}
 
diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go
index e519766142..2cb16daebc 100644
--- a/routers/api/packages/container/container.go
+++ b/routers/api/packages/container/container.go
@@ -385,9 +385,9 @@ func EndUploadBlob(ctx *context.Context) {
 		}
 		return
 	}
-	close := true
+	doClose := true
 	defer func() {
-		if close {
+		if doClose {
 			uploader.Close()
 		}
 	}()
@@ -427,7 +427,7 @@ func EndUploadBlob(ctx *context.Context) {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
 	}
-	close = false
+	doClose = false
 
 	if err := container_service.RemoveBlobUploadByID(ctx, uploader.ID); err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
diff --git a/routers/api/packages/cran/cran.go b/routers/api/packages/cran/cran.go
index 2cec75294f..f1d616724a 100644
--- a/routers/api/packages/cran/cran.go
+++ b/routers/api/packages/cran/cran.go
@@ -151,12 +151,12 @@ func UploadBinaryPackageFile(ctx *context.Context) {
 }
 
 func uploadPackageFile(ctx *context.Context, compositeKey string, properties map[string]string) {
-	upload, close, err := ctx.UploadStream()
+	upload, needToClose, err := ctx.UploadStream()
 	if err != nil {
 		apiError(ctx, http.StatusBadRequest, err)
 		return
 	}
-	if close {
+	if needToClose {
 		defer upload.Close()
 	}
 
diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go
index 241de3ac5d..8c05476cbc 100644
--- a/routers/api/packages/debian/debian.go
+++ b/routers/api/packages/debian/debian.go
@@ -127,12 +127,12 @@ func UploadPackageFile(ctx *context.Context) {
 		return
 	}
 
-	upload, close, err := ctx.UploadStream()
+	upload, needToClose, err := ctx.UploadStream()
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
 	}
-	if close {
+	if needToClose {
 		defer upload.Close()
 	}
 
diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go
index 8232931134..e66f3ee676 100644
--- a/routers/api/packages/generic/generic.go
+++ b/routers/api/packages/generic/generic.go
@@ -90,12 +90,12 @@ func UploadPackage(ctx *context.Context) {
 		return
 	}
 
-	upload, close, err := ctx.UploadStream()
+	upload, needToClose, err := ctx.UploadStream()
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
 	}
-	if close {
+	if needToClose {
 		defer upload.Close()
 	}
 
diff --git a/routers/api/packages/goproxy/goproxy.go b/routers/api/packages/goproxy/goproxy.go
index d658066bb4..56a07dbd43 100644
--- a/routers/api/packages/goproxy/goproxy.go
+++ b/routers/api/packages/goproxy/goproxy.go
@@ -154,12 +154,12 @@ func resolvePackage(ctx *context.Context, ownerID int64, name, version string) (
 }
 
 func UploadPackage(ctx *context.Context) {
-	upload, close, err := ctx.UploadStream()
+	upload, needToClose, err := ctx.UploadStream()
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
 	}
-	if close {
+	if needToClose {
 		defer upload.Close()
 	}
 
diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go
index 09156ece6b..26b0ae226e 100644
--- a/routers/api/packages/nuget/nuget.go
+++ b/routers/api/packages/nuget/nuget.go
@@ -594,13 +594,13 @@ func UploadSymbolPackage(ctx *context.Context) {
 func processUploadedFile(ctx *context.Context, expectedType nuget_module.PackageType) (*nuget_module.Package, *packages_module.HashedBuffer, []io.Closer) {
 	closables := make([]io.Closer, 0, 2)
 
-	upload, close, err := ctx.UploadStream()
+	upload, needToClose, err := ctx.UploadStream()
 	if err != nil {
 		apiError(ctx, http.StatusBadRequest, err)
 		return nil, nil, closables
 	}
 
-	if close {
+	if needToClose {
 		closables = append(closables, upload)
 	}
 
diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go
index 4de361c214..c59366992c 100644
--- a/routers/api/packages/rpm/rpm.go
+++ b/routers/api/packages/rpm/rpm.go
@@ -117,12 +117,12 @@ func GetRepositoryFile(ctx *context.Context) {
 }
 
 func UploadPackageFile(ctx *context.Context) {
-	upload, close, err := ctx.UploadStream()
+	upload, needToClose, err := ctx.UploadStream()
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
 	}
-	if close {
+	if needToClose {
 		defer upload.Close()
 	}
 
diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go
index d2fbcd01f0..ba5f4de080 100644
--- a/routers/api/packages/rubygems/rubygems.go
+++ b/routers/api/packages/rubygems/rubygems.go
@@ -197,12 +197,12 @@ func DownloadPackageFile(ctx *context.Context) {
 
 // UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
 func UploadPackageFile(ctx *context.Context) {
-	upload, close, err := ctx.UploadStream()
+	upload, needToClose, err := ctx.UploadStream()
 	if err != nil {
 		apiError(ctx, http.StatusBadRequest, err)
 		return
 	}
-	if close {
+	if needToClose {
 		defer upload.Close()
 	}
 
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index 0d304dd66d..35c1fdcc0c 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -217,7 +217,6 @@ func SearchIssues(ctx *context.APIContext) {
 
 	var includedAnyLabels []int64
 	{
-
 		labels := ctx.FormTrim("labels")
 		var includedLabelNames []string
 		if len(labels) > 0 {
diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go
index 864644e1ef..2a896de4fe 100644
--- a/routers/api/v1/repo/mirror.go
+++ b/routers/api/v1/repo/mirror.go
@@ -180,7 +180,6 @@ func ListPushMirrors(ctx *context.APIContext) {
 		if err == nil {
 			responsePushMirrors = append(responsePushMirrors, m)
 		}
-
 	}
 	ctx.SetLinkHeader(len(responsePushMirrors), utils.GetListOptions(ctx).PageSize)
 	ctx.SetTotalCountHeader(count)
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index e982b5a450..852ec78ade 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -1058,7 +1058,6 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
 		isSameRepo = true
 		headUser = ctx.Repo.Owner
 		headBranch = headInfos[0]
-
 	} else if len(headInfos) == 2 {
 		headUser, err = user_model.GetUserByName(ctx, headInfos[0])
 		if err != nil {
@@ -1072,7 +1071,6 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
 		headBranch = headInfos[1]
 		// The head repository can also point to the same repo
 		isSameRepo = ctx.Repo.Owner.ID == headUser.ID
-
 	} else {
 		ctx.NotFound()
 		return nil, nil, nil, nil, "", ""
diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go
index 77c0d25e2a..39e1d487fa 100644
--- a/routers/api/v1/repo/pull_review.go
+++ b/routers/api/v1/repo/pull_review.go
@@ -875,7 +875,6 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions
 	}
 
 	if ctx.Repo.Repository.Owner.IsOrganization() && len(opts.TeamReviewers) > 0 {
-
 		teamReviewers := make([]*organization.Team, 0, len(opts.TeamReviewers))
 		for _, t := range opts.TeamReviewers {
 			var teamReviewer *organization.Team
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 6692633fab..66eb227c19 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -1094,7 +1094,6 @@ func updateMirror(ctx *context.APIContext, opts api.EditRepoOption) error {
 
 	// update MirrorInterval
 	if opts.MirrorInterval != nil {
-
 		// MirrorInterval should be a duration
 		interval, err := time.ParseDuration(*opts.MirrorInterval)
 		if err != nil {
diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go
index b6f51cdadc..1b92c7bceb 100644
--- a/routers/api/v1/repo/wiki.go
+++ b/routers/api/v1/repo/wiki.go
@@ -478,7 +478,6 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error)
 func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) {
 	wikiRepo, err := gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
 	if err != nil {
-
 		if git.IsErrNotExist(err) || err.Error() == "no such file or directory" {
 			ctx.NotFound(err)
 		} else {
diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go
index cb356a184a..da9fa20082 100644
--- a/routers/private/hook_pre_receive.go
+++ b/routers/private/hook_pre_receive.go
@@ -259,7 +259,6 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
 				UserMsg: fmt.Sprintf("branch %s is protected from force push", branchName),
 			})
 			return
-
 		}
 	}
 
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index fa687bbb93..7d70d9f2ac 100644
--- a/routers/web/repo/actions/view.go
+++ b/routers/web/repo/actions/view.go
@@ -689,7 +689,6 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
 	writer := zip.NewWriter(ctx.Resp)
 	defer writer.Close()
 	for _, art := range artifacts {
-
 		f, err := storage.ActionsArtifacts.Open(art.StoragePath)
 		if err != nil {
 			ctx.Error(http.StatusInternalServerError, err.Error())
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index b0b4b65331..97f2195116 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -927,7 +927,6 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles
 					}
 				}
 			}
-
 		}
 
 		if template.Ref != "" && !strings.HasPrefix(template.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/<ref>
@@ -1680,7 +1679,6 @@ func ViewIssue(ctx *context.Context) {
 			if comment.ProjectID > 0 && comment.Project == nil {
 				comment.Project = ghostProject
 			}
-
 		} else if comment.Type == issues_model.CommentTypeAssignees || comment.Type == issues_model.CommentTypeReviewRequest {
 			if err = comment.LoadAssigneeUserAndTeam(ctx); err != nil {
 				ctx.ServerError("LoadAssigneeUserAndTeam", err)
@@ -2605,7 +2603,6 @@ func SearchIssues(ctx *context.Context) {
 
 	var includedAnyLabels []int64
 	{
-
 		labels := ctx.FormTrim("labels")
 		var includedLabelNames []string
 		if len(labels) > 0 {
@@ -2993,7 +2990,6 @@ func NewComment(ctx *context.Context) {
 		if (ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) || (ctx.IsSigned && issue.IsPoster(ctx.Doer.ID))) &&
 			(form.Status == "reopen" || form.Status == "close") &&
 			!(issue.IsPull && issue.PullRequest.HasMerged) {
-
 			// Duplication and conflict check should apply to reopen pull request.
 			var pr *issues_model.PullRequest
 
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index c6c6142534..3f10034c6b 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -670,7 +670,6 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
 	}
 
 	if pb != nil && pb.EnableStatusCheck {
-
 		var missingRequiredChecks []string
 		for _, requiredContext := range pb.StatusCheckContexts {
 			contextFound := false
@@ -873,7 +872,6 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
 
 	// Validate the given commit sha to show (if any passed)
 	if willShowSpecifiedCommit || willShowSpecifiedCommitRange {
-
 		foundStartCommit := len(specifiedStartCommit) == 0
 		foundEndCommit := len(specifiedEndCommit) == 0
 
@@ -1185,7 +1183,6 @@ func UpdatePullRequest(ctx *context.Context) {
 			ctx.Flash.Error(flashError)
 			ctx.Redirect(issue.Link())
 			return
-
 		}
 		ctx.Flash.Error(err.Error())
 		ctx.Redirect(issue.Link())
diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go
index 81af2cbb51..e8a3c48d7f 100644
--- a/routers/web/repo/pull_review.go
+++ b/routers/web/repo/pull_review.go
@@ -302,7 +302,6 @@ func UpdateViewedFiles(ctx *context.Context) {
 
 	updatedFiles := make(map[string]pull_model.ViewedState, len(data.Files))
 	for file, viewed := range data.Files {
-
 		// Only unviewed and viewed are possible, has-changed can not be set from the outside
 		state := pull_model.Unviewed
 		if viewed {
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index e98cd46c4e..2644b27229 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -352,7 +352,6 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
 	// or of directory if not in root directory.
 	ctx.Data["LatestCommit"] = latestCommit
 	if latestCommit != nil {
-
 		verification := asymkey_model.ParseCommitWithSignature(ctx, latestCommit)
 
 		if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
diff --git a/routers/web/webfinger.go b/routers/web/webfinger.go
index 099f6236a6..1f3de70db0 100644
--- a/routers/web/webfinger.go
+++ b/routers/web/webfinger.go
@@ -102,23 +102,11 @@ func WebfingerQuery(ctx *context.Context) {
 			default:
 				ctx.Error(http.StatusNotFound)
 				return
-
-			}
-		case 4:
-			//nolint:gocritic
-			if parts[3] == "teams" {
-				ctx.Error(http.StatusNotFound)
-				return
-
-			} else {
-				ctx.Error(http.StatusNotFound)
-				return
 			}
 
 		default:
 			ctx.Error(http.StatusNotFound)
 			return
-
 		}
 
 	default:
diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go
index 365212d9c2..78f413c214 100644
--- a/services/actions/notifier_helper.go
+++ b/services/actions/notifier_helper.go
@@ -329,13 +329,15 @@ func handleWorkflows(
 			TriggerEvent:      dwf.TriggerEvent.Name,
 			Status:            actions_model.StatusWaiting,
 		}
-		if need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer); err != nil {
+
+		need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer)
+		if err != nil {
 			log.Error("check if need approval for repo %d with user %d: %v", input.Repo.ID, input.Doer.ID, err)
 			continue
-		} else {
-			run.NeedApproval = need
 		}
 
+		run.NeedApproval = need
+
 		if err := run.LoadAttributes(ctx); err != nil {
 			log.Error("LoadAttributes: %v", err)
 			continue
diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go
index 7c5d3da595..1f70edaa82 100644
--- a/services/auth/source/ldap/source_sync.go
+++ b/services/auth/source/ldap/source_sync.go
@@ -159,7 +159,6 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
 				!strings.EqualFold(usr.Email, su.Mail) ||
 				usr.FullName != fullName ||
 				!usr.IsActive {
-
 				log.Trace("SyncExternalUsers[%s]: Updating user %s", source.authSource.Name, usr.Name)
 
 				opts := &user_service.UpdateOptions{
diff --git a/services/context/repo.go b/services/context/repo.go
index 43eeab8098..3e30f2ba97 100644
--- a/services/context/repo.go
+++ b/services/context/repo.go
@@ -848,7 +848,6 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
 	case RepoRefBranch:
 		ref := getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsBranchExist)
 		if len(ref) == 0 {
-
 			// check if ref is HEAD
 			parts := strings.Split(path, "/")
 			if parts[0] == headRefName {
@@ -991,7 +990,6 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
 					return cancel
 				}
 				ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
-
 			} else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
 				ctx.Repo.IsViewTag = true
 				ctx.Repo.TagName = refName
diff --git a/services/doctor/packages_nuget.go b/services/doctor/packages_nuget.go
index 8c0a2d856d..47fdb3ac12 100644
--- a/services/doctor/packages_nuget.go
+++ b/services/doctor/packages_nuget.go
@@ -51,7 +51,6 @@ func PackagesNugetNuspecCheck(ctx context.Context, logger log.Logger, autofix bo
 		logger.Info("Found %d versions for package %s", len(pvs), pkg.Name)
 
 		for _, pv := range pvs {
-
 			pfs, err := packages.GetFilesByVersionID(ctx, pv.ID)
 			if err != nil {
 				logger.Error("Failed to get files for package version %s %s: %v", pkg.Name, pv.Version, err)
diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go
index 0c89839edf..d9dbeedee5 100644
--- a/services/gitdiff/gitdiff.go
+++ b/services/gitdiff/gitdiff.go
@@ -1040,8 +1040,8 @@ func createDiffFile(diff *Diff, line string) *DiffFile {
 			// diff --git a/b b/b b/b b/b b/b b/b
 			//
 			midpoint := (len(line) + len(cmdDiffHead) - 1) / 2
-			new, old := line[len(cmdDiffHead):midpoint], line[midpoint+1:]
-			if len(new) > 2 && len(old) > 2 && new[2:] == old[2:] {
+			newl, old := line[len(cmdDiffHead):midpoint], line[midpoint+1:]
+			if len(newl) > 2 && len(old) > 2 && newl[2:] == old[2:] {
 				curFile.OldName = old[2:]
 				curFile.Name = old[2:]
 			}
diff --git a/services/issue/commit.go b/services/issue/commit.go
index e493a03211..8b927d52b6 100644
--- a/services/issue/commit.go
+++ b/services/issue/commit.go
@@ -117,7 +117,6 @@ func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_m
 		var refIssue *issues_model.Issue
 		var err error
 		for _, ref := range references.FindAllIssueReferences(c.Message) {
-
 			// issue is from another repo
 			if len(ref.Owner) > 0 && len(ref.Name) > 0 {
 				refRepo, err = repo_model.GetRepositoryByOwnerAndName(ctx, ref.Owner, ref.Name)
@@ -185,15 +184,15 @@ func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_m
 					continue
 				}
 			}
-			close := ref.Action == references.XRefActionCloses
-			if close && len(ref.TimeLog) > 0 {
+			isClosed := ref.Action == references.XRefActionCloses
+			if isClosed && len(ref.TimeLog) > 0 {
 				if err := issueAddTime(ctx, refIssue, doer, c.Timestamp, ref.TimeLog); err != nil {
 					return err
 				}
 			}
-			if close != refIssue.IsClosed {
+			if isClosed != refIssue.IsClosed {
 				refIssue.Repo = refRepo
-				if err := ChangeStatus(ctx, refIssue, doer, c.Sha1, close); err != nil {
+				if err := ChangeStatus(ctx, refIssue, doer, c.Sha1, isClosed); err != nil {
 					return err
 				}
 			}
diff --git a/services/migrations/gitea_downloader.go b/services/migrations/gitea_downloader.go
index d402a238f2..272bf02e11 100644
--- a/services/migrations/gitea_downloader.go
+++ b/services/migrations/gitea_downloader.go
@@ -410,7 +410,6 @@ func (g *GiteaDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, err
 		return nil, false, fmt.Errorf("error while listing issues: %w", err)
 	}
 	for _, issue := range issues {
-
 		labels := make([]*base.Label, 0, len(issue.Labels))
 		for i := range issue.Labels {
 			labels = append(labels, g.convertGiteaLabel(issue.Labels[i]))
diff --git a/services/migrations/gitlab.go b/services/migrations/gitlab.go
index bbc44e958a..065b687fa6 100644
--- a/services/migrations/gitlab.go
+++ b/services/migrations/gitlab.go
@@ -421,7 +421,6 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
 		return nil, false, fmt.Errorf("error while listing issues: %w", err)
 	}
 	for _, issue := range issues {
-
 		labels := make([]*base.Label, 0, len(issue.Labels))
 		for _, l := range issue.Labels {
 			labels = append(labels, &base.Label{
diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go
index fa23986c54..f5eaeaf091 100644
--- a/services/mirror/mirror_pull.go
+++ b/services/mirror/mirror_pull.go
@@ -523,13 +523,13 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
 			theCommits.Commits = theCommits.Commits[:setting.UI.FeedMaxCommitNum]
 		}
 
-		if newCommit, err := gitRepo.GetCommit(newCommitID); err != nil {
+		newCommit, err := gitRepo.GetCommit(newCommitID)
+		if err != nil {
 			log.Error("SyncMirrors [repo: %-v]: unable to get commit %s: %v", m.Repo, newCommitID, err)
 			continue
-		} else {
-			theCommits.HeadCommit = repo_module.CommitToPushCommit(newCommit)
 		}
 
+		theCommits.HeadCommit = repo_module.CommitToPushCommit(newCommit)
 		theCommits.CompareURL = m.Repo.ComposeCompareURL(oldCommitID, newCommitID)
 
 		notify_service.SyncPushCommits(ctx, m.Repo.MustOwner(ctx), m.Repo, &repo_module.PushUpdateOptions{
@@ -557,7 +557,6 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
 			log.Error("SyncMirrors [repo: %-v]: unable to update repository 'updated_unix': %v", m.Repo, err)
 			return false
 		}
-
 	}
 
 	log.Trace("SyncMirrors [repo: %-v]: Successfully updated", m.Repo)
diff --git a/services/pull/merge.go b/services/pull/merge.go
index 2989d77c6a..1d6431ab66 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -237,9 +237,9 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
 		if err = ref.Issue.LoadRepo(ctx); err != nil {
 			return err
 		}
-		close := ref.RefAction == references.XRefActionCloses
-		if close != ref.Issue.IsClosed {
-			if err = issue_service.ChangeStatus(ctx, ref.Issue, doer, pr.MergedCommitID, close); err != nil {
+		isClosed := ref.RefAction == references.XRefActionCloses
+		if isClosed != ref.Issue.IsClosed {
+			if err = issue_service.ChangeStatus(ctx, ref.Issue, doer, pr.MergedCommitID, isClosed); err != nil {
 				// Allow ErrDependenciesLeft
 				if !issues_model.IsErrDependenciesLeft(err) {
 					return err
diff --git a/services/pull/pull.go b/services/pull/pull.go
index 720efdb0cb..5fa426483f 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -803,7 +803,6 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
 			if err != nil {
 				log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err)
 				return ""
-
 			}
 			if len(commits) == 0 {
 				break
diff --git a/services/repository/adopt.go b/services/repository/adopt.go
index b337eac38a..31e3e581b3 100644
--- a/services/repository/adopt.go
+++ b/services/repository/adopt.go
@@ -357,7 +357,6 @@ func ListUnadoptedRepositories(ctx context.Context, query string, opts *db.ListO
 				return err
 			}
 			repoNamesToCheck = repoNamesToCheck[:0]
-
 		}
 		return filepath.SkipDir
 	}); err != nil {
diff --git a/services/repository/contributors_graph.go b/services/repository/contributors_graph.go
index 516e7f1475..7241d3d655 100644
--- a/services/repository/contributors_graph.go
+++ b/services/repository/contributors_graph.go
@@ -194,7 +194,6 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int
 					Stats: &commitStats,
 				}
 				extendedCommitStats = append(extendedCommitStats, res)
-
 			}
 			_ = stdoutReader.Close()
 			return nil
diff --git a/services/repository/files/update.go b/services/repository/files/update.go
index e677949e86..81a61da5ed 100644
--- a/services/repository/files/update.go
+++ b/services/repository/files/update.go
@@ -208,7 +208,6 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
 				return nil, fmt.Errorf("ConvertToSHA1: Invalid last commit ID: %w", err)
 			}
 			opts.LastCommitID = lastCommitID.String()
-
 		}
 
 		for _, file := range opts.Files {
@@ -360,7 +359,6 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep
 					Path: file.Options.treePath,
 				}
 			}
-
 		}
 	}
 
diff --git a/services/user/delete.go b/services/user/delete.go
index e890990994..74dbc09b82 100644
--- a/services/user/delete.go
+++ b/services/user/delete.go
@@ -106,7 +106,6 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error)
 
 	if purge || (setting.Service.UserDeleteWithCommentsMaxTime != 0 &&
 		u.CreatedUnix.AsTime().Add(setting.Service.UserDeleteWithCommentsMaxTime).After(time.Now())) {
-
 		// Delete Comments
 		const batchSize = 50
 		for {
diff --git a/services/user/update_test.go b/services/user/update_test.go
index 7ed764b539..c2ff26a140 100644
--- a/services/user/update_test.go
+++ b/services/user/update_test.go
@@ -94,7 +94,7 @@ func TestUpdateAuth(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28})
-	copy := *user
+	userCopy := *user
 
 	assert.NoError(t, UpdateAuth(db.DefaultContext, user, &UpdateAuthOptions{
 		LoginName: optional.Some("new-login"),
@@ -106,8 +106,8 @@ func TestUpdateAuth(t *testing.T) {
 		MustChangePassword: optional.Some(true),
 	}))
 	assert.True(t, user.MustChangePassword)
-	assert.NotEqual(t, copy.Passwd, user.Passwd)
-	assert.NotEqual(t, copy.Salt, user.Salt)
+	assert.NotEqual(t, userCopy.Passwd, user.Passwd)
+	assert.NotEqual(t, userCopy.Salt, user.Salt)
 
 	assert.NoError(t, UpdateAuth(db.DefaultContext, user, &UpdateAuthOptions{
 		ProhibitLogin: optional.Some(true),
diff --git a/services/webhook/discord.go b/services/webhook/discord.go
index cb756688c8..80f6cfb79b 100644
--- a/services/webhook/discord.go
+++ b/services/webhook/discord.go
@@ -304,14 +304,12 @@ func (discordHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook,
 
 func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, error) {
 	switch event {
-
 	case webhook_module.HookEventPullRequestReviewApproved:
 		return "approved", nil
 	case webhook_module.HookEventPullRequestReviewRejected:
 		return "rejected", nil
 	case webhook_module.HookEventPullRequestReviewComment:
 		return "comment", nil
-
 	default:
 		return "", errors.New("unknown event type")
 	}
diff --git a/services/webhook/matrix.go b/services/webhook/matrix.go
index 24d6cc6d99..06176e8dd3 100644
--- a/services/webhook/matrix.go
+++ b/services/webhook/matrix.go
@@ -217,7 +217,6 @@ func (m matrixConvertor) Push(p *api.PushPayload) (MatrixPayload, error) {
 		if i < len(p.Commits)-1 {
 			text += "<br>"
 		}
-
 	}
 
 	return m.newPayload(text, p.Commits...)
diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go
index 97f6844abc..07f0bf52a3 100644
--- a/tests/e2e/e2e_test.go
+++ b/tests/e2e/e2e_test.go
@@ -106,18 +106,20 @@ func TestE2e(t *testing.T) {
 				cmd := exec.Command(runArgs[0], runArgs...)
 				cmd.Env = os.Environ()
 				cmd.Env = append(cmd.Env, fmt.Sprintf("GITEA_URL=%s", setting.AppURL))
+
 				var stdout, stderr bytes.Buffer
 				cmd.Stdout = &stdout
 				cmd.Stderr = &stderr
+
 				err := cmd.Run()
 				if err != nil {
 					// Currently colored output is conflicting. Using Printf until that is resolved.
 					fmt.Printf("%v", stdout.String())
 					fmt.Printf("%v", stderr.String())
 					log.Fatal("Playwright Failed: %s", err)
-				} else {
-					fmt.Printf("%v", stdout.String())
 				}
+
+				fmt.Printf("%v", stdout.String())
 			})
 		})
 	}
diff --git a/tests/integration/api_notification_test.go b/tests/integration/api_notification_test.go
index 528890ca22..abb9852eef 100644
--- a/tests/integration/api_notification_test.go
+++ b/tests/integration/api_notification_test.go
@@ -111,7 +111,7 @@ func TestAPINotification(t *testing.T) {
 
 	MakeRequest(t, NewRequest(t, "GET", "/api/v1/notifications/new"), http.StatusUnauthorized)
 
-	new := struct {
+	newStruct := struct {
 		New int64 `json:"new"`
 	}{}
 
@@ -119,8 +119,8 @@ func TestAPINotification(t *testing.T) {
 	req = NewRequest(t, "GET", "/api/v1/notifications/new").
 		AddTokenAuth(token)
 	resp = MakeRequest(t, req, http.StatusOK)
-	DecodeJSON(t, resp, &new)
-	assert.True(t, new.New > 0)
+	DecodeJSON(t, resp, &newStruct)
+	assert.True(t, newStruct.New > 0)
 
 	// -- mark notifications as read --
 	req = NewRequest(t, "GET", "/api/v1/notifications?status-types=unread").
@@ -153,8 +153,8 @@ func TestAPINotification(t *testing.T) {
 	req = NewRequest(t, "GET", "/api/v1/notifications/new").
 		AddTokenAuth(token)
 	resp = MakeRequest(t, req, http.StatusOK)
-	DecodeJSON(t, resp, &new)
-	assert.True(t, new.New == 0)
+	DecodeJSON(t, resp, &newStruct)
+	assert.True(t, newStruct.New == 0)
 }
 
 func TestAPINotificationPUT(t *testing.T) {
diff --git a/tests/integration/api_packages_alpine_test.go b/tests/integration/api_packages_alpine_test.go
index ea9735236b..f70d3c23af 100644
--- a/tests/integration/api_packages_alpine_test.go
+++ b/tests/integration/api_packages_alpine_test.go
@@ -480,7 +480,6 @@ aiIK5QoSDwAAAAAAAAAAAAAAAP/IK49O1e8AKAAA`
 					req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/x86_64/%s-%s.apk", rootURL, branch, repository, pkg, packageVersion)).
 						AddBasicAuth(user.Name)
 					MakeRequest(t, req, http.StatusNoContent)
-
 				}
 				// Deleting the last file of an architecture should remove that index
 				req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/x86_64/APKINDEX.tar.gz", rootURL, branch, repository))
diff --git a/tests/integration/pull_status_test.go b/tests/integration/pull_status_test.go
index bb7098e424..80eea34513 100644
--- a/tests/integration/pull_status_test.go
+++ b/tests/integration/pull_status_test.go
@@ -71,7 +71,6 @@ func TestPullCreate_CommitStatus(t *testing.T) {
 
 		// Update commit status, and check if icon is updated as well
 		for _, status := range statusList {
-
 			// Call API to add status for commit
 			t.Run("CreateStatus", doAPICreateCommitStatus(testCtx, commitID, api.CreateStatusOption{
 				State:       status,

From f619b9622841c3de045184c5e298a478bafa097a Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Mon, 22 Apr 2024 16:24:47 +0200
Subject: [PATCH 041/107] Enable jquery-related eslint rules that have no
 violations (#30632)

All these have no violations, so enable them.

(cherry picked from commit 99c5683da5e5c50154dcf9c07229a455a5095058)

Conflicts:
	.eslintrc.yaml
	do not enable no-sizzle as Forgejo still uses it
---
 .eslintrc.yaml | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/.eslintrc.yaml b/.eslintrc.yaml
index f28d2ac247..91019cde84 100644
--- a/.eslintrc.yaml
+++ b/.eslintrc.yaml
@@ -4,7 +4,6 @@ reportUnusedDisableDirectives: true
 ignorePatterns:
   - /web_src/js/vendor
   - /web_src/fomantic
-  - /public/assets/js
 
 parserOptions:
   sourceType: module
@@ -311,7 +310,7 @@ rules:
   jquery/no-merge: [2]
   jquery/no-param: [2]
   jquery/no-parent: [0]
-  jquery/no-parents: [0]
+  jquery/no-parents: [2]
   jquery/no-parse-html: [2]
   jquery/no-prop: [2]
   jquery/no-proxy: [2]
@@ -320,8 +319,8 @@ rules:
   jquery/no-show: [2]
   jquery/no-size: [2]
   jquery/no-sizzle: [0]
-  jquery/no-slide: [0]
-  jquery/no-submit: [0]
+  jquery/no-slide: [2]
+  jquery/no-submit: [2]
   jquery/no-text: [0]
   jquery/no-toggle: [2]
   jquery/no-trigger: [0]
@@ -459,7 +458,7 @@ rules:
   no-jquery/no-other-utils: [2]
   no-jquery/no-param: [2]
   no-jquery/no-parent: [0]
-  no-jquery/no-parents: [0]
+  no-jquery/no-parents: [2]
   no-jquery/no-parse-html-literal: [0]
   no-jquery/no-parse-html: [2]
   no-jquery/no-parse-json: [2]

From 4e6e63dca20bc5c1afcb8bb5ae3a6aad8b26d300 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Tue, 23 Apr 2024 10:22:43 +0800
Subject: [PATCH 042/107] Fix compare api swagger (#30648)

The swagger format on #30349 is not right. This PR will fix it.

(cherry picked from commit 8924d9b2efd52132876fcd106c625a2a2db7a295)
---
 routers/api/v1/repo/compare.go | 2 +-
 templates/swagger/v1_json.tmpl | 6 ++----
 2 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/routers/api/v1/repo/compare.go b/routers/api/v1/repo/compare.go
index 549b9b7fa9..cfd61d768c 100644
--- a/routers/api/v1/repo/compare.go
+++ b/routers/api/v1/repo/compare.go
@@ -16,7 +16,7 @@ import (
 
 // CompareDiff compare two branches or commits
 func CompareDiff(ctx *context.APIContext) {
-	// swagger:operation GET /repos/{owner}/{repo}/compare/{basehead} Get commit comparison information
+	// swagger:operation GET /repos/{owner}/{repo}/compare/{basehead} repository repoCompareDiff
 	// ---
 	// summary: Get commit comparison information
 	// produces:
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index c49c6208ef..2473e96006 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -5216,12 +5216,10 @@
           "application/json"
         ],
         "tags": [
-          "Get",
-          "commit",
-          "comparison"
+          "repository"
         ],
         "summary": "Get commit comparison information",
-        "operationId": "information",
+        "operationId": "repoCompareDiff",
         "parameters": [
           {
             "type": "string",

From b9891088adb43f40a44050006317e098ff255727 Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Wed, 17 Apr 2024 23:58:37 +0800
Subject: [PATCH 043/107] Allow everyone to read or write a wiki by a repo unit
 setting (#30495)

Replace #6312
Help #5833
Wiki solution for #639

(cherry picked from commit 3feba9f1f44156c256a30d25ad1c25f751819c94)

Conflicts:
  Trash everything, just keep the migration placeholder to ensure the Gitea
  sequence is preserved. The Wiki edition is implemented differently.
---
 models/migrations/migrations.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 7146c49676..ecc41894fc 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -584,6 +584,8 @@ var migrations = []Migration{
 	NewMigration("Add commit status summary table", v1_23.AddCommitStatusSummary),
 	// v296 -> v297
 	NewMigration("Add missing field of commit status summary table", v1_23.AddCommitStatusSummary2),
+	// v297 -> v298
+	NewMigration("Add everyone_access_mode for repo_unit", noopMigration),
 }
 
 // GetCurrentDBVersion returns the current db version

From 4f73382e9530c22d0f895854519bc19f5e5f1c5b Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Tue, 23 Apr 2024 11:00:57 +0800
Subject: [PATCH 044/107] Fix wrong table name (#30557)

The table name should be `oauth2_application` but `o_auth2_application`

Caused by
https://github.com/go-gitea/gitea/pull/21316/files#diff-9610efbc608a41f1f2eaff5790423f0a187906f6ff0beb23a5e8d18366cc2ccfR38

(cherry picked from commit e94864e86c43f435af7e1fc3c4831a4cc0a3e981)

Conflicts:
	models/migrations/migrations.go
	trivial context conflict because
	Allow everyone to read or write a wiki by a repo unit setting (#30495)
        was skipped.
---
 models/auth/oauth2_test.go                             |  2 --
 ...{o_auth2_application.yml => oauth2_application.yml} |  0
 models/migrations/migrations.go                        |  2 ++
 models/migrations/v1_18/v230.go                        |  6 +++---
 models/migrations/v1_18/v230_test.go                   |  6 +++---
 models/migrations/v1_23/v298.go                        | 10 ++++++++++
 6 files changed, 18 insertions(+), 8 deletions(-)
 rename models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/{o_auth2_application.yml => oauth2_application.yml} (100%)
 create mode 100644 models/migrations/v1_23/v298.go

diff --git a/models/auth/oauth2_test.go b/models/auth/oauth2_test.go
index 5361f61db2..6602f850cf 100644
--- a/models/auth/oauth2_test.go
+++ b/models/auth/oauth2_test.go
@@ -16,8 +16,6 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-//////////////////// Application
-
 func TestOAuth2Application_GenerateClientSecret(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
diff --git a/models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/o_auth2_application.yml b/models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/oauth2_application.yml
similarity index 100%
rename from models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/o_auth2_application.yml
rename to models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/oauth2_application.yml
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index ecc41894fc..15b12ed70c 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -586,6 +586,8 @@ var migrations = []Migration{
 	NewMigration("Add missing field of commit status summary table", v1_23.AddCommitStatusSummary2),
 	// v297 -> v298
 	NewMigration("Add everyone_access_mode for repo_unit", noopMigration),
+	// v298 -> v299
+	NewMigration("Drop wrongly created table o_auth2_application", v1_23.DropWronglyCreatedTable),
 }
 
 // GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v1_18/v230.go b/models/migrations/v1_18/v230.go
index cf94926be1..ea5b4d02e1 100644
--- a/models/migrations/v1_18/v230.go
+++ b/models/migrations/v1_18/v230.go
@@ -9,9 +9,9 @@ import (
 
 // AddConfidentialColumnToOAuth2ApplicationTable: add ConfidentialClient column, setting existing rows to true
 func AddConfidentialClientColumnToOAuth2ApplicationTable(x *xorm.Engine) error {
-	type OAuth2Application struct {
+	type oauth2Application struct {
+		ID                 int64
 		ConfidentialClient bool `xorm:"NOT NULL DEFAULT TRUE"`
 	}
-
-	return x.Sync(new(OAuth2Application))
+	return x.Sync(new(oauth2Application))
 }
diff --git a/models/migrations/v1_18/v230_test.go b/models/migrations/v1_18/v230_test.go
index 308f3a5023..40db4c2ffe 100644
--- a/models/migrations/v1_18/v230_test.go
+++ b/models/migrations/v1_18/v230_test.go
@@ -13,12 +13,12 @@ import (
 
 func Test_AddConfidentialClientColumnToOAuth2ApplicationTable(t *testing.T) {
 	// premigration
-	type OAuth2Application struct {
+	type oauth2Application struct {
 		ID int64
 	}
 
 	// Prepare and load the testing database
-	x, deferable := base.PrepareTestEnv(t, 0, new(OAuth2Application))
+	x, deferable := base.PrepareTestEnv(t, 0, new(oauth2Application))
 	defer deferable()
 	if x == nil || t.Failed() {
 		return
@@ -36,7 +36,7 @@ func Test_AddConfidentialClientColumnToOAuth2ApplicationTable(t *testing.T) {
 	}
 
 	got := []ExpectedOAuth2Application{}
-	if err := x.Table("o_auth2_application").Select("id, confidential_client").Find(&got); !assert.NoError(t, err) {
+	if err := x.Table("oauth2_application").Select("id, confidential_client").Find(&got); !assert.NoError(t, err) {
 		return
 	}
 
diff --git a/models/migrations/v1_23/v298.go b/models/migrations/v1_23/v298.go
new file mode 100644
index 0000000000..8761a05d3d
--- /dev/null
+++ b/models/migrations/v1_23/v298.go
@@ -0,0 +1,10 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_23 //nolint
+
+import "xorm.io/xorm"
+
+func DropWronglyCreatedTable(x *xorm.Engine) error {
+	return x.DropTables("o_auth2_application")
+}

From 168cb758ec70f0c44b603edfad0356ae82d60a9d Mon Sep 17 00:00:00 2001
From: Zettat123 <zettat123@gmail.com>
Date: Tue, 23 Apr 2024 11:51:52 +0800
Subject: [PATCH 045/107] Add a db consistency check to remove runners that do
 not belong to a repository (#30614)

Follow #30406

(cherry picked from commit 30dd4beeee631860c7dd393c341e9955997095a4)
---
 models/actions/runner.go         | 26 ++++++++++++++++++++++++--
 services/doctor/dbconsistency.go |  6 ++++++
 2 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/models/actions/runner.go b/models/actions/runner.go
index 67f003387b..9192925d5a 100644
--- a/models/actions/runner.go
+++ b/models/actions/runner.go
@@ -270,7 +270,7 @@ func CountRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
 	// Only affect action runners were a owner ID is set, as actions runners
 	// could also be created on a repository.
 	return db.GetEngine(ctx).Table("action_runner").
-		Join("LEFT", "user", "`action_runner`.owner_id = `user`.id").
+		Join("LEFT", "`user`", "`action_runner`.owner_id = `user`.id").
 		Where("`action_runner`.owner_id != ?", 0).
 		And(builder.IsNull{"`user`.id"}).
 		Count(new(ActionRunner))
@@ -279,7 +279,7 @@ func CountRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
 func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
 	subQuery := builder.Select("`action_runner`.id").
 		From("`action_runner`").
-		Join("LEFT", "user", "`action_runner`.owner_id = `user`.id").
+		Join("LEFT", "`user`", "`action_runner`.owner_id = `user`.id").
 		Where(builder.Neq{"`action_runner`.owner_id": 0}).
 		And(builder.IsNull{"`user`.id"})
 	b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`")
@@ -289,3 +289,25 @@ func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
 	}
 	return res.RowsAffected()
 }
+
+func CountRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) {
+	return db.GetEngine(ctx).Table("action_runner").
+		Join("LEFT", "`repository`", "`action_runner`.repo_id = `repository`.id").
+		Where("`action_runner`.repo_id != ?", 0).
+		And(builder.IsNull{"`repository`.id"}).
+		Count(new(ActionRunner))
+}
+
+func FixRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) {
+	subQuery := builder.Select("`action_runner`.id").
+		From("`action_runner`").
+		Join("LEFT", "`repository`", "`action_runner`.repo_id = `repository`.id").
+		Where(builder.Neq{"`action_runner`.repo_id": 0}).
+		And(builder.IsNull{"`repository`.id"})
+	b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`")
+	res, err := db.GetEngine(ctx).Exec(b)
+	if err != nil {
+		return 0, err
+	}
+	return res.RowsAffected()
+}
diff --git a/services/doctor/dbconsistency.go b/services/doctor/dbconsistency.go
index d036f75b2a..6b931b7036 100644
--- a/services/doctor/dbconsistency.go
+++ b/services/doctor/dbconsistency.go
@@ -159,6 +159,12 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
 			Fixer:        actions_model.FixRunnersWithoutBelongingOwner,
 			FixedMessage: "Removed",
 		},
+		{
+			Name:         "Action Runners without existing repository",
+			Counter:      actions_model.CountRunnersWithoutBelongingRepo,
+			Fixer:        actions_model.FixRunnersWithoutBelongingRepo,
+			FixedMessage: "Removed",
+		},
 		{
 			Name:         "Topics with empty repository count",
 			Counter:      repo_model.CountOrphanedTopics,

From 39faa125d58a06e258673dfe4e1e949b7c714ec1 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Tue, 23 Apr 2024 06:17:51 +0200
Subject: [PATCH 046/107] Fix project name wrapping, remove horizontal margin
 on header (#30631)

Enable wrapping of unbroken lines:

<img width="1308" alt="Screenshot 2024-04-22 at 00 31 33"
src="https://github.com/go-gitea/gitea/assets/115237/1a28ade1-d708-4260-96a3-cf508b6dcb79">

Remove extra margin added by nested `.ui.container` on certain
viewports:

Before:
<img width="1305" alt="Screenshot 2024-04-22 at 00 40 23"
src="https://github.com/go-gitea/gitea/assets/115237/d3d8c0d1-380c-4867-b95c-4d53d70d4a93">

After:
<img width="1310" alt="Screenshot 2024-04-22 at 00 40 33"
src="https://github.com/go-gitea/gitea/assets/115237/2ba7b9f2-db2f-4bcc-8cce-5c415625ddea">

(cherry picked from commit 370b1bdb3757e91c59303b0ce6ec49c56eca795b)
---
 templates/projects/list.tmpl | 4 ++--
 templates/projects/view.tmpl | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl
index 0908f5648c..b892cff996 100644
--- a/templates/projects/list.tmpl
+++ b/templates/projects/list.tmpl
@@ -41,9 +41,9 @@
 <div class="milestone-list">
 	{{range .Projects}}
 		<li class="milestone-card">
-			<h3 class="flex-text-block tw-m-0">
+			<h3 class="flex-text-block tw-m-0 tw-gap-3">
 				{{svg .IconName 16}}
-				<a class="muted" href="{{.Link ctx}}">{{.Title}}</a>
+				<a class="muted tw-break-anywhere" href="{{.Link ctx}}">{{.Title}}</a>
 			</h3>
 			<div class="milestone-toolbar">
 				<div class="group">
diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl
index f9b85360e0..3e000660e2 100644
--- a/templates/projects/view.tmpl
+++ b/templates/projects/view.tmpl
@@ -1,8 +1,8 @@
 {{$canWriteProject := and .CanWriteProjects (or (not .Repository) (not .Repository.IsArchived))}}
 
-<div class="ui container">
-	<div class="tw-flex tw-justify-between tw-items-center tw-mb-4">
-		<h2 class="tw-mb-0">{{.Project.Title}}</h2>
+<div class="ui container tw-max-w-full">
+	<div class="tw-flex tw-justify-between tw-items-center tw-mb-4 tw-gap-3">
+		<h2 class="tw-mb-0 tw-flex-1 tw-break-anywhere">{{.Project.Title}}</h2>
 		{{if $canWriteProject}}
 			<div class="ui compact mini menu">
 				<a class="item" href="{{.Link}}/edit?redirect=project">

From 50917ead5fb79a3aa81dd455c1d43c89b451e8fc Mon Sep 17 00:00:00 2001
From: Kemal Zebari <60799661+kemzeb@users.noreply.github.com>
Date: Tue, 23 Apr 2024 00:10:01 -0700
Subject: [PATCH 047/107] Perform Newest sort type correctly when sorting
 issues (#30644)

Should resolve #30642.

Before this commit, we were treating an empty `?sort=` query parameter
as the correct sorting type (which is to sort issues in descending order
by their created UNIX time). But when we perform `sort=latest`, we did
not include this as a type so we would sort by the most recently updated
when reaching the `default` switch statement block.

This commit fixes this by considering the empty string, "latest", and
just any other string that is not mentioned in the switch statement as
sorting by newest.

(cherry picked from commit 9b7af4340c36d3e1888788499d16f83feeb1601b)
---
 modules/indexer/issues/dboptions.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/modules/indexer/issues/dboptions.go b/modules/indexer/issues/dboptions.go
index 4a98b4588a..8f94088742 100644
--- a/modules/indexer/issues/dboptions.go
+++ b/modules/indexer/issues/dboptions.go
@@ -68,7 +68,7 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
 	searchOpt.Paginator = opts.Paginator
 
 	switch opts.SortType {
-	case "":
+	case "", "latest":
 		searchOpt.SortBy = SortByCreatedDesc
 	case "oldest":
 		searchOpt.SortBy = SortByCreatedAsc
@@ -86,7 +86,7 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
 		searchOpt.SortBy = SortByDeadlineDesc
 	case "priority", "priorityrepo", "project-column-sorting":
 		// Unsupported sort type for search
-		searchOpt.SortBy = SortByUpdatedDesc
+		fallthrough
 	default:
 		searchOpt.SortBy = SortByUpdatedDesc
 	}

From a72b660cbb61d1932190e5d1cecd9defbab1260b Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Tue, 23 Apr 2024 16:31:51 +0800
Subject: [PATCH 048/107] Fix flash message for flex-container (#30657)

(cherry picked from commit dd2aaadce3ecd3134a1ba0c82c5aaa05d6c11b2b)

Conflicts:
	templates/admin/layout_head.tmpl
	    mostly already done by https://codeberg.org/forgejo/forgejo/pulls/3087
	web_src/css/base.css
            the conflict is because
            https://codeberg.org/forgejo/forgejo/pulls/3350
            skipped Remove fomantic menu module (gitea#30325)
            and it was not ported.
---
 templates/admin/layout_head.tmpl        | 2 +-
 templates/user/dashboard/dashboard.tmpl | 2 +-
 web_src/css/base.css                    | 6 ------
 3 files changed, 2 insertions(+), 8 deletions(-)

diff --git a/templates/admin/layout_head.tmpl b/templates/admin/layout_head.tmpl
index 8ba47f2f14..7cc6624d50 100644
--- a/templates/admin/layout_head.tmpl
+++ b/templates/admin/layout_head.tmpl
@@ -1,6 +1,6 @@
 {{template "base/head" .ctxData}}
 <div role="main" aria-label="{{.ctxData.Title}}" class="page-content {{.pageClass}}">
-	<div class="ui container flex-container">
+	<div class="ui container fluid padded flex-container">
 		{{template "admin/navbar" .ctxData}}
 		<div class="flex-container-main">
 			{{template "base/alert" .ctxData}}
diff --git a/templates/user/dashboard/dashboard.tmpl b/templates/user/dashboard/dashboard.tmpl
index 415423d436..5dc46dc0a5 100644
--- a/templates/user/dashboard/dashboard.tmpl
+++ b/templates/user/dashboard/dashboard.tmpl
@@ -1,9 +1,9 @@
 {{template "base/head" .}}
 <div role="main" aria-label="{{.Title}}" class="page-content dashboard feeds">
 	{{template "user/dashboard/navbar" .}}
-	{{template "base/alert" .}}
 	<div class="ui container flex-container">
 		<div class="flex-container-main">
+			{{template "base/alert" .}}
 			{{template "user/heatmap" .}}
 			{{template "user/dashboard/feeds" .}}
 		</div>
diff --git a/web_src/css/base.css b/web_src/css/base.css
index 4f62dd103c..3d91586934 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -636,12 +636,6 @@ img.ui.avatar,
   background: var(--color-active);
 }
 
-/* add horizontal margin to elements that are outside top-level of .flex-container or .ui.container */
-.page-content > .flash-message {
-  margin-left: var(--page-margin-x);
-  margin-right: var(--page-margin-x);
-}
-
 .ui.form .fields.error .field textarea,
 .ui.form .fields.error .field select,
 .ui.form .fields.error .field input:not([type]),

From 561a7cf520b534198636d1a2fa1589b5eb3dfdfe Mon Sep 17 00:00:00 2001
From: sillyguodong <33891828+sillyguodong@users.noreply.github.com>
Date: Wed, 24 Apr 2024 02:55:25 +0800
Subject: [PATCH 049/107] Interpolate runs-on with variables when scheduling
 tasks (#30640)

Follow #29468
1. Interpolate runs-on with variables when scheduling tasks.
2. The `GetVariablesOfRun` function will check if the `Repo` of the run
is nil.

---------

Co-authored-by: Giteabot <teabot@gitea.io>
(cherry picked from commit 2f6b1c46a1a4a90f56ca0f3ad7840e8e70daeab5)

Conflicts:
	services/actions/schedule_tasks.go
	trivial conflict because of 'Add vars context to cron jobs (#3059)'
---
 models/actions/run.go              | 22 ++++++++++++++++------
 models/actions/variable.go         |  5 +++++
 services/actions/schedule_tasks.go |  7 ++-----
 3 files changed, 23 insertions(+), 11 deletions(-)

diff --git a/models/actions/run.go b/models/actions/run.go
index 493bd0173c..397455e41d 100644
--- a/models/actions/run.go
+++ b/models/actions/run.go
@@ -98,13 +98,10 @@ func (run *ActionRun) LoadAttributes(ctx context.Context) error {
 		return nil
 	}
 
-	if run.Repo == nil {
-		repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
-		if err != nil {
-			return err
-		}
-		run.Repo = repo
+	if err := run.LoadRepo(ctx); err != nil {
+		return err
 	}
+
 	if err := run.Repo.LoadAttributes(ctx); err != nil {
 		return err
 	}
@@ -120,6 +117,19 @@ func (run *ActionRun) LoadAttributes(ctx context.Context) error {
 	return nil
 }
 
+func (run *ActionRun) LoadRepo(ctx context.Context) error {
+	if run == nil || run.Repo != nil {
+		return nil
+	}
+
+	repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
+	if err != nil {
+		return err
+	}
+	run.Repo = repo
+	return nil
+}
+
 func (run *ActionRun) Duration() time.Duration {
 	return calculateDuration(run.Started, run.Stopped, run.Status) + run.PreviousDuration
 }
diff --git a/models/actions/variable.go b/models/actions/variable.go
index b0a455e675..8aff844659 100644
--- a/models/actions/variable.go
+++ b/models/actions/variable.go
@@ -92,6 +92,11 @@ func DeleteVariable(ctx context.Context, id int64) error {
 func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) {
 	variables := map[string]string{}
 
+	if err := run.LoadRepo(ctx); err != nil {
+		log.Error("LoadRepo: %v", err)
+		return nil, err
+	}
+
 	// Global
 	globalVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{})
 	if err != nil {
diff --git a/services/actions/schedule_tasks.go b/services/actions/schedule_tasks.go
index 33013e9a9d..18f3324fd2 100644
--- a/services/actions/schedule_tasks.go
+++ b/services/actions/schedule_tasks.go
@@ -132,13 +132,10 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule)
 		Status:        actions_model.StatusWaiting,
 	}
 
-	if err := run.LoadAttributes(ctx); err != nil {
-		log.Error("LoadAttributes: %v", err)
-	}
-
 	vars, err := actions_model.GetVariablesOfRun(ctx, run)
 	if err != nil {
-		log.Error("GetVariablesOfSchedule: %v", err)
+		log.Error("GetVariablesOfRun: %v", err)
+		return err
 	}
 
 	// Parse the workflow specification from the cron schedule

From bafe2373752642bbdc961877894e70501db65d66 Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Wed, 24 Apr 2024 03:24:10 +0800
Subject: [PATCH 050/107] Avoid doubled border for the PR info segment (#30663)

(cherry picked from commit 2ee93ea17869de8fe24c6965fa3416ff30d55c5a)
---
 templates/repo/issue/view_content/pull.tmpl | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl
index 2d657c74ac..de8c5e5989 100644
--- a/templates/repo/issue/view_content/pull.tmpl
+++ b/templates/repo/issue/view_content/pull.tmpl
@@ -20,6 +20,7 @@
 	{{- else if .Issue.PullRequest.CanAutoMerge}}green
 	{{- else}}red{{end}}">{{svg "octicon-git-merge" 40}}</div>
 	<div class="content">
+		{{if .LatestCommitStatus}}
 		<div class="ui attached segment fitted">
 		{{template "repo/pulls/status" (dict
 			"CommitStatus" .LatestCommitStatus
@@ -29,8 +30,9 @@
 			"is_context_required" .is_context_required
 		)}}
 		</div>
+		{{end}}
 		{{$showGeneralMergeForm := false}}
-		<div class="ui attached merge-section segment {{if not $.LatestCommitStatus}}no-header{{end}} flex-items-block">
+		<div class="ui attached segment merge-section {{if not $.LatestCommitStatus}}no-header{{end}} flex-items-block">
 			{{if .Issue.PullRequest.HasMerged}}
 				{{if .IsPullBranchDeletable}}
 					<div class="item item-section text tw-flex-1">

From 2079d61a1445e249d4f6e2f0a1bdfcd444b2ab09 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Tue, 23 Apr 2024 23:53:57 +0200
Subject: [PATCH 051/107] Fix checkbox field markup (#30666)

Fixes https://github.com/go-gitea/gitea/issues/30664.

Previous use was not a supported way by fomantic and the misuse only
became visible after the checkbox migration.

(cherry picked from commit 1a2ae64b16f10b8d1e17197d18b9eb373faf58db)
---
 .../user/settings/applications_oauth2_edit_form.tmpl      | 8 +++++---
 templates/user/settings/applications_oauth2_list.tmpl     | 8 +++++---
 2 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/templates/user/settings/applications_oauth2_edit_form.tmpl b/templates/user/settings/applications_oauth2_edit_form.tmpl
index f7ef115693..199d43a65c 100644
--- a/templates/user/settings/applications_oauth2_edit_form.tmpl
+++ b/templates/user/settings/applications_oauth2_edit_form.tmpl
@@ -41,9 +41,11 @@
 			<label for="redirect-uris">{{ctx.Locale.Tr "settings.oauth2_redirect_uris"}}</label>
 			<textarea name="redirect_uris" id="redirect-uris" required>{{StringUtils.Join .App.RedirectURIs "\n"}}</textarea>
 		</div>
-		<div class="field ui checkbox {{if .Err_ConfidentialClient}}error{{end}}">
-			<label>{{ctx.Locale.Tr "settings.oauth2_confidential_client"}}</label>
-			<input type="checkbox" name="confidential_client" {{if .App.ConfidentialClient}}checked{{end}}>
+		<div class="field {{if .Err_ConfidentialClient}}error{{end}}">
+			<div class="ui checkbox">
+				<label>{{ctx.Locale.Tr "settings.oauth2_confidential_client"}}</label>
+				<input type="checkbox" name="confidential_client" {{if .App.ConfidentialClient}}checked{{end}}>
+			</div>
 		</div>
 		<button class="ui primary button">
 			{{ctx.Locale.Tr "settings.save_application"}}
diff --git a/templates/user/settings/applications_oauth2_list.tmpl b/templates/user/settings/applications_oauth2_list.tmpl
index cfcb6d053d..c75cbd532e 100644
--- a/templates/user/settings/applications_oauth2_list.tmpl
+++ b/templates/user/settings/applications_oauth2_list.tmpl
@@ -61,9 +61,11 @@
 			<label for="redirect-uris">{{ctx.Locale.Tr "settings.oauth2_redirect_uris"}}</label>
 			<textarea name="redirect_uris" id="redirect-uris"></textarea>
 		</div>
-		<div class="field ui checkbox {{if .Err_ConfidentialClient}}error{{end}}">
-			<label>{{ctx.Locale.Tr "settings.oauth2_confidential_client"}}</label>
-			<input type="checkbox" name="confidential_client" checked>
+		<div class="field {{if .Err_ConfidentialClient}}error{{end}}">
+			<div class="ui checkbox">
+				<label>{{ctx.Locale.Tr "settings.oauth2_confidential_client"}}</label>
+				<input type="checkbox" name="confidential_client" checked>
+			</div>
 		</div>
 		<button class="ui primary button">
 			{{ctx.Locale.Tr "settings.create_oauth2_application_button"}}

From 931ca1834039fab8d578d4d90281dfaa277fdcf2 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Wed, 24 Apr 2024 09:58:24 +0800
Subject: [PATCH 052/107] Fix some bug on migrations (#30647)

Fix https://github.com/go-gitea/gitea/pull/23894#discussion_r1573718690

(cherry picked from commit 2ad9ef4984f0b68ef38241fd6b557d8427d851d8)

Conflicts:
	models/migrations/v1_16/v210.go
	models/migrations/v1_22/v286.go
	trivial conflicts because MSSQL is no longer supported
---
 models/migrations/v1_16/v210.go | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/models/migrations/v1_16/v210.go b/models/migrations/v1_16/v210.go
index f9d1eb5c84..db45b11aed 100644
--- a/models/migrations/v1_16/v210.go
+++ b/models/migrations/v1_16/v210.go
@@ -68,11 +68,6 @@ func RemigrateU2FCredentials(x *xorm.Engine) error {
 		if err != nil {
 			return err
 		}
-	case schemas.ORACLE:
-		_, err := x.Exec("ALTER TABLE webauthn_credential MODIFY credential_id VARCHAR(410)")
-		if err != nil {
-			return err
-		}
 	case schemas.POSTGRES:
 		_, err := x.Exec("ALTER TABLE webauthn_credential ALTER COLUMN credential_id TYPE VARCHAR(410)")
 		if err != nil {

From bce70cc02400cf6d4fc77d5693c496587802c6a1 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Wed, 24 Apr 2024 15:11:52 +0200
Subject: [PATCH 053/107] Fix border-radius of header+segment boxes (#30667)

This is a very old bug with the bottom border-radiuses not being there
and the `:has` selector now makes it possible to cleanly solve it. It
affects all header+segment boxes, which there are many throughout the
UI:

<img width="1017" alt="Screenshot 2024-04-23 at 20 47 21"
src="https://github.com/go-gitea/gitea/assets/115237/870fe352-cc38-4bd6-bfe6-9fe8c3066f92">

(cherry picked from commit 3f19a6334575e1d2849999e8339f1b515cefaf1a)
---
 web_src/css/modules/segment.css | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/web_src/css/modules/segment.css b/web_src/css/modules/segment.css
index d4eaf633d7..04543f0986 100644
--- a/web_src/css/modules/segment.css
+++ b/web_src/css/modules/segment.css
@@ -153,6 +153,11 @@
   border-top: none;
 }
 
+.ui.attached.segment:has(+ .ui[class*="top attached"].header),
+.ui.attached.segment:last-child {
+  border-radius: 0 0 0.28571429rem 0.28571429rem;
+}
+
 .ui[class*="top attached"].segment {
   bottom: 0;
   margin-bottom: 0;

From 93820d26bafadf211f5cdb0fa8e6a1f2383e9677 Mon Sep 17 00:00:00 2001
From: Jiaxin Zhu <shin00pku@gmail.com>
Date: Thu, 25 Apr 2024 08:07:38 +0800
Subject: [PATCH 054/107] Fix view of readme file in the home code page.
 (#30564)

Gitea attempts to display image file, pdf file, etc. named readme in the
home code page (but it cannot).
I think only the markdown and plain-text file should be displayed, which
is also the behavior of GitHub.

Co-authored-by: jxshin <zhujiaxinabc@gmail.com>
(cherry picked from commit a63f14b90839821a480fb56fd9b45a27864b77d1)
---
 templates/repo/view_list.tmpl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl
index fb257bd474..7ec9acc84e 100644
--- a/templates/repo/view_list.tmpl
+++ b/templates/repo/view_list.tmpl
@@ -68,6 +68,6 @@
 		{{end}}
 	</tbody>
 </table>
-{{if .ReadmeExist}}
+{{if and .ReadmeExist (or .IsMarkup .IsPlainText)}}
 	{{template "repo/view_file" .}}
 {{end}}

From 5e81ae17983486d5b7dee65a850462592439c278 Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Thu, 25 Apr 2024 21:01:38 +0800
Subject: [PATCH 055/107] Refactor imagediff and fix regression bug (#30694)

Fix #30683

(cherry picked from commit fd63b96f6a4c5b3ea9e53d37af85e0d2d09715b9)
---
 web_src/js/features/imagediff.js | 204 +++++++++++++++----------------
 1 file changed, 102 insertions(+), 102 deletions(-)

diff --git a/web_src/js/features/imagediff.js b/web_src/js/features/imagediff.js
index 192a642834..d1b139ffde 100644
--- a/web_src/js/features/imagediff.js
+++ b/web_src/js/features/imagediff.js
@@ -1,6 +1,6 @@
 import $ from 'jquery';
 import {GET} from '../modules/fetch.js';
-import {hideElem, loadElem} from '../utils/dom.js';
+import {hideElem, loadElem, queryElemChildren} from '../utils/dom.js';
 import {parseDom} from '../utils.js';
 
 function getDefaultSvgBoundsIfUndefined(text, src) {
@@ -38,36 +38,36 @@ function getDefaultSvgBoundsIfUndefined(text, src) {
   return null;
 }
 
+function createContext(imageAfter, imageBefore) {
+  const sizeAfter = {
+    width: imageAfter?.width || 0,
+    height: imageAfter?.height || 0,
+  };
+  const sizeBefore = {
+    width: imageBefore?.width || 0,
+    height: imageBefore?.height || 0,
+  };
+  const maxSize = {
+    width: Math.max(sizeBefore.width, sizeAfter.width),
+    height: Math.max(sizeBefore.height, sizeAfter.height),
+  };
+
+  return {
+    imageAfter,
+    imageBefore,
+    sizeAfter,
+    sizeBefore,
+    maxSize,
+    ratio: [
+      Math.floor(maxSize.width - sizeAfter.width) / 2,
+      Math.floor(maxSize.height - sizeAfter.height) / 2,
+      Math.floor(maxSize.width - sizeBefore.width) / 2,
+      Math.floor(maxSize.height - sizeBefore.height) / 2,
+    ],
+  };
+}
+
 export function initImageDiff() {
-  function createContext(image1, image2) {
-    const size1 = {
-      width: image1 && image1.width || 0,
-      height: image1 && image1.height || 0,
-    };
-    const size2 = {
-      width: image2 && image2.width || 0,
-      height: image2 && image2.height || 0,
-    };
-    const max = {
-      width: Math.max(size2.width, size1.width),
-      height: Math.max(size2.height, size1.height),
-    };
-
-    return {
-      $image1: $(image1),
-      $image2: $(image2),
-      size1,
-      size2,
-      max,
-      ratio: [
-        Math.floor(max.width - size1.width) / 2,
-        Math.floor(max.height - size1.height) / 2,
-        Math.floor(max.width - size2.width) / 2,
-        Math.floor(max.height - size2.height) / 2,
-      ],
-    };
-  }
-
   $('.image-diff:not([data-image-diff-loaded])').each(async function() {
     const $container = $(this);
     this.setAttribute('data-image-diff-loaded', 'true');
@@ -116,94 +116,96 @@ export function initImageDiff() {
       initOverlay(createContext($imagesAfter[2], $imagesBefore[2]));
     }
 
-    this.querySelector(':scope > .image-diff-tabs')?.classList.remove('is-loading');
+    queryElemChildren(this, '.image-diff-tabs', (el) => el.classList.remove('is-loading'));
 
     function initSideBySide(container, sizes) {
       let factor = 1;
-      if (sizes.max.width > (diffContainerWidth - 24) / 2) {
-        factor = (diffContainerWidth - 24) / 2 / sizes.max.width;
+      if (sizes.maxSize.width > (diffContainerWidth - 24) / 2) {
+        factor = (diffContainerWidth - 24) / 2 / sizes.maxSize.width;
       }
 
-      const widthChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalWidth !== sizes.$image2[0].naturalWidth;
-      const heightChanged = sizes.$image1.length !== 0 && sizes.$image2.length !== 0 && sizes.$image1[0].naturalHeight !== sizes.$image2[0].naturalHeight;
-      if (sizes.$image1?.length) {
+      const widthChanged = sizes.imageAfter && sizes.imageBefore && sizes.imageAfter.naturalWidth !== sizes.imageBefore.naturalWidth;
+      const heightChanged = sizes.imageAfter && sizes.imageBefore && sizes.imageAfter.naturalHeight !== sizes.imageBefore.naturalHeight;
+      if (sizes.imageAfter) {
         const boundsInfoAfterWidth = container.querySelector('.bounds-info-after .bounds-info-width');
-        boundsInfoAfterWidth.textContent = `${sizes.$image1[0].naturalWidth}px`;
-        if (widthChanged) boundsInfoAfterWidth.classList.add('green');
-
+        if (boundsInfoAfterWidth) {
+          boundsInfoAfterWidth.textContent = `${sizes.imageAfter.naturalWidth}px`;
+          boundsInfoAfterWidth.classList.toggle('green', widthChanged);
+        }
         const boundsInfoAfterHeight = container.querySelector('.bounds-info-after .bounds-info-height');
-        boundsInfoAfterHeight.textContent = `${sizes.$image1[0].naturalHeight}px`;
-        if (heightChanged) boundsInfoAfterHeight.classList.add('green');
+        if (boundsInfoAfterHeight) {
+          boundsInfoAfterHeight.textContent = `${sizes.imageAfter.naturalHeight}px`;
+          boundsInfoAfterHeight.classList.toggle('green', heightChanged);
+        }
       }
 
-      if (sizes.$image2?.length) {
+      if (sizes.imageBefore) {
         const boundsInfoBeforeWidth = container.querySelector('.bounds-info-before .bounds-info-width');
-        boundsInfoBeforeWidth.textContent = `${sizes.$image2[0].naturalWidth}px`;
-        if (widthChanged) boundsInfoBeforeWidth.classList.add('red');
-
+        if (boundsInfoBeforeWidth) {
+          boundsInfoBeforeWidth.textContent = `${sizes.imageBefore.naturalWidth}px`;
+          boundsInfoBeforeWidth.classList.toggle('red', widthChanged);
+        }
         const boundsInfoBeforeHeight = container.querySelector('.bounds-info-before .bounds-info-height');
-        boundsInfoBeforeHeight.textContent = `${sizes.$image2[0].naturalHeight}px`;
-        if (heightChanged) boundsInfoBeforeHeight.classList.add('red');
+        if (boundsInfoBeforeHeight) {
+          boundsInfoBeforeHeight.textContent = `${sizes.imageBefore.naturalHeight}px`;
+          boundsInfoBeforeHeight.classList.add('red', heightChanged);
+        }
       }
 
-      const image1 = sizes.$image1[0];
-      if (image1) {
-        const container = image1.parentNode;
-        image1.style.width = `${sizes.size1.width * factor}px`;
-        image1.style.height = `${sizes.size1.height * factor}px`;
+      if (sizes.imageAfter) {
+        const container = sizes.imageAfter.parentNode;
+        sizes.imageAfter.style.width = `${sizes.sizeAfter.width * factor}px`;
+        sizes.imageAfter.style.height = `${sizes.sizeAfter.height * factor}px`;
         container.style.margin = '10px auto';
-        container.style.width = `${sizes.size1.width * factor + 2}px`;
-        container.style.height = `${sizes.size1.height * factor + 2}px`;
+        container.style.width = `${sizes.sizeAfter.width * factor + 2}px`;
+        container.style.height = `${sizes.sizeAfter.height * factor + 2}px`;
       }
 
-      const image2 = sizes.$image2[0];
-      if (image2) {
-        const container = image2.parentNode;
-        image2.style.width = `${sizes.size2.width * factor}px`;
-        image2.style.height = `${sizes.size2.height * factor}px`;
+      if (sizes.imageBefore) {
+        const container = sizes.imageBefore.parentNode;
+        sizes.imageBefore.style.width = `${sizes.sizeBefore.width * factor}px`;
+        sizes.imageBefore.style.height = `${sizes.sizeBefore.height * factor}px`;
         container.style.margin = '10px auto';
-        container.style.width = `${sizes.size2.width * factor + 2}px`;
-        container.style.height = `${sizes.size2.height * factor + 2}px`;
+        container.style.width = `${sizes.sizeBefore.width * factor + 2}px`;
+        container.style.height = `${sizes.sizeBefore.height * factor + 2}px`;
       }
     }
 
     function initSwipe(sizes) {
       let factor = 1;
-      if (sizes.max.width > diffContainerWidth - 12) {
-        factor = (diffContainerWidth - 12) / sizes.max.width;
+      if (sizes.maxSize.width > diffContainerWidth - 12) {
+        factor = (diffContainerWidth - 12) / sizes.maxSize.width;
       }
 
-      const image1 = sizes.$image1[0];
-      if (image1) {
-        const container = image1.parentNode;
+      if (sizes.imageAfter) {
+        const container = sizes.imageAfter.parentNode;
         const swipeFrame = container.parentNode;
-        image1.style.width = `${sizes.size1.width * factor}px`;
-        image1.style.height = `${sizes.size1.height * factor}px`;
+        sizes.imageAfter.style.width = `${sizes.sizeAfter.width * factor}px`;
+        sizes.imageAfter.style.height = `${sizes.sizeAfter.height * factor}px`;
         container.style.margin = `0px ${sizes.ratio[0] * factor}px`;
-        container.style.width = `${sizes.size1.width * factor + 2}px`;
-        container.style.height = `${sizes.size1.height * factor + 2}px`;
+        container.style.width = `${sizes.sizeAfter.width * factor + 2}px`;
+        container.style.height = `${sizes.sizeAfter.height * factor + 2}px`;
         swipeFrame.style.padding = `${sizes.ratio[1] * factor}px 0 0 0`;
-        swipeFrame.style.width = `${sizes.max.width * factor + 2}px`;
+        swipeFrame.style.width = `${sizes.maxSize.width * factor + 2}px`;
       }
 
-      const image2 = sizes.$image2[0];
-      if (image2) {
-        const container = image2.parentNode;
+      if (sizes.imageBefore) {
+        const container = sizes.imageBefore.parentNode;
         const swipeFrame = container.parentNode;
-        image2.style.width = `${sizes.size2.width * factor}px`;
-        image2.style.height = `${sizes.size2.height * factor}px`;
+        sizes.imageBefore.style.width = `${sizes.sizeBefore.width * factor}px`;
+        sizes.imageBefore.style.height = `${sizes.sizeBefore.height * factor}px`;
         container.style.margin = `${sizes.ratio[3] * factor}px ${sizes.ratio[2] * factor}px`;
-        container.style.width = `${sizes.size2.width * factor + 2}px`;
-        container.style.height = `${sizes.size2.height * factor + 2}px`;
-        swipeFrame.style.width = `${sizes.max.width * factor + 2}px`;
-        swipeFrame.style.height = `${sizes.max.height * factor + 2}px`;
+        container.style.width = `${sizes.sizeBefore.width * factor + 2}px`;
+        container.style.height = `${sizes.sizeBefore.height * factor + 2}px`;
+        swipeFrame.style.width = `${sizes.maxSize.width * factor + 2}px`;
+        swipeFrame.style.height = `${sizes.maxSize.height * factor + 2}px`;
       }
 
       // extra height for inner "position: absolute" elements
       const swipe = $container.find('.diff-swipe')[0];
       if (swipe) {
-        swipe.style.width = `${sizes.max.width * factor + 2}px`;
-        swipe.style.height = `${sizes.max.height * factor + 30}px`;
+        swipe.style.width = `${sizes.maxSize.width * factor + 2}px`;
+        swipe.style.height = `${sizes.maxSize.height * factor + 30}px`;
       }
 
       $container.find('.swipe-bar').on('mousedown', function(e) {
@@ -229,39 +231,37 @@ export function initImageDiff() {
 
     function initOverlay(sizes) {
       let factor = 1;
-      if (sizes.max.width > diffContainerWidth - 12) {
-        factor = (diffContainerWidth - 12) / sizes.max.width;
+      if (sizes.maxSize.width > diffContainerWidth - 12) {
+        factor = (diffContainerWidth - 12) / sizes.maxSize.width;
       }
 
-      const image1 = sizes.$image1[0];
-      if (image1) {
-        const container = image1.parentNode;
-        image1.style.width = `${sizes.size1.width * factor}px`;
-        image1.style.height = `${sizes.size1.height * factor}px`;
+      if (sizes.imageAfter) {
+        const container = sizes.imageAfter.parentNode;
+        sizes.imageAfter.style.width = `${sizes.sizeAfter.width * factor}px`;
+        sizes.imageAfter.style.height = `${sizes.sizeAfter.height * factor}px`;
         container.style.margin = `${sizes.ratio[1] * factor}px ${sizes.ratio[0] * factor}px`;
-        container.style.width = `${sizes.size1.width * factor + 2}px`;
-        container.style.height = `${sizes.size1.height * factor + 2}px`;
+        container.style.width = `${sizes.sizeAfter.width * factor + 2}px`;
+        container.style.height = `${sizes.sizeAfter.height * factor + 2}px`;
       }
 
-      const image2 = sizes.$image2[0];
-      if (image2) {
-        const container = image2.parentNode;
+      if (sizes.imageBefore) {
+        const container = sizes.imageBefore.parentNode;
         const overlayFrame = container.parentNode;
-        image2.style.width = `${sizes.size2.width * factor}px`;
-        image2.style.height = `${sizes.size2.height * factor}px`;
+        sizes.imageBefore.style.width = `${sizes.sizeBefore.width * factor}px`;
+        sizes.imageBefore.style.height = `${sizes.sizeBefore.height * factor}px`;
         container.style.margin = `${sizes.ratio[3] * factor}px ${sizes.ratio[2] * factor}px`;
-        container.style.width = `${sizes.size2.width * factor + 2}px`;
-        container.style.height = `${sizes.size2.height * factor + 2}px`;
+        container.style.width = `${sizes.sizeBefore.width * factor + 2}px`;
+        container.style.height = `${sizes.sizeBefore.height * factor + 2}px`;
 
         // some inner elements are `position: absolute`, so the container's height must be large enough
-        overlayFrame.style.width = `${sizes.max.width * factor + 2}px`;
-        overlayFrame.style.height = `${sizes.max.height * factor + 2}px`;
+        overlayFrame.style.width = `${sizes.maxSize.width * factor + 2}px`;
+        overlayFrame.style.height = `${sizes.maxSize.height * factor + 2}px`;
       }
 
       const rangeInput = $container[0].querySelector('input[type="range"]');
       function updateOpacity() {
-        if (sizes?.$image1?.[0]) {
-          sizes.$image1[0].parentNode.style.opacity = `${rangeInput.value / 100}`;
+        if (sizes.imageAfter) {
+          sizes.imageAfter.parentNode.style.opacity = `${rangeInput.value / 100}`;
         }
       }
       rangeInput?.addEventListener('input', updateOpacity);

From 9ea73fcb098be565522ed67e81940e9fe93722dc Mon Sep 17 00:00:00 2001
From: Yarden Shoham <git@yardenshoham.com>
Date: Fri, 26 Apr 2024 10:27:34 +0300
Subject: [PATCH 056/107] Bump htmx version to 1.9.12 (#30711)

There are no breaking changes. I tested and everything works as before.

Signed-off-by: Yarden Shoham <git@yardenshoham.com>
(cherry picked from commit 68a3e6b5e64b4035aa0659cb6daa1c4d1eec892a)
---
 package-lock.json | 8 ++++----
 package.json      | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index be6eadaee6..3f50d23588 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -28,7 +28,7 @@
         "esbuild-loader": "4.1.0",
         "escape-goat": "4.0.0",
         "fast-glob": "3.3.2",
-        "htmx.org": "1.9.11",
+        "htmx.org": "1.9.12",
         "idiomorph": "0.3.0",
         "jquery": "3.7.1",
         "katex": "0.16.10",
@@ -6924,9 +6924,9 @@
       }
     },
     "node_modules/htmx.org": {
-      "version": "1.9.11",
-      "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.11.tgz",
-      "integrity": "sha512-WlVuICn8dfNOOgYmdYzYG8zSnP3++AdHkMHooQAzGZObWpVXYathpz/I37ycF4zikR6YduzfCvEcxk20JkIUsw=="
+      "version": "1.9.12",
+      "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz",
+      "integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw=="
     },
     "node_modules/human-signals": {
       "version": "5.0.0",
diff --git a/package.json b/package.json
index 5a95503a58..ce6e673f11 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
     "esbuild-loader": "4.1.0",
     "escape-goat": "4.0.0",
     "fast-glob": "3.3.2",
-    "htmx.org": "1.9.11",
+    "htmx.org": "1.9.12",
     "idiomorph": "0.3.0",
     "jquery": "3.7.1",
     "katex": "0.16.10",

From 089e95f250d130e7906ec45d1fe3a7c51a0f8573 Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Fri, 26 Apr 2024 19:21:04 +0800
Subject: [PATCH 057/107] Fix code search input for different views (#30678)

Now only show the "code search" on the repo home page, because it only
does global search.
So do not show it when viewing file or directory to avoid misleading
users (it doesn't search in a directory)

(cherry picked from commit 993736d838c36e26951b6cfea9c6a549958addd1)
---
 routers/web/repo/commit.go  |  2 --
 routers/web/repo/compare.go |  1 -
 routers/web/repo/pull.go    |  1 -
 templates/repo/home.tmpl    | 15 ++++++++++++---
 4 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index 718454e063..33491ec696 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -212,8 +212,6 @@ func SearchCommits(ctx *context.Context) {
 
 // FileHistory show a file's reversions
 func FileHistory(ctx *context.Context) {
-	ctx.Data["IsRepoToolbarCommits"] = true
-
 	fileName := ctx.Repo.TreePath
 	if len(fileName) == 0 {
 		Commits(ctx)
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index b07209c779..a61e23add3 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -800,7 +800,6 @@ func CompareDiff(ctx *context.Context) {
 	}
 	ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + separator + base.ShortSha(afterCommitID)
 
-	ctx.Data["IsRepoToolbarCommits"] = true
 	ctx.Data["IsDiffCompare"] = true
 	_, templateErrs := setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates)
 
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index 3f10034c6b..a0dd36927f 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -1436,7 +1436,6 @@ func CompareAndPullRequestPost(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
 	ctx.Data["PageIsComparePull"] = true
 	ctx.Data["IsDiffCompare"] = true
-	ctx.Data["IsRepoToolbarCommits"] = true
 	ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
 	ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
 	upload.AddUploadContext(ctx, "comment")
diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl
index 3c994af194..235a7e4503 100644
--- a/templates/repo/home.tmpl
+++ b/templates/repo/home.tmpl
@@ -106,7 +106,16 @@
 						{{ctx.Locale.Tr "repo.use_template"}}
 					</a>
 				{{end}}
-				{{if (not $isHomepage)}}
+				{{if $isHomepage}}
+					{{/* only show the "code search" on the repo home page, it only does global search,
+						so do not show it when viewing file or directory to avoid misleading users (it doesn't search in a directory) */}}
+					<form class="ignore-dirty" action="{{.RepoLink}}/search" method="get">
+						<div class="ui small action input">
+							<input name="q" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
+							{{template "shared/search/button"}}
+						</div>
+					</form>
+				{{else}}
 					<span class="breadcrumb repo-path tw-ml-1">
 						<a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
 						{{- range $i, $v := .TreeNames -}}
@@ -145,7 +154,7 @@
 					</div>
 					{{template "repo/cite/cite_modal" .}}
 				{{end}}
-				{{if and (not $isHomepage) (not .IsViewFile) (not .IsBlame)}}
+				{{if and (not $isHomepage) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
 					<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
 						{{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}}
 					</a>
@@ -156,7 +165,7 @@
 			{{template "repo/view_file" .}}
 		{{else if .IsBlame}}
 			{{template "repo/blame" .}}
-		{{else}}
+		{{else}}{{/* IsViewDirectory */}}
 			{{template "repo/view_list" .}}
 		{{end}}
 	</div>

From 7b456a28d18ae5a016341934184bf1e0a4dad986 Mon Sep 17 00:00:00 2001
From: Bo-Yi Wu <appleboy.tw@gmail.com>
Date: Fri, 26 Apr 2024 21:11:49 +0800
Subject: [PATCH 058/107] feat(api): enhance Actions Secrets Management API for
 repository (#30656)

- Add endpoint to list repository action secrets in API routes
- Implement `ListActionsSecrets` function to retrieve action secrets
from the database
- Update Swagger documentation to include the new
`/repos/{owner}/{repo}/actions/secrets` endpoint
- Add `actions` package import and define new routes for actions,
secrets, variables, and runners in `api.go`.
- Refactor action-related API functions into `Action` struct methods in
`org/action.go` and `repo/action.go`.
- Remove `actionAPI` struct and related functions, replacing them with
`NewAction()` calls.
- Rename `variables.go` to `action.go` in `org` directory.
- Delete `runners.go` and `secrets.go` in both `org` and `repo`
directories, consolidating their content into `action.go`.
- Update copyright year and add new imports in `org/action.go`.
- Implement `API` interface in `services/actions/interface.go` for
action-related methods.
- Remove individual action-related functions and replace them with
methods on the `Action` struct in `repo/action.go`.

---------

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Signed-off-by: appleboy <appleboy.tw@gmail.com>
(cherry picked from commit 852547d0dc70299589c7bf8d00ea462ed709b8e5)

Conflicts:
	routers/api/v1/api.go
	trivial conflict because of Fix #2512 /api/forgejo/v1/version auth check (#2582)
---
 routers/api/v1/api.go                         |  80 ++++----
 .../api/v1/org/{variables.go => action.go}    | 192 +++++++++++++++++-
 routers/api/v1/org/runners.go                 |  31 ---
 routers/api/v1/org/secrets.go                 | 166 ---------------
 routers/api/v1/repo/action.go                 | 108 +++++++++-
 routers/api/v1/repo/runners.go                |  34 ----
 services/actions/interface.go                 |  28 +++
 templates/swagger/v1_json.tmpl                |  48 +++++
 tests/integration/api_repo_secrets_test.go    |   8 +-
 9 files changed, 410 insertions(+), 285 deletions(-)
 rename routers/api/v1/org/{variables.go => action.go} (58%)
 delete mode 100644 routers/api/v1/org/runners.go
 delete mode 100644 routers/api/v1/org/secrets.go
 delete mode 100644 routers/api/v1/repo/runners.go
 create mode 100644 services/actions/interface.go

diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index a15210ac51..e4c848cd2f 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -93,6 +93,7 @@ import (
 	"code.gitea.io/gitea/routers/api/v1/repo"
 	"code.gitea.io/gitea/routers/api/v1/settings"
 	"code.gitea.io/gitea/routers/api/v1/user"
+	"code.gitea.io/gitea/services/actions"
 	"code.gitea.io/gitea/services/auth"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/forms"
@@ -753,6 +754,34 @@ func Routes() *web.Route {
 
 	m.Use(shared.Middlewares()...)
 
+	addActionsRoutes := func(
+		m *web.Route,
+		reqChecker func(ctx *context.APIContext),
+		act actions.API,
+	) {
+		m.Group("/actions", func() {
+			m.Group("/secrets", func() {
+				m.Get("", reqToken(), reqChecker, act.ListActionsSecrets)
+				m.Combo("/{secretname}").
+					Put(reqToken(), reqChecker, bind(api.CreateOrUpdateSecretOption{}), act.CreateOrUpdateSecret).
+					Delete(reqToken(), reqChecker, act.DeleteSecret)
+			})
+
+			m.Group("/variables", func() {
+				m.Get("", reqToken(), reqChecker, act.ListVariables)
+				m.Combo("/{variablename}").
+					Get(reqToken(), reqChecker, act.GetVariable).
+					Delete(reqToken(), reqChecker, act.DeleteVariable).
+					Post(reqToken(), reqChecker, bind(api.CreateVariableOption{}), act.CreateVariable).
+					Put(reqToken(), reqChecker, bind(api.UpdateVariableOption{}), act.UpdateVariable)
+			})
+
+			m.Group("/runners", func() {
+				m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
+			})
+		})
+	}
+
 	m.Group("", func() {
 		// Miscellaneous (no scope required)
 		if setting.API.EnableSwagger {
@@ -994,26 +1023,11 @@ func Routes() *web.Route {
 					m.Post("/accept", repo.AcceptTransfer)
 					m.Post("/reject", repo.RejectTransfer)
 				}, reqToken())
-				m.Group("/actions", func() {
-					m.Group("/secrets", func() {
-						m.Combo("/{secretname}").
-							Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret).
-							Delete(reqToken(), reqOwner(), repo.DeleteSecret)
-					})
-
-					m.Group("/variables", func() {
-						m.Get("", reqToken(), reqOwner(), repo.ListVariables)
-						m.Combo("/{variablename}").
-							Get(reqToken(), reqOwner(), repo.GetVariable).
-							Delete(reqToken(), reqOwner(), repo.DeleteVariable).
-							Post(reqToken(), reqOwner(), bind(api.CreateVariableOption{}), repo.CreateVariable).
-							Put(reqToken(), reqOwner(), bind(api.UpdateVariableOption{}), repo.UpdateVariable)
-					})
-
-					m.Group("/runners", func() {
-						m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken)
-					})
-				})
+				addActionsRoutes(
+					m,
+					reqOwner(),
+					repo.NewAction(),
+				)
 				m.Group("/hooks/git", func() {
 					m.Combo("").Get(repo.ListGitHooks)
 					m.Group("/{id}", func() {
@@ -1405,27 +1419,11 @@ func Routes() *web.Route {
 				m.Combo("/{username}").Get(reqToken(), org.IsMember).
 					Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
 			})
-			m.Group("/actions", func() {
-				m.Group("/secrets", func() {
-					m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
-					m.Combo("/{secretname}").
-						Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret).
-						Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
-				})
-
-				m.Group("/variables", func() {
-					m.Get("", reqToken(), reqOrgOwnership(), org.ListVariables)
-					m.Combo("/{variablename}").
-						Get(reqToken(), reqOrgOwnership(), org.GetVariable).
-						Delete(reqToken(), reqOrgOwnership(), org.DeleteVariable).
-						Post(reqToken(), reqOrgOwnership(), bind(api.CreateVariableOption{}), org.CreateVariable).
-						Put(reqToken(), reqOrgOwnership(), bind(api.UpdateVariableOption{}), org.UpdateVariable)
-				})
-
-				m.Group("/runners", func() {
-					m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken)
-				})
-			})
+			addActionsRoutes(
+				m,
+				reqOrgOwnership(),
+				org.NewAction(),
+			)
 			m.Group("/public_members", func() {
 				m.Get("", org.ListPublicMembers)
 				m.Combo("/{username}").Get(org.IsPublicMember).
diff --git a/routers/api/v1/org/variables.go b/routers/api/v1/org/action.go
similarity index 58%
rename from routers/api/v1/org/variables.go
rename to routers/api/v1/org/action.go
index eaf7bdc45b..03a1fa8ccc 100644
--- a/routers/api/v1/org/variables.go
+++ b/routers/api/v1/org/action.go
@@ -9,16 +9,188 @@ import (
 
 	actions_model "code.gitea.io/gitea/models/actions"
 	"code.gitea.io/gitea/models/db"
+	secret_model "code.gitea.io/gitea/models/secret"
 	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
+	"code.gitea.io/gitea/routers/api/v1/shared"
 	"code.gitea.io/gitea/routers/api/v1/utils"
 	actions_service "code.gitea.io/gitea/services/actions"
 	"code.gitea.io/gitea/services/context"
+	secret_service "code.gitea.io/gitea/services/secrets"
 )
 
+// ListActionsSecrets list an organization's actions secrets
+func (Action) ListActionsSecrets(ctx *context.APIContext) {
+	// swagger:operation GET /orgs/{org}/actions/secrets organization orgListActionsSecrets
+	// ---
+	// summary: List an organization's actions secrets
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: org
+	//   in: path
+	//   description: name of the organization
+	//   type: string
+	//   required: true
+	// - name: page
+	//   in: query
+	//   description: page number of results to return (1-based)
+	//   type: integer
+	// - name: limit
+	//   in: query
+	//   description: page size of results
+	//   type: integer
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/SecretList"
+	//   "404":
+	//     "$ref": "#/responses/notFound"
+
+	opts := &secret_model.FindSecretsOptions{
+		OwnerID:     ctx.Org.Organization.ID,
+		ListOptions: utils.GetListOptions(ctx),
+	}
+
+	secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
+	if err != nil {
+		ctx.InternalServerError(err)
+		return
+	}
+
+	apiSecrets := make([]*api.Secret, len(secrets))
+	for k, v := range secrets {
+		apiSecrets[k] = &api.Secret{
+			Name:    v.Name,
+			Created: v.CreatedUnix.AsTime(),
+		}
+	}
+
+	ctx.SetTotalCountHeader(count)
+	ctx.JSON(http.StatusOK, apiSecrets)
+}
+
+// create or update one secret of the organization
+func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
+	// swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret
+	// ---
+	// summary: Create or Update a secret value in an organization
+	// consumes:
+	// - application/json
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: org
+	//   in: path
+	//   description: name of organization
+	//   type: string
+	//   required: true
+	// - name: secretname
+	//   in: path
+	//   description: name of the secret
+	//   type: string
+	//   required: true
+	// - name: body
+	//   in: body
+	//   schema:
+	//     "$ref": "#/definitions/CreateOrUpdateSecretOption"
+	// responses:
+	//   "201":
+	//     description: response when creating a secret
+	//   "204":
+	//     description: response when updating a secret
+	//   "400":
+	//     "$ref": "#/responses/error"
+	//   "404":
+	//     "$ref": "#/responses/notFound"
+
+	opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
+
+	_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"), opt.Data)
+	if err != nil {
+		if errors.Is(err, util.ErrInvalidArgument) {
+			ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
+		} else if errors.Is(err, util.ErrNotExist) {
+			ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err)
+		} else {
+			ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
+		}
+		return
+	}
+
+	if created {
+		ctx.Status(http.StatusCreated)
+	} else {
+		ctx.Status(http.StatusNoContent)
+	}
+}
+
+// DeleteSecret delete one secret of the organization
+func (Action) DeleteSecret(ctx *context.APIContext) {
+	// swagger:operation DELETE /orgs/{org}/actions/secrets/{secretname} organization deleteOrgSecret
+	// ---
+	// summary: Delete a secret in an organization
+	// consumes:
+	// - application/json
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: org
+	//   in: path
+	//   description: name of organization
+	//   type: string
+	//   required: true
+	// - name: secretname
+	//   in: path
+	//   description: name of the secret
+	//   type: string
+	//   required: true
+	// responses:
+	//   "204":
+	//     description: delete one secret of the organization
+	//   "400":
+	//     "$ref": "#/responses/error"
+	//   "404":
+	//     "$ref": "#/responses/notFound"
+
+	err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"))
+	if err != nil {
+		if errors.Is(err, util.ErrInvalidArgument) {
+			ctx.Error(http.StatusBadRequest, "DeleteSecret", err)
+		} else if errors.Is(err, util.ErrNotExist) {
+			ctx.Error(http.StatusNotFound, "DeleteSecret", err)
+		} else {
+			ctx.Error(http.StatusInternalServerError, "DeleteSecret", err)
+		}
+		return
+	}
+
+	ctx.Status(http.StatusNoContent)
+}
+
+// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
+// GetRegistrationToken returns the token to register org runners
+func (Action) GetRegistrationToken(ctx *context.APIContext) {
+	// swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken
+	// ---
+	// summary: Get an organization's actions runner registration token
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: org
+	//   in: path
+	//   description: name of the organization
+	//   type: string
+	//   required: true
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/RegistrationToken"
+
+	shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
+}
+
 // ListVariables list org-level variables
-func ListVariables(ctx *context.APIContext) {
+func (Action) ListVariables(ctx *context.APIContext) {
 	// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
 	// ---
 	// summary: Get an org-level variables list
@@ -70,7 +242,7 @@ func ListVariables(ctx *context.APIContext) {
 }
 
 // GetVariable get an org-level variable
-func GetVariable(ctx *context.APIContext) {
+func (Action) GetVariable(ctx *context.APIContext) {
 	// swagger:operation GET /orgs/{org}/actions/variables/{variablename} organization getOrgVariable
 	// ---
 	// summary: Get an org-level variable
@@ -119,7 +291,7 @@ func GetVariable(ctx *context.APIContext) {
 }
 
 // DeleteVariable delete an org-level variable
-func DeleteVariable(ctx *context.APIContext) {
+func (Action) DeleteVariable(ctx *context.APIContext) {
 	// swagger:operation DELETE /orgs/{org}/actions/variables/{variablename} organization deleteOrgVariable
 	// ---
 	// summary: Delete an org-level variable
@@ -163,7 +335,7 @@ func DeleteVariable(ctx *context.APIContext) {
 }
 
 // CreateVariable create an org-level variable
-func CreateVariable(ctx *context.APIContext) {
+func (Action) CreateVariable(ctx *context.APIContext) {
 	// swagger:operation POST /orgs/{org}/actions/variables/{variablename} organization createOrgVariable
 	// ---
 	// summary: Create an org-level variable
@@ -227,7 +399,7 @@ func CreateVariable(ctx *context.APIContext) {
 }
 
 // UpdateVariable update an org-level variable
-func UpdateVariable(ctx *context.APIContext) {
+func (Action) UpdateVariable(ctx *context.APIContext) {
 	// swagger:operation PUT /orgs/{org}/actions/variables/{variablename} organization updateOrgVariable
 	// ---
 	// summary: Update an org-level variable
@@ -289,3 +461,13 @@ func UpdateVariable(ctx *context.APIContext) {
 
 	ctx.Status(http.StatusNoContent)
 }
+
+var _ actions_service.API = new(Action)
+
+// Action implements actions_service.API
+type Action struct{}
+
+// NewAction creates a new Action service
+func NewAction() actions_service.API {
+	return Action{}
+}
diff --git a/routers/api/v1/org/runners.go b/routers/api/v1/org/runners.go
deleted file mode 100644
index 2a52bd8778..0000000000
--- a/routers/api/v1/org/runners.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package org
-
-import (
-	"code.gitea.io/gitea/routers/api/v1/shared"
-	"code.gitea.io/gitea/services/context"
-)
-
-// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
-
-// GetRegistrationToken returns the token to register org runners
-func GetRegistrationToken(ctx *context.APIContext) {
-	// swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken
-	// ---
-	// summary: Get an organization's actions runner registration token
-	// produces:
-	// - application/json
-	// parameters:
-	// - name: org
-	//   in: path
-	//   description: name of the organization
-	//   type: string
-	//   required: true
-	// responses:
-	//   "200":
-	//     "$ref": "#/responses/RegistrationToken"
-
-	shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
-}
diff --git a/routers/api/v1/org/secrets.go b/routers/api/v1/org/secrets.go
deleted file mode 100644
index abb6bb26c4..0000000000
--- a/routers/api/v1/org/secrets.go
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package org
-
-import (
-	"errors"
-	"net/http"
-
-	"code.gitea.io/gitea/models/db"
-	secret_model "code.gitea.io/gitea/models/secret"
-	api "code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/util"
-	"code.gitea.io/gitea/modules/web"
-	"code.gitea.io/gitea/routers/api/v1/utils"
-	"code.gitea.io/gitea/services/context"
-	secret_service "code.gitea.io/gitea/services/secrets"
-)
-
-// ListActionsSecrets list an organization's actions secrets
-func ListActionsSecrets(ctx *context.APIContext) {
-	// swagger:operation GET /orgs/{org}/actions/secrets organization orgListActionsSecrets
-	// ---
-	// summary: List an organization's actions secrets
-	// produces:
-	// - application/json
-	// parameters:
-	// - name: org
-	//   in: path
-	//   description: name of the organization
-	//   type: string
-	//   required: true
-	// - name: page
-	//   in: query
-	//   description: page number of results to return (1-based)
-	//   type: integer
-	// - name: limit
-	//   in: query
-	//   description: page size of results
-	//   type: integer
-	// responses:
-	//   "200":
-	//     "$ref": "#/responses/SecretList"
-	//   "404":
-	//     "$ref": "#/responses/notFound"
-
-	opts := &secret_model.FindSecretsOptions{
-		OwnerID:     ctx.Org.Organization.ID,
-		ListOptions: utils.GetListOptions(ctx),
-	}
-
-	secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
-	if err != nil {
-		ctx.InternalServerError(err)
-		return
-	}
-
-	apiSecrets := make([]*api.Secret, len(secrets))
-	for k, v := range secrets {
-		apiSecrets[k] = &api.Secret{
-			Name:    v.Name,
-			Created: v.CreatedUnix.AsTime(),
-		}
-	}
-
-	ctx.SetTotalCountHeader(count)
-	ctx.JSON(http.StatusOK, apiSecrets)
-}
-
-// create or update one secret of the organization
-func CreateOrUpdateSecret(ctx *context.APIContext) {
-	// swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret
-	// ---
-	// summary: Create or Update a secret value in an organization
-	// consumes:
-	// - application/json
-	// produces:
-	// - application/json
-	// parameters:
-	// - name: org
-	//   in: path
-	//   description: name of organization
-	//   type: string
-	//   required: true
-	// - name: secretname
-	//   in: path
-	//   description: name of the secret
-	//   type: string
-	//   required: true
-	// - name: body
-	//   in: body
-	//   schema:
-	//     "$ref": "#/definitions/CreateOrUpdateSecretOption"
-	// responses:
-	//   "201":
-	//     description: response when creating a secret
-	//   "204":
-	//     description: response when updating a secret
-	//   "400":
-	//     "$ref": "#/responses/error"
-	//   "404":
-	//     "$ref": "#/responses/notFound"
-
-	opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
-
-	_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"), opt.Data)
-	if err != nil {
-		if errors.Is(err, util.ErrInvalidArgument) {
-			ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
-		} else if errors.Is(err, util.ErrNotExist) {
-			ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err)
-		} else {
-			ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
-		}
-		return
-	}
-
-	if created {
-		ctx.Status(http.StatusCreated)
-	} else {
-		ctx.Status(http.StatusNoContent)
-	}
-}
-
-// DeleteSecret delete one secret of the organization
-func DeleteSecret(ctx *context.APIContext) {
-	// swagger:operation DELETE /orgs/{org}/actions/secrets/{secretname} organization deleteOrgSecret
-	// ---
-	// summary: Delete a secret in an organization
-	// consumes:
-	// - application/json
-	// produces:
-	// - application/json
-	// parameters:
-	// - name: org
-	//   in: path
-	//   description: name of organization
-	//   type: string
-	//   required: true
-	// - name: secretname
-	//   in: path
-	//   description: name of the secret
-	//   type: string
-	//   required: true
-	// responses:
-	//   "204":
-	//     description: delete one secret of the organization
-	//   "400":
-	//     "$ref": "#/responses/error"
-	//   "404":
-	//     "$ref": "#/responses/notFound"
-
-	err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"))
-	if err != nil {
-		if errors.Is(err, util.ErrInvalidArgument) {
-			ctx.Error(http.StatusBadRequest, "DeleteSecret", err)
-		} else if errors.Is(err, util.ErrNotExist) {
-			ctx.Error(http.StatusNotFound, "DeleteSecret", err)
-		} else {
-			ctx.Error(http.StatusInternalServerError, "DeleteSecret", err)
-		}
-		return
-	}
-
-	ctx.Status(http.StatusNoContent)
-}
diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go
index 03321d956d..311cfca6e9 100644
--- a/routers/api/v1/repo/action.go
+++ b/routers/api/v1/repo/action.go
@@ -9,17 +9,76 @@ import (
 
 	actions_model "code.gitea.io/gitea/models/actions"
 	"code.gitea.io/gitea/models/db"
+	secret_model "code.gitea.io/gitea/models/secret"
 	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
+	"code.gitea.io/gitea/routers/api/v1/shared"
 	"code.gitea.io/gitea/routers/api/v1/utils"
 	actions_service "code.gitea.io/gitea/services/actions"
 	"code.gitea.io/gitea/services/context"
 	secret_service "code.gitea.io/gitea/services/secrets"
 )
 
+// ListActionsSecrets list an repo's actions secrets
+func (Action) ListActionsSecrets(ctx *context.APIContext) {
+	// swagger:operation GET /repos/{owner}/{repo}/actions/secrets repository repoListActionsSecrets
+	// ---
+	// summary: List an repo's actions secrets
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: owner
+	//   in: path
+	//   description: owner of the repository
+	//   type: string
+	//   required: true
+	// - name: repo
+	//   in: path
+	//   description: name of the repository
+	//   type: string
+	//   required: true
+	// - name: page
+	//   in: query
+	//   description: page number of results to return (1-based)
+	//   type: integer
+	// - name: limit
+	//   in: query
+	//   description: page size of results
+	//   type: integer
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/SecretList"
+	//   "404":
+	//     "$ref": "#/responses/notFound"
+
+	repo := ctx.Repo.Repository
+
+	opts := &secret_model.FindSecretsOptions{
+		RepoID:      repo.ID,
+		ListOptions: utils.GetListOptions(ctx),
+	}
+
+	secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
+	if err != nil {
+		ctx.InternalServerError(err)
+		return
+	}
+
+	apiSecrets := make([]*api.Secret, len(secrets))
+	for k, v := range secrets {
+		apiSecrets[k] = &api.Secret{
+			Name:    v.Name,
+			Created: v.CreatedUnix.AsTime(),
+		}
+	}
+
+	ctx.SetTotalCountHeader(count)
+	ctx.JSON(http.StatusOK, apiSecrets)
+}
+
 // create or update one secret of the repository
-func CreateOrUpdateSecret(ctx *context.APIContext) {
+func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
 	// swagger:operation PUT /repos/{owner}/{repo}/actions/secrets/{secretname} repository updateRepoSecret
 	// ---
 	// summary: Create or Update a secret value in a repository
@@ -82,7 +141,7 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
 }
 
 // DeleteSecret delete one secret of the repository
-func DeleteSecret(ctx *context.APIContext) {
+func (Action) DeleteSecret(ctx *context.APIContext) {
 	// swagger:operation DELETE /repos/{owner}/{repo}/actions/secrets/{secretname} repository deleteRepoSecret
 	// ---
 	// summary: Delete a secret in a repository
@@ -133,7 +192,7 @@ func DeleteSecret(ctx *context.APIContext) {
 }
 
 // GetVariable get a repo-level variable
-func GetVariable(ctx *context.APIContext) {
+func (Action) GetVariable(ctx *context.APIContext) {
 	// swagger:operation GET /repos/{owner}/{repo}/actions/variables/{variablename} repository getRepoVariable
 	// ---
 	// summary: Get a repo-level variable
@@ -186,7 +245,7 @@ func GetVariable(ctx *context.APIContext) {
 }
 
 // DeleteVariable delete a repo-level variable
-func DeleteVariable(ctx *context.APIContext) {
+func (Action) DeleteVariable(ctx *context.APIContext) {
 	// swagger:operation DELETE /repos/{owner}/{repo}/actions/variables/{variablename} repository deleteRepoVariable
 	// ---
 	// summary: Delete a repo-level variable
@@ -235,7 +294,7 @@ func DeleteVariable(ctx *context.APIContext) {
 }
 
 // CreateVariable create a repo-level variable
-func CreateVariable(ctx *context.APIContext) {
+func (Action) CreateVariable(ctx *context.APIContext) {
 	// swagger:operation POST /repos/{owner}/{repo}/actions/variables/{variablename} repository createRepoVariable
 	// ---
 	// summary: Create a repo-level variable
@@ -302,7 +361,7 @@ func CreateVariable(ctx *context.APIContext) {
 }
 
 // UpdateVariable update a repo-level variable
-func UpdateVariable(ctx *context.APIContext) {
+func (Action) UpdateVariable(ctx *context.APIContext) {
 	// swagger:operation PUT /repos/{owner}/{repo}/actions/variables/{variablename} repository updateRepoVariable
 	// ---
 	// summary: Update a repo-level variable
@@ -369,7 +428,7 @@ func UpdateVariable(ctx *context.APIContext) {
 }
 
 // ListVariables list repo-level variables
-func ListVariables(ctx *context.APIContext) {
+func (Action) ListVariables(ctx *context.APIContext) {
 	// swagger:operation GET /repos/{owner}/{repo}/actions/variables repository getRepoVariablesList
 	// ---
 	// summary: Get repo-level variables list
@@ -423,3 +482,38 @@ func ListVariables(ctx *context.APIContext) {
 	ctx.SetTotalCountHeader(count)
 	ctx.JSON(http.StatusOK, variables)
 }
+
+// GetRegistrationToken returns the token to register repo runners
+func (Action) GetRegistrationToken(ctx *context.APIContext) {
+	// swagger:operation GET /repos/{owner}/{repo}/runners/registration-token repository repoGetRunnerRegistrationToken
+	// ---
+	// summary: Get a repository's actions runner registration token
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: owner
+	//   in: path
+	//   description: owner of the repo
+	//   type: string
+	//   required: true
+	// - name: repo
+	//   in: path
+	//   description: name of the repo
+	//   type: string
+	//   required: true
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/RegistrationToken"
+
+	shared.GetRegistrationToken(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
+}
+
+var _ actions_service.API = new(Action)
+
+// Action implements actions_service.API
+type Action struct{}
+
+// NewAction creates a new Action service
+func NewAction() actions_service.API {
+	return Action{}
+}
diff --git a/routers/api/v1/repo/runners.go b/routers/api/v1/repo/runners.go
deleted file mode 100644
index fe133b311d..0000000000
--- a/routers/api/v1/repo/runners.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package repo
-
-import (
-	"code.gitea.io/gitea/routers/api/v1/shared"
-	"code.gitea.io/gitea/services/context"
-)
-
-// GetRegistrationToken returns the token to register repo runners
-func GetRegistrationToken(ctx *context.APIContext) {
-	// swagger:operation GET /repos/{owner}/{repo}/runners/registration-token repository repoGetRunnerRegistrationToken
-	// ---
-	// summary: Get a repository's actions runner registration token
-	// produces:
-	// - application/json
-	// parameters:
-	// - name: owner
-	//   in: path
-	//   description: owner of the repo
-	//   type: string
-	//   required: true
-	// - name: repo
-	//   in: path
-	//   description: name of the repo
-	//   type: string
-	//   required: true
-	// responses:
-	//   "200":
-	//     "$ref": "#/responses/RegistrationToken"
-
-	shared.GetRegistrationToken(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
-}
diff --git a/services/actions/interface.go b/services/actions/interface.go
new file mode 100644
index 0000000000..d4fa782fec
--- /dev/null
+++ b/services/actions/interface.go
@@ -0,0 +1,28 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package actions
+
+import "code.gitea.io/gitea/services/context"
+
+// API for actions of a repository or organization
+type API interface {
+	// ListActionsSecrets list secrets
+	ListActionsSecrets(*context.APIContext)
+	// CreateOrUpdateSecret create or update a secret
+	CreateOrUpdateSecret(*context.APIContext)
+	// DeleteSecret delete a secret
+	DeleteSecret(*context.APIContext)
+	// ListVariables list variables
+	ListVariables(*context.APIContext)
+	// GetVariable get a variable
+	GetVariable(*context.APIContext)
+	// DeleteVariable delete a variable
+	DeleteVariable(*context.APIContext)
+	// CreateVariable create a variable
+	CreateVariable(*context.APIContext)
+	// UpdateVariable update a variable
+	UpdateVariable(*context.APIContext)
+	// GetRegistrationToken get registration token
+	GetRegistrationToken(*context.APIContext)
+}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 2473e96006..dbf9eb89e2 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -3711,6 +3711,54 @@
         }
       }
     },
+    "/repos/{owner}/{repo}/actions/secrets": {
+      "get": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "repository"
+        ],
+        "summary": "List an repo's actions secrets",
+        "operationId": "repoListActionsSecrets",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "owner of the repository",
+            "name": "owner",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of the repository",
+            "name": "repo",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "integer",
+            "description": "page number of results to return (1-based)",
+            "name": "page",
+            "in": "query"
+          },
+          {
+            "type": "integer",
+            "description": "page size of results",
+            "name": "limit",
+            "in": "query"
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/SecretList"
+          },
+          "404": {
+            "$ref": "#/responses/notFound"
+          }
+        }
+      }
+    },
     "/repos/{owner}/{repo}/actions/secrets/{secretname}": {
       "put": {
         "consumes": [
diff --git a/tests/integration/api_repo_secrets_test.go b/tests/integration/api_repo_secrets_test.go
index feb9bae2b2..c3074d9ece 100644
--- a/tests/integration/api_repo_secrets_test.go
+++ b/tests/integration/api_repo_secrets_test.go
@@ -24,6 +24,12 @@ func TestAPIRepoSecrets(t *testing.T) {
 	session := loginUser(t, user.Name)
 	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
 
+	t.Run("List", func(t *testing.T) {
+		req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/secrets", repo.FullName())).
+			AddTokenAuth(token)
+		MakeRequest(t, req, http.StatusOK)
+	})
+
 	t.Run("Create", func(t *testing.T) {
 		cases := []struct {
 			Name           string
@@ -31,7 +37,7 @@ func TestAPIRepoSecrets(t *testing.T) {
 		}{
 			{
 				Name:           "",
-				ExpectedStatus: http.StatusNotFound,
+				ExpectedStatus: http.StatusMethodNotAllowed,
 			},
 			{
 				Name:           "-",

From 3c3cdb2736a4305540fad10672d0b22a5fcea87f Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Sat, 27 Apr 2024 09:21:07 +0200
Subject: [PATCH 059/107] Suppress browserslist warning in webpack target
 (#30571)

1. Set
[`BROWSERSLIST_IGNORE_OLD_DATA`](https://github.com/browserslist/browserslist/blob/c6ddf7b3870a4585822d06ec77e8dd2401b8e1ed/node.js#L400)
to avoid warning on outdated browserslist data which the end user can
likely not do anything about and which is currently visible in the v1.21
branch.
2. Suppress all command echoing and add a "Running webpack..." message
in place.

Warning in question was this:

```
Browserslist: caniuse-lite is outdated. Please run:
  npx update-browserslist-db@latest
  Why you should do it regularly: https://github.com/browserslist/update-db#readme
```

(cherry picked from commit dcc3c17e5c41ad446b71215b095617e066a2e8e1)
---
 Makefile | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/Makefile b/Makefile
index 6e46283ac8..6d4e3e0703 100644
--- a/Makefile
+++ b/Makefile
@@ -913,8 +913,9 @@ webpack: $(WEBPACK_DEST)
 
 $(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
 	@$(MAKE) -s node-check node_modules
-	rm -rf $(WEBPACK_DEST_ENTRIES)
-	npx webpack
+	@rm -rf $(WEBPACK_DEST_ENTRIES)
+	@echo "Running webpack..."
+	@BROWSERSLIST_IGNORE_OLD_DATA=true npx webpack
 	@touch $(WEBPACK_DEST)
 
 .PHONY: svg

From bb7d22d019006ba4ef80d9815910b6a3da8e5a19 Mon Sep 17 00:00:00 2001
From: Chongyi Zheng <git@zcy.dev>
Date: Sat, 27 Apr 2024 06:44:49 -0400
Subject: [PATCH 060/107] Remove unused parameter for some functions in
 `services/mirror` (#30724)

Suggested by gopls `unusedparams`

(cherry picked from commit 4ae6b1a5534e4cc85602e990054c66a08b11852e)
---
 services/mirror/mirror.go      | 6 +++---
 services/mirror/mirror_pull.go | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go
index 72e545581a..0270f87039 100644
--- a/services/mirror/mirror.go
+++ b/services/mirror/mirror.go
@@ -40,7 +40,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
 	}
 	log.Trace("Doing: Update")
 
-	handler := func(idx int, bean any) error {
+	handler := func(bean any) error {
 		var repo *repo_model.Repository
 		var mirrorType SyncType
 		var referenceID int64
@@ -91,7 +91,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
 	pullMirrorsRequested := 0
 	if pullLimit != 0 {
 		if err := repo_model.MirrorsIterate(ctx, pullLimit, func(idx int, bean any) error {
-			if err := handler(idx, bean); err != nil {
+			if err := handler(bean); err != nil {
 				return err
 			}
 			pullMirrorsRequested++
@@ -105,7 +105,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
 	pushMirrorsRequested := 0
 	if pushLimit != 0 {
 		if err := repo_model.PushMirrorsIterate(ctx, pushLimit, func(idx int, bean any) error {
-			if err := handler(idx, bean); err != nil {
+			if err := handler(bean); err != nil {
 				return err
 			}
 			pushMirrorsRequested++
diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go
index f5eaeaf091..9f7ffb29c9 100644
--- a/services/mirror/mirror_pull.go
+++ b/services/mirror/mirror_pull.go
@@ -466,7 +466,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
 
 	log.Trace("SyncMirrors [repo: %-v]: %d branches updated", m.Repo, len(results))
 	if len(results) > 0 {
-		if ok := checkAndUpdateEmptyRepository(ctx, m, gitRepo, results); !ok {
+		if ok := checkAndUpdateEmptyRepository(ctx, m, results); !ok {
 			log.Error("SyncMirrors [repo: %-v]: checkAndUpdateEmptyRepository: %v", m.Repo, err)
 			return false
 		}
@@ -564,7 +564,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
 	return true
 }
 
-func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, gitRepo *git.Repository, results []*mirrorSyncResult) bool {
+func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, results []*mirrorSyncResult) bool {
 	if !m.Repo.IsEmpty {
 		return true
 	}

From 8ee31a6ba3ba89ffb774309d3ef49fe9a90749f8 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Sat, 27 Apr 2024 13:22:55 +0200
Subject: [PATCH 061/107] Improve diff stats bar (#30669)

Minor tweaks:

- Remove unnecessary `item` class which was causing unwanted padding to
be added.
- Add some padding and prevent wrapping so it looks better on mobile.
- Increase width by 4px.

<img width="116" alt="Screenshot 2024-04-24 at 00 15 07"
src="https://github.com/go-gitea/gitea/assets/115237/1f1cf54c-8053-4297-b309-71d9c2ceb9ee">
<img width="441" alt="Screenshot 2024-04-24 at 00 14 57"
src="https://github.com/go-gitea/gitea/assets/115237/2f3a33dc-edad-4b97-b64c-6812aae513cb">

(cherry picked from commit b2abac5e5ff05362e2d200c99cf792e7c3ba1330)
---
 templates/repo/pulls/tab_menu.tmpl | 2 +-
 web_src/css/repo.css               | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/templates/repo/pulls/tab_menu.tmpl b/templates/repo/pulls/tab_menu.tmpl
index a6d058f160..d5a8d6ed21 100644
--- a/templates/repo/pulls/tab_menu.tmpl
+++ b/templates/repo/pulls/tab_menu.tmpl
@@ -16,7 +16,7 @@
 			<span class="ui small label">{{if .NumFiles}}{{.NumFiles}}{{else}}-{{end}}</span>
 		</a>
 		{{if or .Diff.TotalAddition .Diff.TotalDeletion}}
-		<span class="item tw-ml-auto tw-pr-0 tw-font-bold tw-flex tw-items-center tw-gap-2">
+		<span class="tw-ml-auto tw-pl-3 tw-whitespace-nowrap tw-pr-0 tw-font-bold tw-flex tw-items-center tw-gap-2">
 			<span><span class="text green">{{if .Diff.TotalAddition}}+{{.Diff.TotalAddition}}{{end}}</span> <span class="text red">{{if .Diff.TotalDeletion}}-{{.Diff.TotalDeletion}}{{end}}</span></span>
 			<span class="diff-stats-bar">
 				<div class="diff-stats-add-bar" style="width: {{Eval 100 "*" .Diff.TotalAddition "/" "(" .Diff.TotalAddition "+" .Diff.TotalDeletion "+" 0.0 ")"}}%"></div>
diff --git a/web_src/css/repo.css b/web_src/css/repo.css
index 87dbeb5bba..f1d331ff73 100644
--- a/web_src/css/repo.css
+++ b/web_src/css/repo.css
@@ -2693,7 +2693,7 @@ tbody.commit-list {
   display: inline-block;
   background-color: var(--color-red);
   height: 12px;
-  width: 40px;
+  width: 44px;
 }
 
 .diff-stats-bar .diff-stats-add-bar {

From 4ed372af13a10e5a45464ac38f16c634707267c4 Mon Sep 17 00:00:00 2001
From: Kemal Zebari <60799661+kemzeb@users.noreply.github.com>
Date: Sat, 27 Apr 2024 04:55:03 -0700
Subject: [PATCH 062/107] Prevent allow/reject reviews on merged/closed PRs
 (#30686)

Resolves #30675.

(cherry picked from commit dd301cae1c40c9ef2805bd13af6b09a81ff4f5d7)

Conflicts:
	tests/integration/pull_review_test.go
	trivial context conflict in import
---
 routers/api/v1/repo/pull_review.go    | 13 ++++-
 routers/web/repo/pull_review.go       |  2 +
 services/pull/review.go               |  8 +++
 templates/repo/diff/new_review.tmpl   | 28 +++++----
 tests/integration/pull_review_test.go | 82 +++++++++++++++++++++++++++
 5 files changed, 119 insertions(+), 14 deletions(-)

diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go
index 39e1d487fa..6799e43c73 100644
--- a/routers/api/v1/repo/pull_review.go
+++ b/routers/api/v1/repo/pull_review.go
@@ -4,6 +4,7 @@
 package repo
 
 import (
+	"errors"
 	"fmt"
 	"net/http"
 	"strings"
@@ -519,7 +520,11 @@ func CreatePullReview(ctx *context.APIContext) {
 	// create review and associate all pending review comments
 	review, _, err := pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, opts.CommitID, nil)
 	if err != nil {
-		ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
+		if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) {
+			ctx.Error(http.StatusUnprocessableEntity, "", err)
+		} else {
+			ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
+		}
 		return
 	}
 
@@ -607,7 +612,11 @@ func SubmitPullReview(ctx *context.APIContext) {
 	// create review and associate all pending review comments
 	review, _, err = pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, headCommitID, nil)
 	if err != nil {
-		ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
+		if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) {
+			ctx.Error(http.StatusUnprocessableEntity, "", err)
+		} else {
+			ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
+		}
 		return
 	}
 
diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go
index e8a3c48d7f..24763668d0 100644
--- a/routers/web/repo/pull_review.go
+++ b/routers/web/repo/pull_review.go
@@ -248,6 +248,8 @@ func SubmitReview(ctx *context.Context) {
 		if issues_model.IsContentEmptyErr(err) {
 			ctx.Flash.Error(ctx.Tr("repo.issues.review.content.empty"))
 			ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
+		} else if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) {
+			ctx.Status(http.StatusUnprocessableEntity)
 		} else {
 			ctx.ServerError("SubmitReview", err)
 		}
diff --git a/services/pull/review.go b/services/pull/review.go
index 6ad931b679..cff6f346ae 100644
--- a/services/pull/review.go
+++ b/services/pull/review.go
@@ -6,6 +6,7 @@ package pull
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"io"
 	"regexp"
@@ -43,6 +44,9 @@ func (err ErrDismissRequestOnClosedPR) Unwrap() error {
 	return util.ErrPermissionDenied
 }
 
+// ErrSubmitReviewOnClosedPR represents an error when an user tries to submit an approve or reject review associated to a closed or merged PR.
+var ErrSubmitReviewOnClosedPR = errors.New("can't submit review for a closed or merged PR")
+
 // checkInvalidation checks if the line of code comment got changed by another commit.
 // If the line got changed the comment is going to be invalidated.
 func checkInvalidation(ctx context.Context, c *issues_model.Comment, doer *user_model.User, repo *git.Repository, branch string) error {
@@ -293,6 +297,10 @@ func SubmitReview(ctx context.Context, doer *user_model.User, gitRepo *git.Repos
 	if reviewType != issues_model.ReviewTypeApprove && reviewType != issues_model.ReviewTypeReject {
 		stale = false
 	} else {
+		if issue.IsClosed {
+			return nil, nil, ErrSubmitReviewOnClosedPR
+		}
+
 		headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
 		if err != nil {
 			return nil, nil, err
diff --git a/templates/repo/diff/new_review.tmpl b/templates/repo/diff/new_review.tmpl
index a2eae007a5..1b74a230f4 100644
--- a/templates/repo/diff/new_review.tmpl
+++ b/templates/repo/diff/new_review.tmpl
@@ -30,20 +30,24 @@
 				{{end}}
 				<div class="divider"></div>
 				{{$showSelfTooltip := (and $.IsSigned ($.Issue.IsPoster $.SignedUser.ID))}}
-				{{if $showSelfTooltip}}
-					<span class="tw-inline-block" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.review.self_approve"}}">
-						<button type="submit" name="type" value="approve" disabled class="ui submit primary tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.approve"}}</button>
-					</span>
-				{{else}}
-					<button type="submit" name="type" value="approve" class="ui submit primary tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.approve"}}</button>
+				{{if not $.Issue.IsClosed}}
+					{{if $showSelfTooltip}}
+						<span class="tw-inline-block" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.review.self_approve"}}">
+							<button type="submit" name="type" value="approve" disabled class="ui submit primary tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.approve"}}</button>
+						</span>
+					{{else}}
+						<button type="submit" name="type" value="approve" class="ui submit primary tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.approve"}}</button>
+					{{end}}
 				{{end}}
 				<button type="submit" name="type" value="comment" class="ui submit tiny basic button btn-submit">{{ctx.Locale.Tr "repo.diff.review.comment"}}</button>
-				{{if $showSelfTooltip}}
-					<span class="tw-inline-block" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.review.self_reject"}}">
-						<button type="submit" name="type" value="reject" disabled class="ui submit red tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.reject"}}</button>
-					</span>
-				{{else}}
-					<button type="submit" name="type" value="reject" class="ui submit red tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.reject"}}</button>
+				{{if not $.Issue.IsClosed}}
+					{{if $showSelfTooltip}}
+						<span class="tw-inline-block" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.review.self_reject"}}">
+							<button type="submit" name="type" value="reject" disabled class="ui submit red tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.reject"}}</button>
+						</span>
+					{{else}}
+						<button type="submit" name="type" value="reject" class="ui submit red tiny button btn-submit">{{ctx.Locale.Tr "repo.diff.review.reject"}}</button>
+					{{end}}
 				{{end}}
 			</form>
 		</div>
diff --git a/tests/integration/pull_review_test.go b/tests/integration/pull_review_test.go
index 63ec3f9f35..bcdb352612 100644
--- a/tests/integration/pull_review_test.go
+++ b/tests/integration/pull_review_test.go
@@ -6,13 +6,16 @@ package integration
 import (
 	"context"
 	"net/http"
+	"net/http/httptest"
 	"net/url"
+	"path"
 	"strconv"
 	"strings"
 	"testing"
 
 	"code.gitea.io/gitea/models/db"
 	issues_model "code.gitea.io/gitea/models/issues"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
@@ -399,3 +402,82 @@ func TestPullView_CodeOwner(t *testing.T) {
 		})
 	})
 }
+
+func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) {
+	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+		user1Session := loginUser(t, "user1")
+		user2Session := loginUser(t, "user2")
+
+		// Have user1 create a fork of repo1.
+		testRepoFork(t, user1Session, "user2", "repo1", "user1", "repo1")
+
+		t.Run("Submit approve/reject review on merged PR", func(t *testing.T) {
+			// Create a merged PR (made by user1) in the upstream repo1.
+			testEditFile(t, user1Session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
+			resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "master", "This is a pull title")
+			elem := strings.Split(test.RedirectURL(resp), "/")
+			assert.EqualValues(t, "pulls", elem[3])
+			testPullMerge(t, user1Session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false)
+
+			// Grab the CSRF token.
+			req := NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4]))
+			resp = user2Session.MakeRequest(t, req, http.StatusOK)
+			htmlDoc := NewHTMLParser(t, resp.Body)
+
+			// Submit an approve review on the PR.
+			testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "approve", http.StatusUnprocessableEntity)
+
+			// Submit a reject review on the PR.
+			testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "reject", http.StatusUnprocessableEntity)
+		})
+
+		t.Run("Submit approve/reject review on closed PR", func(t *testing.T) {
+			// Created a closed PR (made by user1) in the upstream repo1.
+			testEditFileToNewBranch(t, user1Session, "user1", "repo1", "master", "a-test-branch", "README.md", "Hello, World (Editied...again)\n")
+			resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "a-test-branch", "This is a pull title")
+			elem := strings.Split(test.RedirectURL(resp), "/")
+			assert.EqualValues(t, "pulls", elem[3])
+			testIssueClose(t, user1Session, elem[1], elem[2], elem[4])
+
+			// Grab the CSRF token.
+			req := NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4]))
+			resp = user2Session.MakeRequest(t, req, http.StatusOK)
+			htmlDoc := NewHTMLParser(t, resp.Body)
+
+			// Submit an approve review on the PR.
+			testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "approve", http.StatusUnprocessableEntity)
+
+			// Submit a reject review on the PR.
+			testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "reject", http.StatusUnprocessableEntity)
+		})
+	})
+}
+
+func testSubmitReview(t *testing.T, session *TestSession, csrf, owner, repo, pullNumber, reviewType string, expectedSubmitStatus int) *httptest.ResponseRecorder {
+	options := map[string]string{
+		"_csrf":     csrf,
+		"commit_id": "",
+		"content":   "test",
+		"type":      reviewType,
+	}
+
+	submitURL := path.Join(owner, repo, "pulls", pullNumber, "files", "reviews", "submit")
+	req := NewRequestWithValues(t, "POST", submitURL, options)
+	return session.MakeRequest(t, req, expectedSubmitStatus)
+}
+
+func testIssueClose(t *testing.T, session *TestSession, owner, repo, issueNumber string) *httptest.ResponseRecorder {
+	req := NewRequest(t, "GET", path.Join(owner, repo, "pulls", issueNumber))
+	resp := session.MakeRequest(t, req, http.StatusOK)
+
+	htmlDoc := NewHTMLParser(t, resp.Body)
+	closeURL := path.Join(owner, repo, "issues", issueNumber, "comments")
+
+	options := map[string]string{
+		"_csrf":  htmlDoc.GetCSRF(),
+		"status": "close",
+	}
+
+	req = NewRequestWithValues(t, "POST", closeURL, options)
+	return session.MakeRequest(t, req, http.StatusOK)
+}

From 38ed1cbc9d6253bdeef8ecf09998941019047b36 Mon Sep 17 00:00:00 2001
From: Yarden Shoham <git@yardenshoham.com>
Date: Sat, 27 Apr 2024 16:05:06 +0300
Subject: [PATCH 063/107] Don't show loading indicators when refreshing the
 system status (#30712)

Signed-off-by: Yarden Shoham <git@yardenshoham.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
(cherry picked from commit 51c28d96838a743d2ba4fd679d92e8e15b536a19)
---
 templates/admin/dashboard.tmpl | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl
index bfd2ee6670..9b89b8335f 100644
--- a/templates/admin/dashboard.tmpl
+++ b/templates/admin/dashboard.tmpl
@@ -76,7 +76,8 @@
 			{{ctx.Locale.Tr "admin.dashboard.system_status"}}
 		</h4>
 		{{/* TODO: make these stats work in multi-server deployments, likely needs per-server stats in DB */}}
-		<div hx-get="{{$.Link}}/system_status" hx-swap="morph:innerHTML" hx-trigger="every 5s" hx-indicator=".divider" class="ui attached table segment">
+		<div class="no-loading-indicator tw-hidden"></div>
+		<div hx-get="{{$.Link}}/system_status" hx-swap="morph:innerHTML" hx-trigger="every 5s" hx-indicator=".no-loading-indicator" class="ui attached table segment">
 			{{template "admin/system_status" .}}
 		</div>
 	</div>

From cbcee5d87d8b48fd9c82857851c109a6629f25c7 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Sat, 27 Apr 2024 15:35:26 +0200
Subject: [PATCH 064/107] Issue card improvements (#30687)

Fixes https://github.com/go-gitea/gitea/issues/30682 and does a few
improvements:

- Use gap instead of margin/padding
- Don't render empty image div
- Remove `right floated` class that did nothing

<img width="406" alt="Screenshot 2024-04-24 at 20 21 20"
src="https://github.com/go-gitea/gitea/assets/115237/2fa88707-c2c4-40df-aee7-a684c3097ed0">

---------

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
(cherry picked from commit b93c87b6fe025408777d9f2091d29941e439e58c)
---
 templates/repo/issue/card.tmpl  | 25 +++++++++++++++----------
 web_src/css/repo/issue-card.css |  2 +-
 2 files changed, 16 insertions(+), 11 deletions(-)

diff --git a/templates/repo/issue/card.tmpl b/templates/repo/issue/card.tmpl
index bb9340bb2e..4a0ac050aa 100644
--- a/templates/repo/issue/card.tmpl
+++ b/templates/repo/issue/card.tmpl
@@ -1,13 +1,16 @@
 {{with .Issue}}
 	{{if eq $.Page.Project.CardType 1}}{{/* Images and Text*/}}
+		{{$attachments := index $.Page.issuesAttachmentMap .ID}}
+		{{if $attachments}}
 		<div class="card-attachment-images">
-			{{range (index $.Page.issuesAttachmentMap .ID)}}
+			{{range $attachments}}
 				<img src="{{.DownloadURL}}" alt="{{.Name}}" />
 			{{end}}
 		</div>
+		{{end}}
 	{{end}}
-	<div class="content tw-p-0 tw-w-full">
-		<div class="tw-flex tw-items-start">
+	<div class="content tw-w-full">
+		<div class="tw-flex tw-items-start tw-gap-[5px]">
 			<div class="issue-card-icon">
 				{{template "shared/issueicon" .}}
 			</div>
@@ -18,7 +21,7 @@
 				</a>
 			{{end}}
 		</div>
-		<div class="meta tw-my-1">
+		<div class="meta">
 			<span class="text light grey muted-links">
 				{{if not $.Page.Repository}}{{.Repo.FullName}}{{end}}#{{.Index}}
 				{{$timeStr := TimeSinceUnix .GetLastEventTimestamp ctx.Locale}}
@@ -59,13 +62,15 @@
 	</div>
 
 	{{if or .Labels .Assignees}}
-	<div class="extra content labels-list tw-p-0 tw-pt-1">
-		{{range .Labels}}
-			<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx ctx.Locale .}}</a>
-		{{end}}
-		<div class="right floated">
+	<div class="tw-flex tw-justify-between">
+		<div class="labels-list tw-flex-1">
+			{{range .Labels}}
+				<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx ctx.Locale .}}</a>
+			{{end}}
+		</div>
+		<div class="tw-flex tw-flex-wrap tw-content-start tw-gap-1">
 			{{range .Assignees}}
-				<a target="_blank" href="{{.HomeLink}}" data-tooltip-content="{{ctx.Locale.Tr "repo.projects.column.assigned_to"}} {{.Name}}">{{ctx.AvatarUtils.Avatar . 28 "mini tw-mr-2"}}</a>
+				<a target="_blank" href="{{.HomeLink}}" data-tooltip-content="{{ctx.Locale.Tr "repo.projects.column.assigned_to"}} {{.Name}}">{{ctx.AvatarUtils.Avatar . 28}}</a>
 			{{end}}
 		</div>
 	</div>
diff --git a/web_src/css/repo/issue-card.css b/web_src/css/repo/issue-card.css
index b9368df4f6..609b1b3dbd 100644
--- a/web_src/css/repo/issue-card.css
+++ b/web_src/css/repo/issue-card.css
@@ -1,6 +1,7 @@
 .issue-card {
   display: flex;
   flex-direction: column;
+  gap: 4px;
   align-items: start;
   border-radius: var(--border-radius);
   padding: 8px 10px;
@@ -17,7 +18,6 @@
 .issue-card-title {
   flex: 1;
   font-size: 14px;
-  margin-left: 4px;
 }
 
 .issue-card.sortable-chosen .issue-card-title {

From 2d2c18f0bd6ae3caa85eeedc64ccbcc9f53c42b8 Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Sat, 27 Apr 2024 22:02:07 +0800
Subject: [PATCH 065/107] Rename migration package name for 1.22-rc1 (#30730)

Ref: Propose to restart 1.22 release #30501
(cherry picked from commit 6d2a307ad8af7d686f1c3a3706ff0f2df895658a)

Conflicts:
	models/migrations/migrations.go
	models/migrations/v1_22/v297.go
	trivial conflict because a migration does not exist in Forgejo
---
 models/migrations/migrations.go                 | 13 +++++++------
 models/migrations/{v1_23 => v1_22}/v294.go      |  2 +-
 models/migrations/{v1_23 => v1_22}/v294_test.go |  2 +-
 models/migrations/{v1_23 => v1_22}/v295.go      |  2 +-
 models/migrations/{v1_23 => v1_22}/v296.go      |  2 +-
 models/migrations/{v1_23 => v1_22}/v298.go      |  2 +-
 6 files changed, 12 insertions(+), 11 deletions(-)
 rename models/migrations/{v1_23 => v1_22}/v294.go (98%)
 rename models/migrations/{v1_23 => v1_22}/v294_test.go (98%)
 rename models/migrations/{v1_23 => v1_22}/v295.go (96%)
 rename models/migrations/{v1_23 => v1_22}/v296.go (95%)
 rename models/migrations/{v1_23 => v1_22}/v298.go (90%)

diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 15b12ed70c..5d80f9fd10 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -22,7 +22,6 @@ import (
 	"code.gitea.io/gitea/models/migrations/v1_20"
 	"code.gitea.io/gitea/models/migrations/v1_21"
 	"code.gitea.io/gitea/models/migrations/v1_22"
-	"code.gitea.io/gitea/models/migrations/v1_23"
 	"code.gitea.io/gitea/models/migrations/v1_6"
 	"code.gitea.io/gitea/models/migrations/v1_7"
 	"code.gitea.io/gitea/models/migrations/v1_8"
@@ -576,18 +575,20 @@ var migrations = []Migration{
 	// v293 -> v294
 	NewMigration("Ensure every project has exactly one default column", v1_22.CheckProjectColumnsConsistency),
 
-	// Gitea 1.22.0 ends at 294
+	// Gitea 1.22.0-rc0 ends at 294
 
 	// v294 -> v295
-	NewMigration("Add unique index for project issue table", v1_23.AddUniqueIndexForProjectIssue),
+	NewMigration("Add unique index for project issue table", v1_22.AddUniqueIndexForProjectIssue),
 	// v295 -> v296
-	NewMigration("Add commit status summary table", v1_23.AddCommitStatusSummary),
+	NewMigration("Add commit status summary table", v1_22.AddCommitStatusSummary),
 	// v296 -> v297
-	NewMigration("Add missing field of commit status summary table", v1_23.AddCommitStatusSummary2),
+	NewMigration("Add missing field of commit status summary table", v1_22.AddCommitStatusSummary2),
 	// v297 -> v298
 	NewMigration("Add everyone_access_mode for repo_unit", noopMigration),
 	// v298 -> v299
-	NewMigration("Drop wrongly created table o_auth2_application", v1_23.DropWronglyCreatedTable),
+	NewMigration("Drop wrongly created table o_auth2_application", v1_22.DropWronglyCreatedTable),
+
+	// Gitea 1.22.0-rc1 ends at 299
 }
 
 // GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v1_23/v294.go b/models/migrations/v1_22/v294.go
similarity index 98%
rename from models/migrations/v1_23/v294.go
rename to models/migrations/v1_22/v294.go
index e3e18f68f3..314b4519f1 100644
--- a/models/migrations/v1_23/v294.go
+++ b/models/migrations/v1_22/v294.go
@@ -1,7 +1,7 @@
 // Copyright 2024 The Gitea Authors. All rights reserved.
 // SPDX-License-Identifier: MIT
 
-package v1_23 //nolint
+package v1_22 //nolint
 
 import (
 	"xorm.io/xorm"
diff --git a/models/migrations/v1_23/v294_test.go b/models/migrations/v1_22/v294_test.go
similarity index 98%
rename from models/migrations/v1_23/v294_test.go
rename to models/migrations/v1_22/v294_test.go
index d9a44ad866..82a3bcd602 100644
--- a/models/migrations/v1_23/v294_test.go
+++ b/models/migrations/v1_22/v294_test.go
@@ -1,7 +1,7 @@
 // Copyright 2024 The Gitea Authors. All rights reserved.
 // SPDX-License-Identifier: MIT
 
-package v1_23 //nolint
+package v1_22 //nolint
 
 import (
 	"slices"
diff --git a/models/migrations/v1_23/v295.go b/models/migrations/v1_22/v295.go
similarity index 96%
rename from models/migrations/v1_23/v295.go
rename to models/migrations/v1_22/v295.go
index 9a2003cfc1..17bdadb4ad 100644
--- a/models/migrations/v1_23/v295.go
+++ b/models/migrations/v1_22/v295.go
@@ -1,7 +1,7 @@
 // Copyright 2024 The Gitea Authors. All rights reserved.
 // SPDX-License-Identifier: MIT
 
-package v1_23 //nolint
+package v1_22 //nolint
 
 import "xorm.io/xorm"
 
diff --git a/models/migrations/v1_23/v296.go b/models/migrations/v1_22/v296.go
similarity index 95%
rename from models/migrations/v1_23/v296.go
rename to models/migrations/v1_22/v296.go
index 495ae2ab23..1ecacab95f 100644
--- a/models/migrations/v1_23/v296.go
+++ b/models/migrations/v1_22/v296.go
@@ -1,7 +1,7 @@
 // Copyright 2024 The Gitea Authors. All rights reserved.
 // SPDX-License-Identifier: MIT
 
-package v1_23 //nolint
+package v1_22 //nolint
 
 import "xorm.io/xorm"
 
diff --git a/models/migrations/v1_23/v298.go b/models/migrations/v1_22/v298.go
similarity index 90%
rename from models/migrations/v1_23/v298.go
rename to models/migrations/v1_22/v298.go
index 8761a05d3d..b9f3b95ade 100644
--- a/models/migrations/v1_23/v298.go
+++ b/models/migrations/v1_22/v298.go
@@ -1,7 +1,7 @@
 // Copyright 2024 The Gitea Authors. All rights reserved.
 // SPDX-License-Identifier: MIT
 
-package v1_23 //nolint
+package v1_22 //nolint
 
 import "xorm.io/xorm"
 

From 781789e779f6ab8a85db4d5a252f706485ef7824 Mon Sep 17 00:00:00 2001
From: Chongyi Zheng <git@zcy.dev>
Date: Sat, 27 Apr 2024 12:50:35 -0400
Subject: [PATCH 066/107] Replace deprecated `math/rand` functions (#30733)

Suggested by logs in #30729

- Remove `math/rand.Seed`
`rand.Seed is deprecated: As of Go 1.20 there is no reason to call Seed
with a random value.`
- Replace `math/rand.Read`
`rand.Read is deprecated: For almost all use cases, [crypto/rand.Read]
is more appropriate.`
- Replace `math/rand` with `math/rand/v2`, which is available since Go
1.22

(cherry picked from commit 7b8e418da1e082786b844562a05864ec1177ce97)
---
 models/user/user_test.go              |  2 +-
 modules/auth/password/pwn/pwn_test.go | 16 +++++-----------
 tests/integration/benchmarks_test.go  |  6 +++---
 tests/integration/git_test.go         |  2 +-
 4 files changed, 10 insertions(+), 16 deletions(-)

diff --git a/models/user/user_test.go b/models/user/user_test.go
index 571b8b2a93..4bf8c71369 100644
--- a/models/user/user_test.go
+++ b/models/user/user_test.go
@@ -5,8 +5,8 @@ package user_test
 
 import (
 	"context"
+	"crypto/rand"
 	"fmt"
-	"math/rand"
 	"strings"
 	"testing"
 	"time"
diff --git a/modules/auth/password/pwn/pwn_test.go b/modules/auth/password/pwn/pwn_test.go
index f9deadc8d7..a2a6b3a174 100644
--- a/modules/auth/password/pwn/pwn_test.go
+++ b/modules/auth/password/pwn/pwn_test.go
@@ -4,9 +4,8 @@
 package pwn
 
 import (
-	"math/rand"
+	"math/rand/v2"
 	"net/http"
-	"os"
 	"strings"
 	"testing"
 	"time"
@@ -18,11 +17,6 @@ var client = New(WithHTTP(&http.Client{
 	Timeout: time.Second * 2,
 }))
 
-func TestMain(m *testing.M) {
-	rand.Seed(time.Now().Unix())
-	os.Exit(m.Run())
-}
-
 func TestPassword(t *testing.T) {
 	// Check input error
 	_, err := client.CheckPassword("", false)
@@ -81,24 +75,24 @@ func testPassword() string {
 
 	// Set special character
 	for i := 0; i < 5; i++ {
-		random := rand.Intn(len(specialCharSet))
+		random := rand.IntN(len(specialCharSet))
 		password.WriteString(string(specialCharSet[random]))
 	}
 
 	// Set numeric
 	for i := 0; i < 5; i++ {
-		random := rand.Intn(len(numberSet))
+		random := rand.IntN(len(numberSet))
 		password.WriteString(string(numberSet[random]))
 	}
 
 	// Set uppercase
 	for i := 0; i < 5; i++ {
-		random := rand.Intn(len(upperCharSet))
+		random := rand.IntN(len(upperCharSet))
 		password.WriteString(string(upperCharSet[random]))
 	}
 
 	for i := 0; i < 5; i++ {
-		random := rand.Intn(len(allCharSet))
+		random := rand.IntN(len(allCharSet))
 		password.WriteString(string(allCharSet[random]))
 	}
 	inRune := []rune(password.String())
diff --git a/tests/integration/benchmarks_test.go b/tests/integration/benchmarks_test.go
index 7a882fe836..62da761d2d 100644
--- a/tests/integration/benchmarks_test.go
+++ b/tests/integration/benchmarks_test.go
@@ -4,7 +4,7 @@
 package integration
 
 import (
-	"math/rand"
+	"math/rand/v2"
 	"net/http"
 	"net/url"
 	"testing"
@@ -18,7 +18,7 @@ import (
 func StringWithCharset(length int, charset string) string {
 	b := make([]byte, length)
 	for i := range b {
-		b[i] = charset[rand.Intn(len(charset))]
+		b[i] = charset[rand.IntN(len(charset))]
 	}
 	return string(b)
 }
@@ -37,7 +37,7 @@ func BenchmarkRepoBranchCommit(b *testing.B) {
 				b.ResetTimer()
 				b.Run("CreateBranch", func(b *testing.B) {
 					b.StopTimer()
-					branchName := StringWithCharset(5+rand.Intn(10), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
+					branchName := StringWithCharset(5+rand.IntN(10), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
 					b.StartTimer()
 					for i := 0; i < b.N; i++ {
 						b.Run("new_"+branchName, func(b *testing.B) {
diff --git a/tests/integration/git_test.go b/tests/integration/git_test.go
index f833366568..6ee3be2df2 100644
--- a/tests/integration/git_test.go
+++ b/tests/integration/git_test.go
@@ -5,9 +5,9 @@ package integration
 
 import (
 	"bytes"
+	"crypto/rand"
 	"encoding/hex"
 	"fmt"
-	"math/rand"
 	"net/http"
 	"net/url"
 	"os"

From 7517e7074020bcc7674e90bbf5ecb5a775567441 Mon Sep 17 00:00:00 2001
From: Chongyi Zheng <git@zcy.dev>
Date: Sat, 27 Apr 2024 19:21:33 -0400
Subject: [PATCH 067/107] Use `ProtonMail/go-crypto` for `opengpg` in tests
 (#30736)

(cherry picked from commit 8b8b48ef5fb1c5c164d5534ea4b8049f1db26ce9)

Conflicts:
	go.mod
	trivial context confllict
---
 go.mod                            | 2 +-
 tests/integration/gpg_git_test.go | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index f32e871921..450e395c9c 100644
--- a/go.mod
+++ b/go.mod
@@ -15,6 +15,7 @@ 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/ProtonMail/go-crypto v1.0.0
 	github.com/PuerkitoBio/goquery v1.8.1
 	github.com/alecthomas/chroma/v2 v2.13.0
 	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
@@ -129,7 +130,6 @@ require (
 	github.com/Masterminds/semver/v3 v3.2.1 // indirect
 	github.com/Masterminds/sprig/v3 v3.2.3 // indirect
 	github.com/Microsoft/go-winio v0.6.1 // indirect
-	github.com/ProtonMail/go-crypto v1.0.0 // indirect
 	github.com/RoaringBitmap/roaring v1.7.0 // indirect
 	github.com/andybalholm/brotli v1.1.0 // indirect
 	github.com/andybalholm/cascadia v1.3.2 // indirect
diff --git a/tests/integration/gpg_git_test.go b/tests/integration/gpg_git_test.go
index 00890cfb38..3ba4a5882c 100644
--- a/tests/integration/gpg_git_test.go
+++ b/tests/integration/gpg_git_test.go
@@ -19,9 +19,9 @@ import (
 	"code.gitea.io/gitea/modules/test"
 	"code.gitea.io/gitea/tests"
 
+	"github.com/ProtonMail/go-crypto/openpgp"
+	"github.com/ProtonMail/go-crypto/openpgp/armor"
 	"github.com/stretchr/testify/assert"
-	"golang.org/x/crypto/openpgp"
-	"golang.org/x/crypto/openpgp/armor"
 )
 
 func TestGPGGit(t *testing.T) {

From ec6d46bc8fe626c154fc46dd06d5fd0bf68248a7 Mon Sep 17 00:00:00 2001
From: Chongyi Zheng <git@zcy.dev>
Date: Sun, 28 Apr 2024 00:13:57 -0400
Subject: [PATCH 068/107] Fix nil dereference on error (#30740)

In both cases, the `err` is nil because of `if` checks before

Reference: #30729
(cherry picked from commit 970965f6d8fb4e68613ca445d2414c6c796b5231)
---
 routers/api/actions/artifacts.go | 7 ++++---
 routers/web/repo/actions/view.go | 2 +-
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/routers/api/actions/artifacts.go b/routers/api/actions/artifacts.go
index 72ff704687..416d7e5181 100644
--- a/routers/api/actions/artifacts.go
+++ b/routers/api/actions/artifacts.go
@@ -466,14 +466,15 @@ func (ar artifactRoutes) downloadArtifact(ctx *ArtifactContext) {
 		log.Error("Error getting artifact: %v", err)
 		ctx.Error(http.StatusInternalServerError, err.Error())
 		return
-	} else if !exist {
+	}
+	if !exist {
 		log.Error("artifact with ID %d does not exist", artifactID)
 		ctx.Error(http.StatusNotFound, fmt.Sprintf("artifact with ID %d does not exist", artifactID))
 		return
 	}
 	if artifact.RunID != runID {
-		log.Error("Error dismatch runID and artifactID, task: %v, artifact: %v", runID, artifactID)
-		ctx.Error(http.StatusBadRequest, err.Error())
+		log.Error("Error mismatch runID and artifactID, task: %v, artifact: %v", runID, artifactID)
+		ctx.Error(http.StatusBadRequest)
 		return
 	}
 
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index 7d70d9f2ac..dc21f1a4ed 100644
--- a/routers/web/repo/actions/view.go
+++ b/routers/web/repo/actions/view.go
@@ -552,7 +552,7 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions
 		return nil, nil
 	}
 	if len(jobs) == 0 {
-		ctx.Error(http.StatusNotFound, err.Error())
+		ctx.Error(http.StatusNotFound)
 		return nil, nil
 	}
 

From c54896ba709530daf99e63926ad20ae40d240692 Mon Sep 17 00:00:00 2001
From: Beowulf <beowulf@beocode.eu>
Date: Sun, 28 Apr 2024 13:47:52 +0000
Subject: [PATCH 069/107] Show repo activities even if only code unit active or
 git repo is empty but issue is active (#3455)

When all repository units are deactivated except for the code unit, the activity tab will not be shown.
Since the activities tab also shows contributing stats, it would be good to show the activities tab also when only code is active.
This commit changes the behavior when the activities tab is shown.
Previous it would only be shown when Issues, Pull-Requests or Releases are activated. Now it would additionally be shown when the code unit is activated.

Refs: #3429

| Before (Code + Issues - Owner) | Before (Code - Viewer) | After (Code + Issues - Owner) | After (Code - Viewer) |
| -- | -- | -- | -- |
| ![image](/attachments/2af997bc-1f38-48c6-bdf3-cfbd7087b220)  | ![image](/attachments/ef1797f0-5c9a-4a1a-ba82-749f3ab4f403) | ![image](/attachments/fd28a96c-04ca-407e-a70d-d28b393f223d) | ![image](/attachments/2cd0d559-a6de-4ca0-a736-29c5fea81b5a) |
|  | `/activity` returns 404 for everyone | ![image](/attachments/e0e97d8f-48cb-4c16-a505-1fafa46c4b8e)  | - |

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3455
Reviewed-by: Otto <otto@codeberg.org>
Co-authored-by: Beowulf <beowulf@beocode.eu>
Co-committed-by: Beowulf <beowulf@beocode.eu>
---
 routers/web/repo/activity.go            |   2 +-
 routers/web/web.go                      |   8 +-
 templates/repo/activity.tmpl            |   8 +-
 templates/repo/header.tmpl              |   2 +-
 tests/integration/repo_activity_test.go | 126 ++++++++++++++++++++++++
 5 files changed, 137 insertions(+), 9 deletions(-)

diff --git a/routers/web/repo/activity.go b/routers/web/repo/activity.go
index 6f6641cc65..ba776c84d3 100644
--- a/routers/web/repo/activity.go
+++ b/routers/web/repo/activity.go
@@ -57,7 +57,7 @@ func Activity(ctx *context.Context) {
 		ctx.Repo.CanRead(unit.TypeReleases),
 		ctx.Repo.CanRead(unit.TypeIssues),
 		ctx.Repo.CanRead(unit.TypePullRequests),
-		ctx.Repo.CanRead(unit.TypeCode)); err != nil {
+		ctx.Repo.CanRead(unit.TypeCode) && !ctx.Repo.Repository.IsEmpty); err != nil {
 		ctx.ServerError("GetActivityStats", err)
 		return
 	}
diff --git a/routers/web/web.go b/routers/web/web.go
index 154e1ce24b..8faedca178 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -1423,16 +1423,16 @@ func registerRoutes(m *web.Route) {
 			m.Group("/contributors", func() {
 				m.Get("", repo.Contributors)
 				m.Get("/data", repo.ContributorsData)
-			})
+			}, repo.MustBeNotEmpty, context.RequireRepoReaderOr(unit.TypeCode))
 			m.Group("/code-frequency", func() {
 				m.Get("", repo.CodeFrequency)
 				m.Get("/data", repo.CodeFrequencyData)
-			})
+			}, repo.MustBeNotEmpty, context.RequireRepoReaderOr(unit.TypeCode))
 			m.Group("/recent-commits", func() {
 				m.Get("", repo.RecentCommits)
 				m.Get("/data", repo.RecentCommitsData)
-			})
-		}, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases))
+			}, repo.MustBeNotEmpty, context.RequireRepoReaderOr(unit.TypeCode))
+		}, context.RepoRef(), context.RequireRepoReaderOr(unit.TypeCode, unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases))
 
 		m.Group("/activity_author_data", func() {
 			m.Get("", repo.ActivityAuthors)
diff --git a/templates/repo/activity.tmpl b/templates/repo/activity.tmpl
index a19fb66261..19a09b99b0 100644
--- a/templates/repo/activity.tmpl
+++ b/templates/repo/activity.tmpl
@@ -2,9 +2,11 @@
 <div role="main" aria-label="{{.Title}}" class="page-content repository commits">
 	{{template "repo/header" .}}
 	<div class="ui container flex-container">
-		<div class="flex-container-nav">
-			{{template "repo/navbar" .}}
-		</div>
+		{{if and (not .IsEmptyRepo) (.Permission.CanRead $.UnitTypeCode)}}
+			<div class="flex-container-nav">
+				{{template "repo/navbar" .}}
+			</div>
+		{{end}}
 		<div class="flex-container-main">
 			{{if .PageIsPulse}}{{template "repo/pulse" .}}{{end}}
 			{{if .PageIsContributors}}{{template "repo/contributors" .}}{{end}}
diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl
index 95c2e059d5..cf8ee7d772 100644
--- a/templates/repo/header.tmpl
+++ b/templates/repo/header.tmpl
@@ -159,7 +159,7 @@
 					</a>
 				{{end}}
 
-				{{if and (.Permission.CanReadAny $.UnitTypePullRequests $.UnitTypeIssues $.UnitTypeReleases) (not .IsEmptyRepo)}}
+				{{if and (.Permission.CanReadAny $.UnitTypeCode $.UnitTypePullRequests $.UnitTypeIssues $.UnitTypeReleases)}}
 					<a class="{{if .PageIsActivity}}active {{end}}item" href="{{.RepoLink}}/activity">
 						{{svg "octicon-pulse"}} {{ctx.Locale.Tr "repo.activity"}}
 					</a>
diff --git a/tests/integration/repo_activity_test.go b/tests/integration/repo_activity_test.go
index 792554db4b..0b1e9939a1 100644
--- a/tests/integration/repo_activity_test.go
+++ b/tests/integration/repo_activity_test.go
@@ -4,13 +4,20 @@
 package integration
 
 import (
+	"fmt"
 	"net/http"
 	"net/url"
 	"strings"
 	"testing"
 
+	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
+	unit_model "code.gitea.io/gitea/models/unit"
+	"code.gitea.io/gitea/models/unittest"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/test"
+	repo_service "code.gitea.io/gitea/services/repository"
+	"code.gitea.io/gitea/tests"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -63,3 +70,122 @@ func TestRepoActivity(t *testing.T) {
 		assert.Len(t, list.Nodes, 3)
 	})
 }
+
+func TestRepoActivityAllUnitsDisabled(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"})
+	session := loginUser(t, user.Name)
+
+	unit_model.LoadUnitConfig()
+
+	// Create a repo, with no unit enabled.
+	repo, err := repo_service.CreateRepository(db.DefaultContext, user, user, repo_service.CreateRepoOptions{
+		Name:     "empty-repo",
+		AutoInit: false,
+	})
+	assert.NoError(t, err)
+	assert.NotEmpty(t, repo)
+
+	enabledUnits := make([]repo_model.RepoUnit, 0)
+	disabledUnits := []unit_model.Type{unit_model.TypeCode, unit_model.TypeIssues, unit_model.TypePullRequests, unit_model.TypeReleases}
+	err = repo_service.UpdateRepositoryUnits(db.DefaultContext, repo, enabledUnits, disabledUnits)
+	assert.NoError(t, err)
+
+	req := NewRequest(t, "GET", fmt.Sprintf("%s/activity", repo.Link()))
+	session.MakeRequest(t, req, http.StatusNotFound)
+	req = NewRequest(t, "GET", fmt.Sprintf("%s/activity/contributors", repo.Link()))
+	session.MakeRequest(t, req, http.StatusNotFound)
+	req = NewRequest(t, "GET", fmt.Sprintf("%s/activity/code-frequency", repo.Link()))
+	session.MakeRequest(t, req, http.StatusNotFound)
+	req = NewRequest(t, "GET", fmt.Sprintf("%s/activity/recent-commits", repo.Link()))
+	session.MakeRequest(t, req, http.StatusNotFound)
+}
+
+func TestRepoActivityOnlyCodeUnitWithEmptyRepo(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"})
+	session := loginUser(t, user.Name)
+
+	unit_model.LoadUnitConfig()
+
+	// Create a empty repo, with only code unit enabled.
+	repo, err := repo_service.CreateRepository(db.DefaultContext, user, user, repo_service.CreateRepoOptions{
+		Name:     "empty-repo",
+		AutoInit: false,
+	})
+	assert.NoError(t, err)
+	assert.NotEmpty(t, repo)
+
+	enabledUnits := make([]repo_model.RepoUnit, 1)
+	enabledUnits[0] = repo_model.RepoUnit{RepoID: repo.ID, Type: unit_model.TypeCode}
+	disabledUnits := []unit_model.Type{unit_model.TypeIssues, unit_model.TypePullRequests, unit_model.TypeReleases}
+	err = repo_service.UpdateRepositoryUnits(db.DefaultContext, repo, enabledUnits, disabledUnits)
+	assert.NoError(t, err)
+
+	req := NewRequest(t, "GET", fmt.Sprintf("%s/activity", repo.Link()))
+	session.MakeRequest(t, req, http.StatusOK)
+
+	// Git repo empty so no activity for contributors etc
+	req = NewRequest(t, "GET", fmt.Sprintf("%s/activity/contributors", repo.Link()))
+	session.MakeRequest(t, req, http.StatusNotFound)
+	req = NewRequest(t, "GET", fmt.Sprintf("%s/activity/code-frequency", repo.Link()))
+	session.MakeRequest(t, req, http.StatusNotFound)
+	req = NewRequest(t, "GET", fmt.Sprintf("%s/activity/recent-commits", repo.Link()))
+	session.MakeRequest(t, req, http.StatusNotFound)
+}
+
+func TestRepoActivityOnlyCodeUnitWithNonEmptyRepo(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"})
+	session := loginUser(t, user.Name)
+
+	unit_model.LoadUnitConfig()
+
+	// Create a repo, with only code unit enabled.
+	repo, _, f := CreateDeclarativeRepo(t, user, "", []unit_model.Type{unit_model.TypeCode}, nil, nil)
+	defer f()
+
+	req := NewRequest(t, "GET", fmt.Sprintf("%s/activity", repo.Link()))
+	session.MakeRequest(t, req, http.StatusOK)
+
+	// Git repo not empty so activity for contributors etc
+	req = NewRequest(t, "GET", fmt.Sprintf("%s/activity/contributors", repo.Link()))
+	session.MakeRequest(t, req, http.StatusOK)
+	req = NewRequest(t, "GET", fmt.Sprintf("%s/activity/code-frequency", repo.Link()))
+	session.MakeRequest(t, req, http.StatusOK)
+	req = NewRequest(t, "GET", fmt.Sprintf("%s/activity/recent-commits", repo.Link()))
+	session.MakeRequest(t, req, http.StatusOK)
+}
+
+func TestRepoActivityOnlyIssuesUnit(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"})
+	session := loginUser(t, user.Name)
+
+	unit_model.LoadUnitConfig()
+
+	// Create a empty repo, with only code unit enabled.
+	repo, err := repo_service.CreateRepository(db.DefaultContext, user, user, repo_service.CreateRepoOptions{
+		Name:     "empty-repo",
+		AutoInit: false,
+	})
+	assert.NoError(t, err)
+	assert.NotEmpty(t, repo)
+
+	enabledUnits := make([]repo_model.RepoUnit, 1)
+	enabledUnits[0] = repo_model.RepoUnit{RepoID: repo.ID, Type: unit_model.TypeIssues}
+	disabledUnits := []unit_model.Type{unit_model.TypeCode, unit_model.TypePullRequests, unit_model.TypeReleases}
+	err = repo_service.UpdateRepositoryUnits(db.DefaultContext, repo, enabledUnits, disabledUnits)
+	assert.NoError(t, err)
+
+	req := NewRequest(t, "GET", fmt.Sprintf("%s/activity", repo.Link()))
+	session.MakeRequest(t, req, http.StatusOK)
+
+	// Git repo empty so no activity for contributors etc
+	req = NewRequest(t, "GET", fmt.Sprintf("%s/activity/contributors", repo.Link()))
+	session.MakeRequest(t, req, http.StatusNotFound)
+	req = NewRequest(t, "GET", fmt.Sprintf("%s/activity/code-frequency", repo.Link()))
+	session.MakeRequest(t, req, http.StatusNotFound)
+	req = NewRequest(t, "GET", fmt.Sprintf("%s/activity/recent-commits", repo.Link()))
+	session.MakeRequest(t, req, http.StatusNotFound)
+}

From 295b2fff5a8f2f95d93e90e1124858672a480942 Mon Sep 17 00:00:00 2001
From: Earl Warren <contact@earl-warren.org>
Date: Sun, 28 Apr 2024 16:01:28 +0200
Subject: [PATCH 070/107] feat(renovate): github.com/urfave/cli/v2 is good at
 managing patches

let them be merged automatically
---
 renovate.json | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/renovate.json b/renovate.json
index c395194167..95efafe763 100644
--- a/renovate.json
+++ b/renovate.json
@@ -72,6 +72,7 @@
     {
       "description": "Split minor and patch updates",
       "matchDepNames": [
+        "github.com/urfave/cli/v2",
         "swagger-ui-dist"
       ],
       "separateMinorPatch": true
@@ -79,6 +80,7 @@
     {
       "description": "Automerge patch updates",
       "matchDepNames": [
+        "github.com/urfave/cli/v2",
         "swagger-ui-dist"
       ],
       "matchUpdateTypes": ["patch"],

From d59ebc3e325d8f557d775946df9059da8c0c71d7 Mon Sep 17 00:00:00 2001
From: JakobDev <jakobdev@gmx.de>
Date: Sun, 28 Apr 2024 22:44:59 +0200
Subject: [PATCH 071/107] Add release note for #3139

---
 release-notes/8.0.0/3139.md | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 release-notes/8.0.0/3139.md

diff --git a/release-notes/8.0.0/3139.md b/release-notes/8.0.0/3139.md
new file mode 100644
index 0000000000..cac054bee8
--- /dev/null
+++ b/release-notes/8.0.0/3139.md
@@ -0,0 +1 @@
+Allow hiding auto generated release archives

From fb5e36bc6f0e51297a18bc27f1d25047743e92ce Mon Sep 17 00:00:00 2001
From: Beowulf <beowulf@beocode.eu>
Date: Sun, 28 Apr 2024 22:40:51 +0200
Subject: [PATCH 072/107] Fixes that the settings button moves in the overflow
 menu and the add more button is on the right

This fix moves the settings button back to the right and the add more
button back to the left.
---
 templates/repo/header.tmpl                  |  2 +-
 tests/e2e/right-settings-button.test.e2e.js | 14 ++++++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl
index cf8ee7d772..bce0e79bca 100644
--- a/templates/repo/header.tmpl
+++ b/templates/repo/header.tmpl
@@ -177,7 +177,7 @@
 					{{$highlightSettings := true}}
 					{{if and .SignedUser.EnableRepoUnitHints (not (.Repository.AllUnitsEnabled ctx))}}
 						{{$highlightSettings = false}}
-						<a id="settings-btn" class="{{if .PageIsRepoSettingsUnits}}active {{end}}right item" href="{{.RepoLink}}/settings/units">
+						<a class="{{if .PageIsRepoSettingsUnits}}active {{end}}item" href="{{.RepoLink}}/settings/units">
 							{{svg "octicon-diff-added"}} {{ctx.Locale.Tr "repo.settings.units.add_more"}}
 						</a>
 					{{end}}
diff --git a/tests/e2e/right-settings-button.test.e2e.js b/tests/e2e/right-settings-button.test.e2e.js
index 7f457dec4a..698aa03192 100644
--- a/tests/e2e/right-settings-button.test.e2e.js
+++ b/tests/e2e/right-settings-button.test.e2e.js
@@ -22,6 +22,20 @@ test.describe('desktop viewport', () => {
     await expect(page.locator('.overflow-menu-button')).toHaveCount(0);
   });
 
+  test('Settings button on right of repo header also when add more button is shown', async ({browser}, workerInfo) => {
+    await login_user(browser, workerInfo, 'user12');
+    const context = await load_logged_in_context(browser, workerInfo, 'user12');
+    const page = await context.newPage();
+
+    await page.goto('/user12/repo10');
+
+    const settingsBtn = page.locator('.overflow-menu-items>#settings-btn');
+    await expect(settingsBtn).toBeVisible();
+    await expect(settingsBtn).toHaveClass(/right/);
+
+    await expect(page.locator('.overflow-menu-button')).toHaveCount(0);
+  });
+
   test('Settings button on right of org header', async ({browser}, workerInfo) => {
     const context = await load_logged_in_context(browser, workerInfo, 'user2');
     const page = await context.newPage();

From e1d93950ad050b9a84b2612894e4fcc658bbd5bd Mon Sep 17 00:00:00 2001
From: oliverpool <git@olivier.pfad.fr>
Date: Thu, 18 Apr 2024 09:55:08 +0200
Subject: [PATCH 073/107] feat: implement PKCE when acting as oauth2 client
 (for user login)

Closes #2766
---
 release-notes/8.0.0/feat/3307.md              |  1 +
 routers/web/auth/oauth.go                     | 36 +++++++++++--
 routers/web/auth/oauth_test.go                |  7 +++
 services/auth/source/oauth2/source_callout.go | 27 ++++++++--
 tests/integration/oauth_test.go               | 54 +++++++++++++++++++
 5 files changed, 119 insertions(+), 6 deletions(-)
 create mode 100644 release-notes/8.0.0/feat/3307.md

diff --git a/release-notes/8.0.0/feat/3307.md b/release-notes/8.0.0/feat/3307.md
new file mode 100644
index 0000000000..6d7dd01415
--- /dev/null
+++ b/release-notes/8.0.0/feat/3307.md
@@ -0,0 +1 @@
+Support [Proof Key for Code Exchange (PKCE - RFC7636)](https://www.rfc-editor.org/rfc/rfc7636) for external login sources using the OAuth2 flow.
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index b48345684b..3ad7a4738e 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -5,6 +5,7 @@ package auth
 
 import (
 	go_context "context"
+	"crypto/sha256"
 	"encoding/base64"
 	"errors"
 	"fmt"
@@ -860,13 +861,19 @@ func SignInOAuth(ctx *context.Context) {
 		return
 	}
 
-	if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
+	codeChallenge, err := generateCodeChallenge(ctx)
+	if err != nil {
+		ctx.ServerError("SignIn", fmt.Errorf("could not generate code_challenge: %w", err))
+		return
+	}
+
+	if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp, codeChallenge); err != nil {
 		if strings.Contains(err.Error(), "no provider for ") {
 			if err = oauth2.ResetOAuth2(ctx); err != nil {
 				ctx.ServerError("SignIn", err)
 				return
 			}
-			if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
+			if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp, codeChallenge); err != nil {
 				ctx.ServerError("SignIn", err)
 			}
 			return
@@ -1203,6 +1210,27 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
 	ctx.Redirect(setting.AppSubURL + "/user/two_factor")
 }
 
+// generateCodeChallenge stores a code verifier in the session and returns a S256 code challenge for PKCE
+func generateCodeChallenge(ctx *context.Context) (codeChallenge string, err error) {
+	codeVerifier, err := util.CryptoRandomString(43) // 256/log2(62) = 256 bits of entropy (each char having log2(62) of randomness)
+	if err != nil {
+		return "", err
+	}
+	if err = ctx.Session.Set("CodeVerifier", codeVerifier); err != nil {
+		return "", err
+	}
+	return encodeCodeChallenge(codeVerifier)
+}
+
+func encodeCodeChallenge(codeVerifier string) (string, error) {
+	hasher := sha256.New()
+	_, err := io.WriteString(hasher, codeVerifier)
+	codeChallenge := base64.RawURLEncoding.EncodeToString(hasher.Sum(nil))
+	return codeChallenge, err
+}
+
+// OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful
+// login the user
 func oAuth2UserLoginCallback(ctx *context.Context, authSource *auth.Source, request *http.Request, response http.ResponseWriter) (*user_model.User, goth.User, error) {
 	gothUser, err := oAuth2FetchUser(ctx, authSource, request, response)
 	if err != nil {
@@ -1239,7 +1267,9 @@ func oAuth2FetchUser(ctx *context.Context, authSource *auth.Source, request *htt
 	}
 
 	// Proceed to authenticate through goth.
-	gothUser, err := oauth2Source.Callback(request, response)
+	codeVerifier, _ := ctx.Session.Get("CodeVerifier").(string)
+	_ = ctx.Session.Delete("CodeVerifier")
+	gothUser, err := oauth2Source.Callback(request, response, codeVerifier)
 	if err != nil {
 		if err.Error() == "securecookie: the value is too long" || strings.Contains(err.Error(), "Data too long") {
 			log.Error("OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider", authSource.Name, setting.OAuth2.MaxTokenLength)
diff --git a/routers/web/auth/oauth_test.go b/routers/web/auth/oauth_test.go
index 4339d9d1eb..3726daee93 100644
--- a/routers/web/auth/oauth_test.go
+++ b/routers/web/auth/oauth_test.go
@@ -93,3 +93,10 @@ func TestNewAccessTokenResponse_OIDCToken(t *testing.T) {
 	assert.Equal(t, user.Email, oidcToken.Email)
 	assert.Equal(t, user.IsActive, oidcToken.EmailVerified)
 }
+
+func TestEncodeCodeChallenge(t *testing.T) {
+	// test vector from https://datatracker.ietf.org/doc/html/rfc7636#page-18
+	codeChallenge, err := encodeCodeChallenge("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk")
+	assert.NoError(t, err)
+	assert.Equal(t, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", codeChallenge)
+}
diff --git a/services/auth/source/oauth2/source_callout.go b/services/auth/source/oauth2/source_callout.go
index 8d70bee248..f95a80fc19 100644
--- a/services/auth/source/oauth2/source_callout.go
+++ b/services/auth/source/oauth2/source_callout.go
@@ -5,16 +5,25 @@ package oauth2
 
 import (
 	"net/http"
+	"net/url"
 
 	"github.com/markbates/goth"
 	"github.com/markbates/goth/gothic"
 )
 
 // Callout redirects request/response pair to authenticate against the provider
-func (source *Source) Callout(request *http.Request, response http.ResponseWriter) error {
+func (source *Source) Callout(request *http.Request, response http.ResponseWriter, codeChallengeS256 string) error {
 	// not sure if goth is thread safe (?) when using multiple providers
 	request.Header.Set(ProviderHeaderKey, source.authSource.Name)
 
+	var querySuffix string
+	if codeChallengeS256 != "" {
+		querySuffix = "&" + url.Values{
+			"code_challenge_method": []string{"S256"},
+			"code_challenge":        []string{codeChallengeS256},
+		}.Encode()
+	}
+
 	// don't use the default gothic begin handler to prevent issues when some error occurs
 	// normally the gothic library will write some custom stuff to the response instead of our own nice error page
 	// gothic.BeginAuthHandler(response, request)
@@ -24,17 +33,29 @@ func (source *Source) Callout(request *http.Request, response http.ResponseWrite
 
 	url, err := gothic.GetAuthURL(response, request)
 	if err == nil {
-		http.Redirect(response, request, url, http.StatusTemporaryRedirect)
+		// hacky way to set the code_challenge, but no better way until
+		// https://github.com/markbates/goth/issues/516 is resolved
+		http.Redirect(response, request, url+querySuffix, http.StatusTemporaryRedirect)
 	}
 	return err
 }
 
 // Callback handles OAuth callback, resolve to a goth user and send back to original url
 // this will trigger a new authentication request, but because we save it in the session we can use that
-func (source *Source) Callback(request *http.Request, response http.ResponseWriter) (goth.User, error) {
+func (source *Source) Callback(request *http.Request, response http.ResponseWriter, codeVerifier string) (goth.User, error) {
 	// not sure if goth is thread safe (?) when using multiple providers
 	request.Header.Set(ProviderHeaderKey, source.authSource.Name)
 
+	if codeVerifier != "" {
+		// hacky way to set the code_verifier...
+		// Will be picked up inside CompleteUserAuth: params := req.URL.Query()
+		// https://github.com/markbates/goth/pull/474/files
+		request = request.Clone(request.Context())
+		q := request.URL.Query()
+		q.Add("code_verifier", codeVerifier)
+		request.URL.RawQuery = q.Encode()
+	}
+
 	gothRWMutex.RLock()
 	defer gothRWMutex.RUnlock()
 
diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go
index 1da1c6f9c0..46beddb5f3 100644
--- a/tests/integration/oauth_test.go
+++ b/tests/integration/oauth_test.go
@@ -6,9 +6,12 @@ package integration
 import (
 	"bytes"
 	"context"
+	"crypto/sha256"
+	"encoding/base64"
 	"fmt"
 	"io"
 	"net/http"
+	"net/url"
 	"testing"
 
 	auth_model "code.gitea.io/gitea/models/auth"
@@ -470,6 +473,57 @@ func TestSignInOAuthCallbackSignIn(t *testing.T) {
 	assert.Greater(t, userAfterLogin.LastLoginUnix, userGitLab.LastLoginUnix)
 }
 
+func TestSignInOAuthCallbackPKCE(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	// Setup authentication source
+	gitlabName := "gitlab"
+	gitlab := addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName))
+	// Create a user as if it had been previously been created by the authentication source.
+	userGitLabUserID := "5678"
+	userGitLab := &user_model.User{
+		Name:        "gitlabuser",
+		Email:       "gitlabuser@example.com",
+		Passwd:      "gitlabuserpassword",
+		Type:        user_model.UserTypeIndividual,
+		LoginType:   auth_model.OAuth2,
+		LoginSource: gitlab.ID,
+		LoginName:   userGitLabUserID,
+	}
+	defer createUser(context.Background(), t, userGitLab)()
+
+	// initial redirection (to generate the code_challenge)
+	session := emptyTestSession(t)
+	req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s", gitlabName))
+	resp := session.MakeRequest(t, req, http.StatusTemporaryRedirect)
+	dest, err := url.Parse(resp.Header().Get("Location"))
+	assert.NoError(t, err)
+	assert.Equal(t, "S256", dest.Query().Get("code_challenge_method"))
+	codeChallenge := dest.Query().Get("code_challenge")
+	assert.NotEmpty(t, codeChallenge)
+
+	// callback (to check the initial code_challenge)
+	defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
+		codeVerifier := req.URL.Query().Get("code_verifier")
+		assert.NotEmpty(t, codeVerifier)
+		assert.Greater(t, len(codeVerifier), 40, codeVerifier)
+
+		sha2 := sha256.New()
+		io.WriteString(sha2, codeVerifier)
+		assert.Equal(t, codeChallenge, base64.RawURLEncoding.EncodeToString(sha2.Sum(nil)))
+
+		return goth.User{
+			Provider: gitlabName,
+			UserID:   userGitLabUserID,
+			Email:    userGitLab.Email,
+		}, nil
+	})()
+	req = NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", gitlabName))
+	resp = session.MakeRequest(t, req, http.StatusSeeOther)
+	assert.Equal(t, "/", test.RedirectURL(resp))
+	unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userGitLab.ID})
+}
+
 func TestSignInOAuthCallbackRedirectToEscaping(t *testing.T) {
 	defer tests.PrepareTestEnv(t)()
 

From 3224628f3626e0ba19a43da998841ea1a9c9a82f Mon Sep 17 00:00:00 2001
From: "Panagiotis \"Ivory\" Vasilopoulos" <git@n0toose.net>
Date: Mon, 29 Apr 2024 16:26:26 +0200
Subject: [PATCH 074/107] [UI] Replace italics in Settings UI

---
 templates/package/shared/cleanup_rules/list.tmpl     | 10 +++++-----
 templates/repo/create.tmpl                           |  2 +-
 templates/repo/issue/labels/label_load_template.tmpl |  2 +-
 templates/repo/settings/deploy_keys.tmpl             |  2 +-
 templates/user/settings/applications.tmpl            |  4 ++--
 templates/user/settings/grants_oauth2.tmpl           |  2 +-
 templates/user/settings/keys_gpg.tmpl                | 12 +++++++++---
 templates/user/settings/keys_principal.tmpl          |  2 +-
 templates/user/settings/keys_ssh.tmpl                |  2 +-
 templates/user/settings/profile.tmpl                 |  4 ++--
 templates/user/settings/security/webauthn.tmpl       |  2 +-
 11 files changed, 25 insertions(+), 19 deletions(-)

diff --git a/templates/package/shared/cleanup_rules/list.tmpl b/templates/package/shared/cleanup_rules/list.tmpl
index 6505c8f9a5..ba1683b932 100644
--- a/templates/package/shared/cleanup_rules/list.tmpl
+++ b/templates/package/shared/cleanup_rules/list.tmpl
@@ -16,26 +16,26 @@
 						<a class="item" href="{{$.Link}}/rules/{{.ID}}">{{.Type.Name}}</a>
 					</div>
 					<div class="flex-item-body">
-						<i>{{if .Enabled}}{{ctx.Locale.Tr "enabled"}}{{else}}{{ctx.Locale.Tr "disabled"}}{{end}}</i>
+						<p>{{if .Enabled}}{{ctx.Locale.Tr "enabled"}}{{else}}{{ctx.Locale.Tr "disabled"}}{{end}}</p>
 					</div>
 					{{if .KeepCount}}
 					<div class="flex-item-body">
-						<i>{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.count"}}:</i> {{if eq .KeepCount 1}}{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.count.1"}}{{else}}{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.count.n" .KeepCount}}{{end}}
+						<p>{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.count"}}:</p> {{if eq .KeepCount 1}}{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.count.1"}}{{else}}{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.count.n" .KeepCount}}{{end}}
 					</div>
 					{{end}}
 					{{if .KeepPattern}}
 					<div class="flex-item-body">
-						<i>{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.pattern"}}:</i> {{StringUtils.EllipsisString .KeepPattern 100}}
+						<p>{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.pattern"}}:</p> {{StringUtils.EllipsisString .KeepPattern 100}}
 					</div>
 					{{end}}
 					{{if .RemoveDays}}
 					<div class="flex-item-body">
-						<i>{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.remove.days"}}:</i> {{ctx.Locale.Tr "tool.days" .RemoveDays}}
+						<p>{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.remove.days"}}:</p> {{ctx.Locale.Tr "tool.days" .RemoveDays}}
 					</div>
 					{{end}}
 					{{if .RemovePattern}}
 					<div class="flex-item-body">
-						<i>{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.remove.pattern"}}:</i> {{StringUtils.EllipsisString .RemovePattern 100}}
+						<p>{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.remove.pattern"}}:</p> {{StringUtils.EllipsisString .RemovePattern 100}}
 					</div>
 					{{end}}
 				</div>
diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl
index bcd3c16b6a..2398cc9ad6 100644
--- a/templates/repo/create.tmpl
+++ b/templates/repo/create.tmpl
@@ -125,7 +125,7 @@
 								<div class="menu">
 									<div class="item" data-value="">{{ctx.Locale.Tr "repo.issue_labels_helper"}}</div>
 									{{range .LabelTemplateFiles}}
-										<div class="item" data-value="{{.DisplayName}}">{{.DisplayName}}<br><i>({{.Description}})</i></div>
+										<div class="item" data-value="{{.DisplayName}}">{{.DisplayName}}<br><p>({{.Description}})</p></div>
 									{{end}}
 								</div>
 							</div>
diff --git a/templates/repo/issue/labels/label_load_template.tmpl b/templates/repo/issue/labels/label_load_template.tmpl
index 0499afea19..0249430051 100644
--- a/templates/repo/issue/labels/label_load_template.tmpl
+++ b/templates/repo/issue/labels/label_load_template.tmpl
@@ -11,7 +11,7 @@
 						<div class="default text">{{ctx.Locale.Tr "repo.issues.label_templates.helper"}}</div>
 						<div class="menu">
 							{{range .LabelTemplateFiles}}
-								<div class="item" data-value="{{.DisplayName}}">{{.DisplayName}}<br><i>({{.Description}})</i></div>
+								<div class="item" data-value="{{.DisplayName}}">{{.DisplayName}}<br><p>({{.Description}})</p></div>
 							{{end}}
 						</div>
 						{{svg "octicon-triangle-down" 18 "dropdown icon"}}
diff --git a/templates/repo/settings/deploy_keys.tmpl b/templates/repo/settings/deploy_keys.tmpl
index da1a321785..3410c103a2 100644
--- a/templates/repo/settings/deploy_keys.tmpl
+++ b/templates/repo/settings/deploy_keys.tmpl
@@ -55,7 +55,7 @@
 									{{.Fingerprint}}
 								</div>
 								<div class="flex-item-body">
-									<i>{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}} —  {{svg "octicon-info"}} {{if .HasUsed}}{{ctx.Locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="text green"{{end}}>{{DateTime "short" .UpdatedUnix}}</span>{{else}}{{ctx.Locale.Tr "settings.no_activity"}}{{end}} - <span>{{ctx.Locale.Tr "settings.can_read_info"}}{{if not .IsReadOnly}} / {{ctx.Locale.Tr "settings.can_write_info"}} {{end}}</span></i>
+									<p>{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}} — {{svg "octicon-info"}} {{if .HasUsed}}{{ctx.Locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="text green"{{end}}>{{DateTime "short" .UpdatedUnix}}</span>{{else}}{{ctx.Locale.Tr "settings.no_activity"}}{{end}} - <span>{{ctx.Locale.Tr "settings.can_read_info"}}{{if not .IsReadOnly}} / {{ctx.Locale.Tr "settings.can_write_info"}} {{end}}</span></p>
 								</div>
 							</div>
 							<div class="flex-item-trailing">
diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl
index 54fdf9fbed..b6bc9fecc9 100644
--- a/templates/user/settings/applications.tmpl
+++ b/templates/user/settings/applications.tmpl
@@ -36,7 +36,7 @@
 								</ul>
 							</details>
 							<div class="flex-item-body">
-								<i>{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}} — {{svg "octicon-info"}} {{if .HasUsed}}{{ctx.Locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="text green"{{end}}>{{DateTime "short" .UpdatedUnix}}</span>{{else}}{{ctx.Locale.Tr "settings.no_activity"}}{{end}}</i>
+								<p>{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}} — {{svg "octicon-info"}} {{if .HasUsed}}{{ctx.Locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="text green"{{end}}>{{DateTime "short" .UpdatedUnix}}</span>{{else}}{{ctx.Locale.Tr "settings.no_activity"}}{{end}}</p>
 							</div>
 						</div>
 						<div class="flex-item-trailing">
@@ -75,7 +75,7 @@
 						{{ctx.Locale.Tr "settings.select_permissions"}}
 					</summary>
 					<p class="activity meta">
-						<i>{{ctx.Locale.Tr "settings.access_token_desc" (`href="/api/swagger" target="_blank"`|SafeHTML) (`href="https://forgejo.org/docs/latest/user/token-scope/" target="_blank"`|SafeHTML)}}</i>
+						<p>{{ctx.Locale.Tr "settings.access_token_desc" (`href="/api/swagger" target="_blank"`|SafeHTML) (`href="https://forgejo.org/docs/latest/user/token-scope/" target="_blank"`|SafeHTML)}}</p>
 					</p>
 					<div class="scoped-access-token-mount">
 						<scoped-access-token-selector
diff --git a/templates/user/settings/grants_oauth2.tmpl b/templates/user/settings/grants_oauth2.tmpl
index b5ae3e0337..e89d275660 100644
--- a/templates/user/settings/grants_oauth2.tmpl
+++ b/templates/user/settings/grants_oauth2.tmpl
@@ -14,7 +14,7 @@
 				<div class="flex-item-main">
 					<div class="flex-item-title">{{.Application.Name}}</div>
 					<div class="flex-item-body">
-						<i>{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}}</i>
+						<p>{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}}</p>
 					</div>
 				</div>
 				<div class="flex-item-trailing">
diff --git a/templates/user/settings/keys_gpg.tmpl b/templates/user/settings/keys_gpg.tmpl
index 386de7e2f8..f5e91cedff 100644
--- a/templates/user/settings/keys_gpg.tmpl
+++ b/templates/user/settings/keys_gpg.tmpl
@@ -63,9 +63,15 @@
 						<b>{{ctx.Locale.Tr "settings.subkeys"}}:</b> {{range .SubsKey}} {{.PaddedKeyID}} {{end}}
 					</div>
 					<div class="flex-item-body">
-						<i>{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .AddedUnix)}}</i>
-						-
-						<i>{{if not .ExpiredUnix.IsZero}}{{ctx.Locale.Tr "settings.valid_until_date" (DateTime "short" .ExpiredUnix)}}{{else}}{{ctx.Locale.Tr "settings.valid_forever"}}{{end}}</i>
+						<p>
+							{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .AddedUnix)}}
+							-
+							{{if not .ExpiredUnix.IsZero}}
+								{{ctx.Locale.Tr "settings.valid_until_date" (DateTime "short" .ExpiredUnix)}}
+							{{else}}
+								{{ctx.Locale.Tr "settings.valid_forever"}}
+							{{end}}
+						</p>
 					</div>
 				</div>
 				<div class="flex-item-trailing">
diff --git a/templates/user/settings/keys_principal.tmpl b/templates/user/settings/keys_principal.tmpl
index 37d8fb0e95..94b1b2c8ff 100644
--- a/templates/user/settings/keys_principal.tmpl
+++ b/templates/user/settings/keys_principal.tmpl
@@ -22,7 +22,7 @@
 					<div class="flex-item-main">
 						<div class="flex-item-title">{{.Name}}</div>
 						<div class="flex-item-body">
-							<i>{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}} —  {{svg "octicon-info" 16}} {{if .HasUsed}}{{ctx.Locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{DateTime "short" .UpdatedUnix}}</span>{{else}}{{ctx.Locale.Tr "settings.no_activity"}}{{end}}</i>
+							<p>{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}} —  {{svg "octicon-info" 16}} {{if .HasUsed}}{{ctx.Locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}>{{DateTime "short" .UpdatedUnix}}</span>{{else}}{{ctx.Locale.Tr "settings.no_activity"}}{{end}}</p>
 						</div>
 					</div>
 					<div class="flex-item-trailing">
diff --git a/templates/user/settings/keys_ssh.tmpl b/templates/user/settings/keys_ssh.tmpl
index 3b6e92f7d7..058dc9ceb7 100644
--- a/templates/user/settings/keys_ssh.tmpl
+++ b/templates/user/settings/keys_ssh.tmpl
@@ -53,7 +53,7 @@
 								{{.Fingerprint}}
 						</div>
 						<div class="flex-item-body">
-								<i>{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}} —	{{svg "octicon-info"}} {{if .HasUsed}}{{ctx.Locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="text green"{{end}}>{{DateTime "short" .UpdatedUnix}}</span>{{else}}{{ctx.Locale.Tr "settings.no_activity"}}{{end}}</i>
+								<p>{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}} — {{svg "octicon-info"}} {{if .HasUsed}}{{ctx.Locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="text green"{{end}}>{{DateTime "short" .UpdatedUnix}}</span>{{else}}{{ctx.Locale.Tr "settings.no_activity"}}{{end}}</p>
 						</div>
 				</div>
 				<div class="flex-item-trailing">
diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl
index 99835ae907..cd6efbfc1c 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -36,14 +36,14 @@
 						</div>
 						{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 						<div class="menu">
-							<div class="item{{if eq "" .SignedUser.Pronouns}} active selected{{end}}" data-value=""><i>{{ctx.Locale.Tr "settings.pronouns_unspecified"}}</i></div>
+							<div class="item{{if eq "" .SignedUser.Pronouns}} active selected{{end}}" data-value=""><p>{{ctx.Locale.Tr "settings.pronouns_unspecified"}}</p></div>
 							<div class="item{{if eq "he/him" .SignedUser.Pronouns}} active selected{{end}}" data-value="he/him">he/him</div>
 							<div class="item{{if eq "she/her" .SignedUser.Pronouns}} active selected{{end}}" data-value="she/her">she/her</div>
 							<div class="item{{if eq "they/them" .SignedUser.Pronouns}} active selected{{end}}" data-value="they/them">they/them</div>
 							<div class="item{{if eq "it/its" .SignedUser.Pronouns}} active selected{{end}}" data-value="it/its">it/its</div>
 							<div class="item{{if eq "any pronouns" .SignedUser.Pronouns}} active selected{{end}}" data-value="any pronouns">any pronouns</div>
 							{{if .PronounsAreCustom}}
-								<div class="item active selected" data-value="{{.SignedUser.Pronouns}}"><i>{{ctx.Locale.Tr "settings.pronouns_custom"}}</i></div>
+								<div class="item active selected" data-value="{{.SignedUser.Pronouns}}"><p>{{ctx.Locale.Tr "settings.pronouns_custom"}}</p></div>
 							{{else}}
 								<div class="item" data-value="!"><i>{{ctx.Locale.Tr "settings.pronouns_custom"}}</i></div>
 							{{end}}
diff --git a/templates/user/settings/security/webauthn.tmpl b/templates/user/settings/security/webauthn.tmpl
index eceee191bd..764844b23b 100644
--- a/templates/user/settings/security/webauthn.tmpl
+++ b/templates/user/settings/security/webauthn.tmpl
@@ -12,7 +12,7 @@
 				<div class="flex-item-main">
 					<div class="flex-item-title">{{.Name}}</div>
 					<div class="flex-item-body">
-						<i>{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}}</i>
+						<p>{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}}</p>
 					</div>
 				</div>
 				<div class="flex-item-trailing">

From 103306f00ca54538ab7ed77aa7990c69ef2e0c63 Mon Sep 17 00:00:00 2001
From: "Panagiotis \"Ivory\" Vasilopoulos" <git@n0toose.net>
Date: Mon, 29 Apr 2024 19:48:21 +0200
Subject: [PATCH 075/107] UI: Disable internal wiki options when enabling
 external wiki

Using "data-target", it is possible to set a value to a target element
that can enable it or disable it. Using "data-context" lets us perform
the opposite action on a different target.

Before, only the #external_wiki_box target was used, which was enabled
or disabled depending on whether the user has chosen to use the internal
wiki or the external wiki. If the user chooses to use the internal wiki,
they will disable the box that lets them enter a link pointing to an
external wiki, and vice versa. Although it is not possible to use, say,
boolean operations, we can introduce a target that is
called #globally_writeable_checkbox that gets enabled when
the #external_wiki_box box is disabled, and vice versa.

This makes the box's behavior more consistent with the behavior in the
"Issues" section. To keep things consistent with that section, a new
property was assigned to the "globally_writeable_checkbox" that makes
the box go a bit further in (`tw-pl-4`).
---
 templates/repo/settings/units/wiki.tmpl | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/templates/repo/settings/units/wiki.tmpl b/templates/repo/settings/units/wiki.tmpl
index dc83483b04..23df294bd3 100644
--- a/templates/repo/settings/units/wiki.tmpl
+++ b/templates/repo/settings/units/wiki.tmpl
@@ -16,13 +16,13 @@
 	<div class="field{{if not $isWikiEnabled}} disabled{{end}}" id="wiki_box">
 		<div class="field">
 			<div class="ui radio checkbox{{if $isWikiGlobalDisabled}} disabled{{end}}"{{if $isWikiGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
-				<input class="enable-system-radio" name="enable_external_wiki" type="radio" value="false" data-target="#external_wiki_box" {{if not (.Repository.UnitEnabled $.Context $.UnitTypeExternalWiki)}}checked{{end}}>
+				<input class="enable-system-radio" name="enable_external_wiki" type="radio" value="false" data-context="#globally_writeable_checkbox" data-target="#external_wiki_box" {{if not (.Repository.UnitEnabled $.Context $.UnitTypeExternalWiki)}}checked{{end}}>
 				<label>{{ctx.Locale.Tr "repo.settings.use_internal_wiki"}}</label>
 			</div>
 		</div>
 		{{if (not .Repository.IsPrivate)}}
-			<div class="field {{if (.Repository.UnitEnabled $.Context $.UnitTypeExternalWiki)}}disabled{{end}}">
-				<div class="field">
+			<div class="field {{if (.Repository.UnitEnabled $.Context $.UnitTypeExternalWiki)}}disabled{{end}}" id="globally_writeable_checkbox">
+				<div class="field tw-pl-4">
 					<div class="ui checkbox">
 						<input name="globally_writeable_wiki" type="checkbox" {{if .Permission.IsGloballyWriteable $.UnitTypeWiki}}checked{{end}}>
 						<label>{{ctx.Locale.Tr "repo.settings.wiki_globally_editable"}}</label>
@@ -32,7 +32,7 @@
 		{{end}}
 		<div class="field">
 			<div class="ui radio checkbox{{if $isExternalWikiGlobalDisabled}} disabled{{end}}"{{if $isExternalWikiGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
-				<input class="enable-system-radio" name="enable_external_wiki" type="radio" value="true" data-target="#external_wiki_box" {{if .Repository.UnitEnabled $.Context $.UnitTypeExternalWiki}}checked{{end}}>
+				<input class="enable-system-radio" name="enable_external_wiki" type="radio" value="true" data-context="#globally_writeable_checkbox" data-target="#external_wiki_box" {{if .Repository.UnitEnabled $.Context $.UnitTypeExternalWiki}}checked{{end}}>
 				<label>{{ctx.Locale.Tr "repo.settings.use_external_wiki"}}</label>
 			</div>
 		</div>

From 870a1c85c75622a566dcab0eee12f0d91d91a06a Mon Sep 17 00:00:00 2001
From: "Panagiotis \"Ivory\" Vasilopoulos" <git@n0toose.net>
Date: Mon, 29 Apr 2024 21:05:18 +0200
Subject: [PATCH 076/107] UI: Actions: Replace runs list description semicolon

The current format makes the text look somewhat like this:

```
testing.yml #15065 :Commit 103306f00c pushed by n0toose
```

This looks wrong. We will have to work on that list at a later point
in time anyways, as well as make the way that we separate information
in subheaders in lists like this one more consistent.

However, this should do for now.

This change should make each entry look like this instead:

```
testing.yml #15065 - Commit 103306f00c pushed by n0toose
```
---
 templates/repo/actions/runs_list.tmpl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/templates/repo/actions/runs_list.tmpl b/templates/repo/actions/runs_list.tmpl
index 20330b5d62..e37f3d7dc3 100644
--- a/templates/repo/actions/runs_list.tmpl
+++ b/templates/repo/actions/runs_list.tmpl
@@ -15,7 +15,7 @@
 					{{if .Title}}{{.Title}}{{else}}{{ctx.Locale.Tr "actions.runs.empty_commit_message"}}{{end}}
 				</a>
 				<div class="flex-item-body">
-					<b>{{if not $.CurWorkflow}}{{.WorkflowID}} {{end}}#{{.Index}}</b>:
+					<b>{{if not $.CurWorkflow}}{{.WorkflowID}} {{end}}#{{.Index}}</b> - 
 					{{- if .ScheduleID -}}
 						{{ctx.Locale.Tr "actions.runs.scheduled"}}
 					{{- else -}}

From 900bf43a8ae46687794719c7460d2a6eeeaa73ff Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Mon, 29 Apr 2024 23:35:44 +0200
Subject: [PATCH 077/107] Fix PR WIP toggle prefixes

When the variable was renamed, this occurence was missed.
---
 templates/repo/issue/view_content/pull.tmpl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl
index 2d657c74ac..2ea9165ac7 100644
--- a/templates/repo/issue/view_content/pull.tmpl
+++ b/templates/repo/issue/view_content/pull.tmpl
@@ -81,7 +81,7 @@
 					{{ctx.Locale.Tr "repo.pulls.data_broken"}}
 				</div>
 			{{else if .IsPullWorkInProgress}}
-				<div class="item toggle-wip" data-title="{{.Issue.Title}}" data-wip-prefix="{{.WorkInProgressPrefix}}" data-update-url="{{.Issue.Link}}/title">
+				<div class="item toggle-wip" data-title="{{.Issue.Title}}" data-wip-prefixes="{{JsonUtils.EncodeToString .PullRequestWorkInProgressPrefixes}}" data-update-url="{{.Issue.Link}}/title">
 					<div class="item-section-left flex-text-inline tw-flex-1">
 						{{svg "octicon-x"}}
 						{{ctx.Locale.Tr "repo.pulls.cannot_merge_work_in_progress"}}

From 5ed3ffc0c8a066b4931e2e4d0233e0bf884de4b3 Mon Sep 17 00:00:00 2001
From: proton-ab <proton_ab@tuta.io>
Date: Mon, 29 Apr 2024 22:17:18 +0000
Subject: [PATCH 078/107] [I18N] Fix tepository->repository typo

Signed-off-by: proton-ab <proton_ab@tuta.io>
---
 options/locale/locale_en-US.ini | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 7125d97d9b..c7a7d32918 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2753,7 +2753,7 @@ team_name_helper = Team names should be short and memorable.
 team_desc_helper = Describe the purpose or role of the team.
 team_access_desc = Repository access
 team_permission_desc = Permission
-team_unit_desc = Allow access to tepository sections
+team_unit_desc = Allow access to repository sections
 team_unit_disabled = (Disabled)
 follow_blocked_user = You cannot follow this organisation because this organisation has blocked you.
 

From a8211e07d80d3c6b0a3a0125de1111dfed7ac6e0 Mon Sep 17 00:00:00 2001
From: Codeberg Build Maintainers <codeberg@codeberg.org>
Date: Mon, 29 Apr 2024 21:57:29 +0000
Subject: [PATCH 079/107] CB/tmpl: Modify icon for add more button

---
 templates/repo/header.tmpl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl
index bce0e79bca..21017415c1 100644
--- a/templates/repo/header.tmpl
+++ b/templates/repo/header.tmpl
@@ -178,7 +178,7 @@
 					{{if and .SignedUser.EnableRepoUnitHints (not (.Repository.AllUnitsEnabled ctx))}}
 						{{$highlightSettings = false}}
 						<a class="{{if .PageIsRepoSettingsUnits}}active {{end}}item" href="{{.RepoLink}}/settings/units">
-							{{svg "octicon-diff-added"}} {{ctx.Locale.Tr "repo.settings.units.add_more"}}
+							{{svg "octicon-plus"}} {{ctx.Locale.Tr "repo.settings.units.add_more"}}
 						</a>
 					{{end}}
 					<a id="settings-btn" class="{{if and .PageIsRepoSettings (or $highlightSettings (not .PageIsRepoSettingsUnits))}}active {{end}}right item" href="{{.RepoLink}}/settings">

From 03c1c88b5be5a0371334ae9e9b6a1b96473eff15 Mon Sep 17 00:00:00 2001
From: Otto Richter <git@otto.splvs.net>
Date: Tue, 30 Apr 2024 01:46:09 +0200
Subject: [PATCH 080/107] Fix colour contrast issues

---
 web_src/css/form.css                       | 2 +-
 web_src/css/themes/theme-forgejo-dark.css  | 2 +-
 web_src/css/themes/theme-forgejo-light.css | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/web_src/css/form.css b/web_src/css/form.css
index a8f73b6b66..c757234e3b 100644
--- a/web_src/css/form.css
+++ b/web_src/css/form.css
@@ -115,7 +115,7 @@ textarea:focus,
 }
 
 .form .help {
-  color: var(--color-secondary-dark-5);
+  color: var(--color-secondary-dark-8);
   padding-bottom: 0.6em;
   display: inline-block;
 }
diff --git a/web_src/css/themes/theme-forgejo-dark.css b/web_src/css/themes/theme-forgejo-dark.css
index 7ff071bbb0..42495c854b 100644
--- a/web_src/css/themes/theme-forgejo-dark.css
+++ b/web_src/css/themes/theme-forgejo-dark.css
@@ -213,7 +213,7 @@
   --color-secondary-bg: var(--steel-700);
   --color-text-focus: #fff;
   --color-expand-button: #3c404d;
-  --color-placeholder-text: var(--steel-450);
+  --color-placeholder-text: var(--color-text-light-3);
   --color-editor-line-highlight: var(--steel-700);
   --color-project-board-bg: var(--color-secondary-light-3);
   --color-project-board-dark-label: var(--color-text-light-3);
diff --git a/web_src/css/themes/theme-forgejo-light.css b/web_src/css/themes/theme-forgejo-light.css
index 0ee7020a5e..ea988e67fa 100644
--- a/web_src/css/themes/theme-forgejo-light.css
+++ b/web_src/css/themes/theme-forgejo-light.css
@@ -231,7 +231,7 @@
   --color-secondary-bg: var(--zinc-100);
   --color-text-focus: #fff;
   --color-expand-button: var(--zinc-200);
-  --color-placeholder-text: var(--zinc-400);
+  --color-placeholder-text: var(--color-text-light-3);
   --color-editor-line-highlight: var(--zinc-100);
   --color-project-board-bg: var(--color-secondary-light-2);
   --color-project-board-dark-label: var(--color-text-light-3);

From aba5fe36d8952d8ecc14f93fdc3fc9dcb7e80228 Mon Sep 17 00:00:00 2001
From: Renovate Bot <forgejo-renovate-action@forgejo.org>
Date: Tue, 30 Apr 2024 00:11:35 +0000
Subject: [PATCH 081/107] Update dependency @stylistic/stylelint-plugin to
 v2.1.2

---
 package-lock.json | 18 +++++++++---------
 package.json      |  2 +-
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index be6eadaee6..8b9c17a97c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -68,7 +68,7 @@
         "@playwright/test": "1.43.0",
         "@stoplight/spectral-cli": "6.11.1",
         "@stylistic/eslint-plugin-js": "1.7.2",
-        "@stylistic/stylelint-plugin": "2.1.1",
+        "@stylistic/stylelint-plugin": "2.1.2",
         "@vitejs/plugin-vue": "5.0.4",
         "@vue/test-utils": "2.4.5",
         "eslint": "8.57.0",
@@ -2145,19 +2145,19 @@
       }
     },
     "node_modules/@stylistic/stylelint-plugin": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-2.1.1.tgz",
-      "integrity": "sha512-xqHTmQZN7EbnFDW7jw0rAsdFNO4IRqvXhrh3qhUlIwF/x09Zm7kgs/ADktHxsTJYcw346PpGihsB0t4pZhpeHw==",
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-2.1.2.tgz",
+      "integrity": "sha512-JsSqu0Y3vsX+PBl+DwULxC0cIv9C1yIcq1MXkx7pBOGtTqU26a75I8MPYMiEYvrsXgsKLi65xVgy1iLVSZquJA==",
       "dev": true,
       "dependencies": {
-        "@csstools/css-parser-algorithms": "^2.5.0",
-        "@csstools/css-tokenizer": "^2.2.3",
-        "@csstools/media-query-list-parser": "^2.1.7",
+        "@csstools/css-parser-algorithms": "^2.6.1",
+        "@csstools/css-tokenizer": "^2.2.4",
+        "@csstools/media-query-list-parser": "^2.1.9",
         "is-plain-object": "^5.0.0",
-        "postcss-selector-parser": "^6.0.15",
+        "postcss-selector-parser": "^6.0.16",
         "postcss-value-parser": "^4.2.0",
         "style-search": "^0.1.0",
-        "stylelint": "^16.2.1"
+        "stylelint": "^16.4.0"
       },
       "engines": {
         "node": "^18.12 || >=20.9"
diff --git a/package.json b/package.json
index 5a95503a58..68f3c4ffcb 100644
--- a/package.json
+++ b/package.json
@@ -67,7 +67,7 @@
     "@playwright/test": "1.43.0",
     "@stoplight/spectral-cli": "6.11.1",
     "@stylistic/eslint-plugin-js": "1.7.2",
-    "@stylistic/stylelint-plugin": "2.1.1",
+    "@stylistic/stylelint-plugin": "2.1.2",
     "@vitejs/plugin-vue": "5.0.4",
     "@vue/test-utils": "2.4.5",
     "eslint": "8.57.0",

From a7fcc3ca3f25aad074f9c6c3d84ac6869c62f317 Mon Sep 17 00:00:00 2001
From: Renovate Bot <forgejo-renovate-action@forgejo.org>
Date: Tue, 30 Apr 2024 00:11:43 +0000
Subject: [PATCH 082/107] Update dependency dayjs to v1.11.11

---
 package-lock.json | 8 ++++----
 package.json      | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index be6eadaee6..4e4beceb13 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,7 +22,7 @@
         "chartjs-plugin-zoom": "2.0.1",
         "clippie": "4.0.7",
         "css-loader": "7.0.0",
-        "dayjs": "1.11.10",
+        "dayjs": "1.11.11",
         "dropzone": "6.0.0-beta.2",
         "easymde": "2.18.0",
         "esbuild-loader": "4.1.0",
@@ -4631,9 +4631,9 @@
       }
     },
     "node_modules/dayjs": {
-      "version": "1.11.10",
-      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
-      "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
+      "version": "1.11.11",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz",
+      "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg=="
     },
     "node_modules/debug": {
       "version": "4.3.4",
diff --git a/package.json b/package.json
index 5a95503a58..a139b6c00e 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,7 @@
     "chartjs-plugin-zoom": "2.0.1",
     "clippie": "4.0.7",
     "css-loader": "7.0.0",
-    "dayjs": "1.11.10",
+    "dayjs": "1.11.11",
     "dropzone": "6.0.0-beta.2",
     "easymde": "2.18.0",
     "esbuild-loader": "4.1.0",

From 11a7d8adfbb3d15490dd80cd0bdf2fa124928a20 Mon Sep 17 00:00:00 2001
From: 0ko <0ko@noreply.codeberg.org>
Date: Tue, 30 Apr 2024 08:59:06 +0500
Subject: [PATCH 083/107] [I18N] Cumulative English locale improvements

- decap wiki UI
- decap release UI
- fix cap of self visibility toggle button on org member list
- simplify `release.add_tag`
- simplify `admin_indexer_commit_sha` (we got multiple hash types now, but this referes to commit)
- some improvements to admin dashboard
- fnetX suggestions: [[1]](https://codeberg.org/forgejo/forgejo/pulls/3266#issuecomment-1795685), [[2]](https://codeberg.org/forgejo/forgejo/pulls/3266#issuecomment-1795734)
---
 options/locale/locale_en-US.ini | 62 +++++++++++++++------------------
 1 file changed, 28 insertions(+), 34 deletions(-)

diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 7125d97d9b..e65219b514 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1067,11 +1067,6 @@ readme = README
 readme_helper = Select a README file template.
 readme_helper_desc = This is the place where you can write a complete description for your project.
 auto_init = Initialize repository (Adds .gitignore, License and README)
-trust_model_helper = Select trust model for signature verification. Possible options are:
-trust_model_helper_collaborator = Collaborator: Trust signatures by collaborators
-trust_model_helper_committer = Committer: Trust signatures that match committers
-trust_model_helper_collaborator_committer = Collaborator+Committer: Trust signatures by collaborators which match the committer
-trust_model_helper_default = Default: Use the default trust model for this installation
 create_repo = Create repository
 default_branch = Default branch
 default_branch_label = default
@@ -2002,22 +1997,22 @@ wiki = Wiki
 wiki.welcome = Welcome to the Wiki.
 wiki.welcome_desc = The wiki lets you write and share documentation with collaborators.
 wiki.desc = Write and share documentation with collaborators.
-wiki.create_first_page = Create the First Page
+wiki.create_first_page = Create the first page
 wiki.page = Page
 wiki.filter_page = Filter page
 wiki.new_page = Page
 wiki.page_title = Page title
 wiki.page_content = Page content
 wiki.default_commit_message = Write a note about this page update (optional).
-wiki.save_page = Save Page
+wiki.save_page = Save page
 wiki.cancel = Cancel
 wiki.last_commit_info = %s edited this page %s
 wiki.edit_page_button = Edit
-wiki.new_page_button = New Page
-wiki.file_revision = Page Revision
-wiki.wiki_page_revisions = Wiki Page Revisions
+wiki.new_page_button = New page
+wiki.file_revision = Page revision
+wiki.wiki_page_revisions = Page revisions
 wiki.back_to_wiki = Back to wiki page
-wiki.delete_page_button = Delete Page
+wiki.delete_page_button = Delete page
 wiki.delete_page_notice_1 = Deleting the wiki page "%s" cannot be undone. Continue?
 wiki.page_already_exists = A wiki page with the same name already exists.
 wiki.reserved_page = The wiki page name "%s" is reserved.
@@ -2184,12 +2179,12 @@ settings.pulls.default_allow_edits_from_maintainers = Allow edits from maintaine
 settings.releases_desc = Enable repository releases
 settings.packages_desc = Enable repository package registry
 settings.projects_desc = Enable repository projects
-settings.actions_desc = Enable repository actions
+settings.actions_desc = Enable integrated CI/CD pipelines with Forgejo Actions
 settings.admin_settings = Administrator settings
 settings.admin_enable_health_check = Enable repository health checks (git fsck)
 settings.admin_code_indexer = Code indexer
 settings.admin_stats_indexer = Code statistics indexer
-settings.admin_indexer_commit_sha = Last indexed SHA
+settings.admin_indexer_commit_sha = Last indexed commit
 settings.admin_indexer_unindexed = Unindexed
 settings.reindex_button = Add to reindex queue
 settings.reindex_requested=Reindex requested
@@ -2610,7 +2605,7 @@ diff.review.reject = Request changes
 diff.review.self_approve = Pull request authors can't approve their own pull request
 diff.committed_by = committed by
 diff.protected = Protected
-diff.image.side_by_side = Side by Side
+diff.image.side_by_side = Side by side
 diff.image.swipe = Swipe
 diff.image.overlay = Overlay
 diff.has_escaped = This line has hidden Unicode characters
@@ -2621,16 +2616,16 @@ releases.desc = Track project versions and downloads.
 release.releases = Releases
 release.detail = Release details
 release.tags = Tags
-release.new_release = New Release
+release.new_release = New release
 release.draft = Draft
-release.prerelease = Pre-Release
+release.prerelease = Pre-release
 release.stable = Stable
 release.compare = Compare
 release.edit = edit
 release.ahead.commits = <strong>%d</strong> commits
 release.ahead.target = to %s since this release
 tag.ahead.target = to %s since this tag
-release.source_code = Source Code
+release.source_code = Source code
 release.new_subheader = Releases organize project versions.
 release.edit_subheader = Releases organize project versions.
 release.tag_name = Tag name
@@ -2641,15 +2636,15 @@ release.tag_helper_existing = Existing tag.
 release.title = Release title
 release.title_empty = Title cannot be empty.
 release.message = Describe this release
-release.prerelease_desc = Mark as Pre-Release
+release.prerelease_desc = Mark as pre-release
 release.prerelease_helper = Mark this release unsuitable for production use.
 release.cancel = Cancel
-release.publish = Publish Release
-release.save_draft = Save Draft
-release.edit_release = Update Release
-release.delete_release = Delete Release
-release.delete_tag = Delete Tag
-release.deletion = Delete Release
+release.publish = Publish release
+release.save_draft = Save draft
+release.edit_release = Update release
+release.delete_release = Delete release
+release.delete_tag = Delete tag
+release.deletion = Delete release
 release.deletion_desc = Deleting a release only removes it from Forgejo. It will not affect the Git tag, the contents of your repository or its history. Continue?
 release.deletion_success = The release has been deleted.
 release.deletion_tag_desc = Will delete this tag from repository. Repository contents and history remain unchanged. Continue?
@@ -2664,7 +2659,7 @@ release.download_count_few = %s downloads
 release.add_tag_msg = Use the title and content of release as tag message.
 release.hide_archive_links = Hide automatically generated archives
 release.hide_archive_links_helper = Hide automatically generated source code archives for this release. For example, if you are uploading your own.
-release.add_tag = Create Tag Only
+release.add_tag = Create tag
 release.releases_for = Releases for %s
 release.tags_for = Tags for %s
 release.system_generated = This attachment is automatically generated.
@@ -2793,9 +2788,9 @@ settings.labels_desc = Add labels which can be used on issues for <strong>all re
 
 members.membership_visibility = Membership visibility:
 members.public = Visible
-members.public_helper = make hidden
+members.public_helper = Make hidden
 members.private = Hidden
-members.private_helper = make visible
+members.private_helper = Make visible
 members.member_role = Member role:
 members.owner = Owner
 members.member = Member
@@ -2951,8 +2946,8 @@ dashboard.last_gc_time = Time since last GC
 dashboard.total_gc_pause = Total GC pause
 dashboard.last_gc_pause = Last GC pause
 dashboard.gc_times = GC times
-dashboard.delete_old_actions = Delete all old actions from database
-dashboard.delete_old_actions.started = Delete all old actions from database started.
+dashboard.delete_old_actions = Delete all old activities from database
+dashboard.delete_old_actions.started = Delete all old activities from database started.
 dashboard.update_checker = Update checker
 dashboard.delete_old_system_notices = Delete all old system notices from database
 dashboard.gc_lfs = Garbage collect LFS meta objects
@@ -2960,8 +2955,8 @@ dashboard.stop_zombie_tasks = Stop zombie tasks
 dashboard.stop_endless_tasks = Stop endless tasks
 dashboard.cancel_abandoned_jobs = Cancel abandoned jobs
 dashboard.start_schedule_tasks = Start schedule tasks
-dashboard.sync_branch.started = Branches Sync started
-dashboard.sync_tag.started = Tags Sync started
+dashboard.sync_branch.started = Branch sync started
+dashboard.sync_tag.started = Tag sync started
 dashboard.rebuild_issue_indexer = Rebuild issue indexer
 
 users.user_manage_panel = Manage user accounts
@@ -2979,7 +2974,7 @@ users.repos = Repos
 users.created = Created
 users.last_login = Last sign-in
 users.never_login = Never signed in
-users.send_register_notify = Send user registration notification
+users.send_register_notify = Notify about registration via email
 users.new_success = The user account "%s" has been created.
 users.edit = Edit
 users.auth_source = Authentication source
@@ -3663,8 +3658,7 @@ management = Manage secrets
 
 [actions]
 actions = Actions
-
-unit.desc = Manage actions
+unit.desc = Manage integrated CI/CD pipelines with Forgejo Actions
 
 status.unknown = Unknown
 status.waiting = Waiting

From 57956d0525cc8cb65858de8b096f39f51344e029 Mon Sep 17 00:00:00 2001
From: Cyborus <cyborus@cyborus.xyz>
Date: Tue, 30 Apr 2024 00:40:39 -0400
Subject: [PATCH 084/107] remove `MAINTAINERS` file

---
 MAINTAINERS | 61 -----------------------------------------------------
 1 file changed, 61 deletions(-)
 delete mode 100644 MAINTAINERS

diff --git a/MAINTAINERS b/MAINTAINERS
deleted file mode 100644
index 72171f80ed..0000000000
--- a/MAINTAINERS
+++ /dev/null
@@ -1,61 +0,0 @@
-Alexey Makhov <amakhov@avito.ru> (@makhov)
-Bo-Yi Wu <appleboy.tw@gmail.com> (@appleboy)
-Ethan Koenig <ethantkoenig@gmail.com> (@ethantkoenig)
-Kees de Vries <bouwko@gmail.com> (@Bwko)
-Kim Carlbäcker <kim.carlbacker@gmail.com> (@bkcsoft)
-LefsFlare <nobody@nobody.tld> (@LefsFlarey)
-Lunny Xiao <xiaolunwen@gmail.com> (@lunny)
-Rachid Zarouali <nobody@nobody.tld> (@xinity)
-Rémy Boulanouar <admin@dblk.org> (@DblK)
-Sandro Santilli <strk@kbt.io> (@strk)
-Thibault Meyer <meyer.thibault@gmail.com> (@0xbaadf00d)
-Thomas Boerger <thomas@webhippie.de> (@tboerger)
-Patrick G <geek1011@outlook.com> (@geek1011)
-Antoine Girard <sapk@sapk.fr> (@sapk)
-Lauris Bukšis-Haberkorns <lauris@nix.lv> (@lafriks)
-Jonas Östanbäck <jonas.ostanback@gmail.com> (@cez81)
-David Schneiderbauer <dschneiderbauer@gmail.com> (@daviian)
-Peter Žeby <morlinest@gmail.com> (@morlinest)
-Matti Ranta <techknowlogick@gitea.io> (@techknowlogick)
-Jonas Franz <info@jonasfranz.software> (@jonasfranz)
-Alexey Terentyev <axifnx@gmail.com> (@axifive)
-Lanre Adelowo <yo@lanre.wtf> (@adelowo)
-Konrad Langenberg <k@knt.li> (@kolaente)
-He-Long Zhang <outman99@hotmail.com> (@BetaCat0)
-Andrew Thornton <art27@cantab.net> (@zeripath)
-John Olheiser <john.olheiser@gmail.com> (@jolheiser)
-Richard Mahn <rich.mahn@unfoldingword.org> (@richmahn)
-Mrsdizzie <info@mrsdizzie.com> (@mrsdizzie)
-silverwind <me@silverwind.io> (@silverwind)
-Gary Kim <gary@garykim.dev> (@gary-kim)
-Guillermo Prandi <gitea.maint@mailfilter.com.ar> (@guillep2k)
-Mura Li <typeless@ctli.io> (@typeless)
-6543 <6543@obermui.de> (@6543)
-jaqra <jaqra@hotmail.com> (@jaqra)
-David Svantesson <davidsvantesson@gmail.com> (@davidsvantesson)
-a1012112796 <1012112796@qq.com> (@a1012112796)
-Karl Heinz Marbaise <kama@soebes.de> (@khmarbaise)
-Norwin Roosen <git@nroo.de> (@noerw)
-Kyle Dumont <kdumontnu@gmail.com> (@kdumontnu)
-Patrick Schratz <patrick.schratz@gmail.com> (@pat-s)
-Janis Estelmann <admin@oldschoolhack.me> (@KN4CK3R)
-Steven Kriegler <sk.bunsenbrenner@gmail.com> (@justusbunsi)
-Jimmy Praet <jimmy.praet@telenet.be> (@jpraet)
-Leon Hofmeister <dev.lh@web.de> (@delvh)
-Wim <wim@42.be> (@42wim)
-Jason Song <i@wolfogre.com> (@wolfogre)
-Yarden Shoham <git@yardenshoham.com> (@yardenshoham)
-Yu Tian <zettat123@gmail.com> (@Zettat123)
-Eddie Yang <576951401@qq.com> (@yp05327)
-Dong Ge <gedong_1994@163.com> (@sillyguodong)
-Xinyi Gong <hestergong@gmail.com> (@HesterG)
-wxiaoguang <wxiaoguang@gmail.com> (@wxiaoguang)
-Gary Moon <gary@garymoon.net> (@garymoon)
-Philip Peterson <philip.c.peterson@gmail.com> (@philip-peterson)
-Denys Konovalov <kontakt@denyskon.de> (@denyskon)
-Punit Inani <punitinani1@gmail.com> (@puni9869)
-CaiCandong  <1290147055@qq.com> (@caicandong)
-Rui Chen  <rui@chenrui.dev> (@chenrui333)
-Nanguan Lin <nanguanlin6@gmail.com> (@lng2020)
-kerwin612 <kerwin612@qq.com> (@kerwin612)
-Gary Wang <git@blumia.net> (@BLumia)

From 79380c209d78c258948f3b861be97bbf9c7d3974 Mon Sep 17 00:00:00 2001
From: oliverpool <git@olivier.pfad.fr>
Date: Tue, 30 Apr 2024 09:29:44 +0200
Subject: [PATCH 085/107] test: webhook fix branch filter tests

---
 models/fixtures/webhook.yml      |  3 +-
 services/webhook/webhook_test.go | 65 +++++++++++++++++++++-----------
 2 files changed, 45 insertions(+), 23 deletions(-)

diff --git a/models/fixtures/webhook.yml b/models/fixtures/webhook.yml
index f0b64cd57b..cab5c5aca0 100644
--- a/models/fixtures/webhook.yml
+++ b/models/fixtures/webhook.yml
@@ -29,8 +29,9 @@
 -
   id: 4
   repo_id: 2
+  type: gitea
   url: http://www.example.com/url4
   http_method: POST
   content_type: 1 # json
-  events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
+  events: '{"send_everything":true,"branch_filter":"{master,feature*}"}'
   is_active: false
diff --git a/services/webhook/webhook_test.go b/services/webhook/webhook_test.go
index 2436b5a4db..f8b66d46fc 100644
--- a/services/webhook/webhook_test.go
+++ b/services/webhook/webhook_test.go
@@ -4,6 +4,7 @@
 package webhook
 
 import (
+	"fmt"
 	"testing"
 
 	"code.gitea.io/gitea/models/db"
@@ -41,38 +42,58 @@ func TestPrepareWebhooks(t *testing.T) {
 	}
 }
 
+func eventType(p api.Payloader) webhook_module.HookEventType {
+	switch p.(type) {
+	case *api.CreatePayload:
+		return webhook_module.HookEventCreate
+	case *api.DeletePayload:
+		return webhook_module.HookEventDelete
+	case *api.PushPayload:
+		return webhook_module.HookEventPush
+	}
+	panic(fmt.Sprintf("no event type for payload %T", p))
+}
+
 func TestPrepareWebhooksBranchFilterMatch(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
-	activateWebhook(t, 4)
+	// branch_filter: {master,feature*}
+	w := unittest.AssertExistsAndLoadBean(t, &webhook_model.Webhook{ID: 4})
+	activateWebhook(t, w.ID)
 
-	hookTasks := []*webhook_model.HookTask{
-		{HookID: 4, EventType: webhook_module.HookEventPush},
-	}
-	for _, hookTask := range hookTasks {
-		unittest.AssertNotExistsBean(t, hookTask)
-	}
-	// this test also ensures that * doesn't handle / in any special way (like shell would)
-	assert.NoError(t, PrepareWebhooks(db.DefaultContext, EventSource{Repository: repo}, webhook_module.HookEventPush, &api.PushPayload{Ref: "refs/heads/feature/7791", Commits: []*api.PayloadCommit{{}}}))
-	for _, hookTask := range hookTasks {
-		unittest.AssertExistsAndLoadBean(t, hookTask)
+	for _, p := range []api.Payloader{
+		&api.PushPayload{Ref: "refs/heads/feature/7791"},
+		&api.CreatePayload{Ref: "refs/heads/feature/7791"}, // branch creation
+		&api.DeletePayload{Ref: "refs/heads/feature/7791"}, // branch deletion
+	} {
+		t.Run(fmt.Sprintf("%T", p), func(t *testing.T) {
+			db.DeleteBeans(db.DefaultContext, webhook_model.HookTask{HookID: w.ID})
+			typ := eventType(p)
+			assert.NoError(t, PrepareWebhook(db.DefaultContext, w, typ, p))
+			unittest.AssertExistsAndLoadBean(t, &webhook_model.HookTask{
+				HookID:    w.ID,
+				EventType: typ,
+			})
+		})
 	}
 }
 
 func TestPrepareWebhooksBranchFilterNoMatch(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
-	hookTasks := []*webhook_model.HookTask{
-		{HookID: 4, EventType: webhook_module.HookEventPush},
-	}
-	for _, hookTask := range hookTasks {
-		unittest.AssertNotExistsBean(t, hookTask)
-	}
-	assert.NoError(t, PrepareWebhooks(db.DefaultContext, EventSource{Repository: repo}, webhook_module.HookEventPush, &api.PushPayload{Ref: "refs/heads/fix_weird_bug"}))
+	// branch_filter: {master,feature*}
+	w := unittest.AssertExistsAndLoadBean(t, &webhook_model.Webhook{ID: 4})
+	activateWebhook(t, w.ID)
 
-	for _, hookTask := range hookTasks {
-		unittest.AssertNotExistsBean(t, hookTask)
+	for _, p := range []api.Payloader{
+		&api.PushPayload{Ref: "refs/heads/fix_weird_bug"},
+		&api.CreatePayload{Ref: "refs/heads/fix_weird_bug"}, // branch creation
+		&api.DeletePayload{Ref: "refs/heads/fix_weird_bug"}, // branch deletion
+	} {
+		t.Run(fmt.Sprintf("%T", p), func(t *testing.T) {
+			db.DeleteBeans(db.DefaultContext, webhook_model.HookTask{HookID: w.ID})
+			assert.NoError(t, PrepareWebhook(db.DefaultContext, w, eventType(p), p))
+			unittest.AssertNotExistsBean(t, &webhook_model.HookTask{HookID: w.ID})
+		})
 	}
 }

From df06904f4ac65dea87534dd33db7554b5373b44e Mon Sep 17 00:00:00 2001
From: oliverpool <git@olivier.pfad.fr>
Date: Tue, 30 Apr 2024 09:30:29 +0200
Subject: [PATCH 086/107] webhook: fix getPayloadBranch

---
 services/webhook/webhook.go | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go
index cf4f2fdfd2..1366ea8e8f 100644
--- a/services/webhook/webhook.go
+++ b/services/webhook/webhook.go
@@ -82,19 +82,17 @@ var hookQueue *queue.WorkerPoolQueue[int64]
 
 // getPayloadBranch returns branch for hook event, if applicable.
 func getPayloadBranch(p api.Payloader) string {
+	var ref string
 	switch pp := p.(type) {
 	case *api.CreatePayload:
-		if pp.RefType == "branch" {
-			return pp.Ref
-		}
+		ref = pp.Ref
 	case *api.DeletePayload:
-		if pp.RefType == "branch" {
-			return pp.Ref
-		}
+		ref = pp.Ref
 	case *api.PushPayload:
-		if strings.HasPrefix(pp.Ref, git.BranchPrefix) {
-			return pp.Ref[len(git.BranchPrefix):]
-		}
+		ref = pp.Ref
+	}
+	if strings.HasPrefix(ref, git.BranchPrefix) {
+		return ref[len(git.BranchPrefix):]
 	}
 	return ""
 }

From cb0f3611718593eec8239bfbccdc7511325cb611 Mon Sep 17 00:00:00 2001
From: oliverpool <git@olivier.pfad.fr>
Date: Tue, 30 Apr 2024 09:55:54 +0200
Subject: [PATCH 087/107] test: webhook gitea tag creation ref

---
 services/webhook/default_test.go | 224 +++++++++++++++++++++++++++++++
 1 file changed, 224 insertions(+)
 create mode 100644 services/webhook/default_test.go

diff --git a/services/webhook/default_test.go b/services/webhook/default_test.go
new file mode 100644
index 0000000000..29f1521cca
--- /dev/null
+++ b/services/webhook/default_test.go
@@ -0,0 +1,224 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package webhook
+
+import (
+	"context"
+	"testing"
+
+	webhook_model "code.gitea.io/gitea/models/webhook"
+	"code.gitea.io/gitea/modules/json"
+	webhook_module "code.gitea.io/gitea/modules/webhook"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestGiteaPayload(t *testing.T) {
+	dh := defaultHandler{
+		forgejo: false,
+	}
+	hook := &webhook_model.Webhook{
+		RepoID:      3,
+		IsActive:    true,
+		Type:        webhook_module.GITEA,
+		URL:         "https://gitea.example.com/",
+		Meta:        ``,
+		HTTPMethod:  "POST",
+		ContentType: webhook_model.ContentTypeJSON,
+	}
+
+	// Woodpecker expects the ref to be short on tag creation only
+	// https://github.com/woodpecker-ci/woodpecker/blob/00ccec078cdced80cf309cd4da460a5041d7991a/server/forge/gitea/helper.go#L134
+	// see https://codeberg.org/codeberg/community/issues/1556
+	t.Run("Create", func(t *testing.T) {
+		p := createTestPayload()
+		data, err := p.JSONPayload()
+		require.NoError(t, err)
+
+		task := &webhook_model.HookTask{
+			HookID:         hook.ID,
+			EventType:      webhook_module.HookEventCreate,
+			PayloadContent: string(data),
+			PayloadVersion: 2,
+		}
+
+		req, reqBody, err := dh.NewRequest(context.Background(), hook, task)
+		require.NoError(t, err)
+		require.NotNil(t, req)
+		require.NotNil(t, reqBody)
+
+		assert.Equal(t, "POST", req.Method)
+		assert.Equal(t, "https://gitea.example.com/", req.URL.String())
+		assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
+		assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+		var body struct {
+			Ref string `json:"ref"`
+		}
+		err = json.NewDecoder(req.Body).Decode(&body)
+		assert.NoError(t, err)
+		assert.Equal(t, "test", body.Ref) // short ref
+	})
+
+	t.Run("Push", func(t *testing.T) {
+		p := pushTestPayload()
+		data, err := p.JSONPayload()
+		require.NoError(t, err)
+
+		task := &webhook_model.HookTask{
+			HookID:         hook.ID,
+			EventType:      webhook_module.HookEventPush,
+			PayloadContent: string(data),
+			PayloadVersion: 2,
+		}
+
+		req, reqBody, err := dh.NewRequest(context.Background(), hook, task)
+		require.NoError(t, err)
+		require.NotNil(t, req)
+		require.NotNil(t, reqBody)
+
+		assert.Equal(t, "POST", req.Method)
+		assert.Equal(t, "https://gitea.example.com/", req.URL.String())
+		assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
+		assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+		var body struct {
+			Ref string `json:"ref"`
+		}
+		err = json.NewDecoder(req.Body).Decode(&body)
+		assert.NoError(t, err)
+		assert.Equal(t, "refs/heads/test", body.Ref) // full ref
+	})
+
+	t.Run("Delete", func(t *testing.T) {
+		p := deleteTestPayload()
+		data, err := p.JSONPayload()
+		require.NoError(t, err)
+
+		task := &webhook_model.HookTask{
+			HookID:         hook.ID,
+			EventType:      webhook_module.HookEventDelete,
+			PayloadContent: string(data),
+			PayloadVersion: 2,
+		}
+
+		req, reqBody, err := dh.NewRequest(context.Background(), hook, task)
+		require.NoError(t, err)
+		require.NotNil(t, req)
+		require.NotNil(t, reqBody)
+
+		assert.Equal(t, "POST", req.Method)
+		assert.Equal(t, "https://gitea.example.com/", req.URL.String())
+		assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
+		assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+		var body struct {
+			Ref string `json:"ref"`
+		}
+		err = json.NewDecoder(req.Body).Decode(&body)
+		assert.NoError(t, err)
+		assert.Equal(t, "test", body.Ref) // short ref
+	})
+}
+
+func TestForgejoPayload(t *testing.T) {
+	dh := defaultHandler{
+		forgejo: true,
+	}
+	hook := &webhook_model.Webhook{
+		RepoID:      3,
+		IsActive:    true,
+		Type:        webhook_module.FORGEJO,
+		URL:         "https://forgejo.example.com/",
+		Meta:        ``,
+		HTTPMethod:  "POST",
+		ContentType: webhook_model.ContentTypeJSON,
+	}
+
+	// always return the full ref for consistency
+	t.Run("Create", func(t *testing.T) {
+		p := createTestPayload()
+		data, err := p.JSONPayload()
+		require.NoError(t, err)
+
+		task := &webhook_model.HookTask{
+			HookID:         hook.ID,
+			EventType:      webhook_module.HookEventCreate,
+			PayloadContent: string(data),
+			PayloadVersion: 2,
+		}
+
+		req, reqBody, err := dh.NewRequest(context.Background(), hook, task)
+		require.NoError(t, err)
+		require.NotNil(t, req)
+		require.NotNil(t, reqBody)
+
+		assert.Equal(t, "POST", req.Method)
+		assert.Equal(t, "https://forgejo.example.com/", req.URL.String())
+		assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
+		assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+		var body struct {
+			Ref string `json:"ref"`
+		}
+		err = json.NewDecoder(req.Body).Decode(&body)
+		assert.NoError(t, err)
+		assert.Equal(t, "refs/heads/test", body.Ref) // full ref
+	})
+
+	t.Run("Push", func(t *testing.T) {
+		p := pushTestPayload()
+		data, err := p.JSONPayload()
+		require.NoError(t, err)
+
+		task := &webhook_model.HookTask{
+			HookID:         hook.ID,
+			EventType:      webhook_module.HookEventPush,
+			PayloadContent: string(data),
+			PayloadVersion: 2,
+		}
+
+		req, reqBody, err := dh.NewRequest(context.Background(), hook, task)
+		require.NoError(t, err)
+		require.NotNil(t, req)
+		require.NotNil(t, reqBody)
+
+		assert.Equal(t, "POST", req.Method)
+		assert.Equal(t, "https://forgejo.example.com/", req.URL.String())
+		assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
+		assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+		var body struct {
+			Ref string `json:"ref"`
+		}
+		err = json.NewDecoder(req.Body).Decode(&body)
+		assert.NoError(t, err)
+		assert.Equal(t, "refs/heads/test", body.Ref) // full ref
+	})
+
+	t.Run("Delete", func(t *testing.T) {
+		p := deleteTestPayload()
+		data, err := p.JSONPayload()
+		require.NoError(t, err)
+
+		task := &webhook_model.HookTask{
+			HookID:         hook.ID,
+			EventType:      webhook_module.HookEventDelete,
+			PayloadContent: string(data),
+			PayloadVersion: 2,
+		}
+
+		req, reqBody, err := dh.NewRequest(context.Background(), hook, task)
+		require.NoError(t, err)
+		require.NotNil(t, req)
+		require.NotNil(t, reqBody)
+
+		assert.Equal(t, "POST", req.Method)
+		assert.Equal(t, "https://forgejo.example.com/", req.URL.String())
+		assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256"))
+		assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+		var body struct {
+			Ref string `json:"ref"`
+		}
+		err = json.NewDecoder(req.Body).Decode(&body)
+		assert.NoError(t, err)
+		assert.Equal(t, "refs/heads/test", body.Ref) // full ref
+	})
+}

From 0d3a9e64918ae5f91e8b4b501742f01ed71f9586 Mon Sep 17 00:00:00 2001
From: oliverpool <git@olivier.pfad.fr>
Date: Tue, 30 Apr 2024 10:18:02 +0200
Subject: [PATCH 088/107] webhook: send short ref on gitea create/delete
 payload

---
 services/webhook/default.go | 42 +++++++++++++++++++++++++++++++------
 1 file changed, 36 insertions(+), 6 deletions(-)

diff --git a/services/webhook/default.go b/services/webhook/default.go
index 314f539648..ea877698c8 100644
--- a/services/webhook/default.go
+++ b/services/webhook/default.go
@@ -12,6 +12,8 @@ import (
 	"strings"
 
 	webhook_model "code.gitea.io/gitea/models/webhook"
+	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/svg"
 	webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -67,6 +69,18 @@ func (defaultHandler) UnmarshalForm(bind func(any)) forms.WebhookForm {
 }
 
 func (defaultHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (req *http.Request, body []byte, err error) {
+	payloadContent := t.PayloadContent
+	if w.Type == webhook_module.GITEA &&
+		(t.EventType == webhook_module.HookEventCreate || t.EventType == webhook_module.HookEventDelete) {
+		// Woodpecker expects the ref to be short on tag creation only
+		// https://github.com/woodpecker-ci/woodpecker/blob/00ccec078cdced80cf309cd4da460a5041d7991a/server/forge/gitea/helper.go#L134
+		// see https://codeberg.org/codeberg/community/issues/1556
+		payloadContent, err = substituteRefShortName(payloadContent)
+		if err != nil {
+			return nil, nil, fmt.Errorf("could not substiture ref: %w", err)
+		}
+	}
+
 	switch w.HTTPMethod {
 	case "":
 		log.Info("HTTP Method for %s webhook %s [ID: %d] is not set, defaulting to POST", w.Type, w.URL, w.ID)
@@ -74,7 +88,7 @@ func (defaultHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook,
 	case http.MethodPost:
 		switch w.ContentType {
 		case webhook_model.ContentTypeJSON:
-			req, err = http.NewRequest("POST", w.URL, strings.NewReader(t.PayloadContent))
+			req, err = http.NewRequest("POST", w.URL, strings.NewReader(payloadContent))
 			if err != nil {
 				return nil, nil, err
 			}
@@ -82,7 +96,7 @@ func (defaultHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook,
 			req.Header.Set("Content-Type", "application/json")
 		case webhook_model.ContentTypeForm:
 			forms := url.Values{
-				"payload": []string{t.PayloadContent},
+				"payload": []string{payloadContent},
 			}
 
 			req, err = http.NewRequest("POST", w.URL, strings.NewReader(forms.Encode()))
@@ -100,7 +114,7 @@ func (defaultHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook,
 			return nil, nil, fmt.Errorf("invalid URL: %w", err)
 		}
 		vals := u.Query()
-		vals["payload"] = []string{t.PayloadContent}
+		vals["payload"] = []string{payloadContent}
 		u.RawQuery = vals.Encode()
 		req, err = http.NewRequest("GET", u.String(), nil)
 		if err != nil {
@@ -109,12 +123,12 @@ func (defaultHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook,
 	case http.MethodPut:
 		switch w.Type {
 		case webhook_module.MATRIX: // used when t.Version == 1
-			txnID, err := getMatrixTxnID([]byte(t.PayloadContent))
+			txnID, err := getMatrixTxnID([]byte(payloadContent))
 			if err != nil {
 				return nil, nil, err
 			}
 			url := fmt.Sprintf("%s/%s", w.URL, url.PathEscape(txnID))
-			req, err = http.NewRequest("PUT", url, strings.NewReader(t.PayloadContent))
+			req, err = http.NewRequest("PUT", url, strings.NewReader(payloadContent))
 			if err != nil {
 				return nil, nil, err
 			}
@@ -125,6 +139,22 @@ func (defaultHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook,
 		return nil, nil, fmt.Errorf("invalid http method: %v", w.HTTPMethod)
 	}
 
-	body = []byte(t.PayloadContent)
+	body = []byte(payloadContent)
 	return req, body, shared.AddDefaultHeaders(req, []byte(w.Secret), t, body)
 }
+
+func substituteRefShortName(body string) (string, error) {
+	var m map[string]any
+	if err := json.Unmarshal([]byte(body), &m); err != nil {
+		return body, err
+	}
+	ref, ok := m["ref"].(string)
+	if !ok {
+		return body, fmt.Errorf("expected string 'ref', got %T", m["ref"])
+	}
+
+	m["ref"] = git.RefName(ref).ShortName()
+
+	buf, err := json.Marshal(m)
+	return string(buf), err
+}

From aeb544aff73430bdb475616468f13ecad6efa463 Mon Sep 17 00:00:00 2001
From: Beowulf <beowulf@beocode.eu>
Date: Sun, 28 Apr 2024 21:24:46 +0200
Subject: [PATCH 089/107] added test for reading inline attachments

---
 services/mailer/incoming/incoming.go      |  7 +++-
 services/mailer/incoming/incoming_test.go | 40 +++++++++++++++++++++++
 2 files changed, 46 insertions(+), 1 deletion(-)

diff --git a/services/mailer/incoming/incoming.go b/services/mailer/incoming/incoming.go
index 555cdfee8b..6530b7cc60 100644
--- a/services/mailer/incoming/incoming.go
+++ b/services/mailer/incoming/incoming.go
@@ -219,7 +219,7 @@ loop:
 			}
 
 			err := func() error {
-				if handledSet.Contains(msg.SeqNum) {
+				if isAlreadyHandled(handledSet, msg) {
 					log.Debug("Skipping already handled message")
 					return nil
 				}
@@ -282,6 +282,11 @@ loop:
 	return nil
 }
 
+// isAlreadyHandled tests if the message was already handled
+func isAlreadyHandled(handledSet *imap.SeqSet, msg *imap.Message) bool {
+	return handledSet.Contains(msg.SeqNum)
+}
+
 // isAutomaticReply tests if the headers indicate an automatic reply
 func isAutomaticReply(env *enmime.Envelope) bool {
 	autoSubmitted := env.GetHeader("Auto-Submitted")
diff --git a/services/mailer/incoming/incoming_test.go b/services/mailer/incoming/incoming_test.go
index 5d84848e3f..f2bb7fc498 100644
--- a/services/mailer/incoming/incoming_test.go
+++ b/services/mailer/incoming/incoming_test.go
@@ -7,10 +7,24 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/emersion/go-imap"
 	"github.com/jhillyerd/enmime"
 	"github.com/stretchr/testify/assert"
 )
 
+func TestNotHandleTwice(t *testing.T) {
+	handledSet := new(imap.SeqSet)
+	msg := imap.NewMessage(90, []imap.FetchItem{imap.FetchBody})
+
+	handled := isAlreadyHandled(handledSet, msg)
+	assert.Equal(t, false, handled)
+
+	handledSet.AddNum(msg.SeqNum)
+
+	handled = isAlreadyHandled(handledSet, msg)
+	assert.Equal(t, true, handled)
+}
+
 func TestIsAutomaticReply(t *testing.T) {
 	cases := []struct {
 		Headers  map[string]string
@@ -95,6 +109,32 @@ func TestGetContentFromMailReader(t *testing.T) {
 	assert.Equal(t, "attachment.txt", content.Attachments[0].Name)
 	assert.Equal(t, []byte("attachment content"), content.Attachments[0].Content)
 
+	mailString = "Content-Type: multipart/mixed; boundary=message-boundary\r\n" +
+		"\r\n" +
+		"--message-boundary\r\n" +
+		"Content-Type: multipart/alternative; boundary=text-boundary\r\n" +
+		"\r\n" +
+		"--text-boundary\r\n" +
+		"Content-Type: text/plain\r\n" +
+		"Content-Disposition: inline\r\n" +
+		"\r\n" +
+		"mail content\r\n" +
+		"--text-boundary--\r\n" +
+		"--message-boundary\r\n" +
+		"Content-Type: text/plain\r\n" +
+		"Content-Disposition: inline; filename=attachment.txt\r\n" +
+		"\r\n" +
+		"attachment content\r\n" +
+		"--message-boundary--\r\n"
+
+	env, err = enmime.ReadEnvelope(strings.NewReader(mailString))
+	assert.NoError(t, err)
+	content = getContentFromMailReader(env)
+	assert.Equal(t, "mail content", content.Content)
+	assert.Len(t, content.Attachments, 1)
+	assert.Equal(t, "attachment.txt", content.Attachments[0].Name)
+	assert.Equal(t, []byte("attachment content"), content.Attachments[0].Content)
+
 	mailString = "Content-Type: multipart/mixed; boundary=message-boundary\r\n" +
 		"\r\n" +
 		"--message-boundary\r\n" +

From 34134df3a7b9457f091e2e6fc18bdb0cd7dbffb9 Mon Sep 17 00:00:00 2001
From: Beowulf <beowulf@beocode.eu>
Date: Tue, 30 Apr 2024 12:05:22 +0200
Subject: [PATCH 090/107] added release notes

---
 release-notes/8.0.0/fix/3504.md | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 release-notes/8.0.0/fix/3504.md

diff --git a/release-notes/8.0.0/fix/3504.md b/release-notes/8.0.0/fix/3504.md
new file mode 100644
index 0000000000..8ece1557ab
--- /dev/null
+++ b/release-notes/8.0.0/fix/3504.md
@@ -0,0 +1 @@
+Fixed that inline attachments of emails (as they occur for example with Apple Mail) are not attached to comments.

From 9a01062ae248327e62c478fd2ec5bd7518ea4f0b Mon Sep 17 00:00:00 2001
From: Gergely Nagy <forgejo@gergo.csillger.hu>
Date: Tue, 30 Apr 2024 12:51:30 +0200
Subject: [PATCH 091/107] Fix user mention processing

When mentioning a user, the markup post-processor did not handle the
case where the mentioned user did not exist well: it tried to skip to
the next node, which in turn, ended up skipping the rest of the line.

To fix this, lets skip just the mentioned, but non-existing user, and
continue processing the current node from there.

Fixes #3535.

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
---
 modules/markup/html.go                | 4 ++--
 modules/templates/util_render_test.go | 5 +++++
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/modules/markup/html.go b/modules/markup/html.go
index 7a5ca14aeb..56f63b8a76 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -623,10 +623,10 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
 		if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) {
 			replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(ctx.Links.Prefix(), mentionedUsername), mention, "mention"))
 			node = node.NextSibling.NextSibling
+			start = 0
 		} else {
-			node = node.NextSibling
+			start = loc.End
 		}
-		start = 0
 	}
 }
 
diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go
index c1d5e26f62..ea01612ac3 100644
--- a/modules/templates/util_render_test.go
+++ b/modules/templates/util_render_test.go
@@ -50,6 +50,11 @@ func TestApostrophesInMentions(t *testing.T) {
 	assert.EqualValues(t, template.HTML("<p><a href=\"/mention-user\" rel=\"nofollow\">@mention-user</a>&#39;s comment</p>\n"), rendered)
 }
 
+func TestNonExistantUserMention(t *testing.T) {
+	rendered := RenderMarkdownToHtml(context.Background(), "@ThisUserDoesNotExist @mention-user")
+	assert.EqualValues(t, template.HTML("<p>@ThisUserDoesNotExist <a href=\"/mention-user\" rel=\"nofollow\">@mention-user</a></p>\n"), rendered)
+}
+
 func TestRenderCommitBody(t *testing.T) {
 	type args struct {
 		ctx   context.Context

From 0d029ebe6d7ed609d7521f7e6b1c09486a4fef55 Mon Sep 17 00:00:00 2001
From: Gergely Nagy <forgejo@gergo.csillger.hu>
Date: Tue, 30 Apr 2024 20:53:47 +0200
Subject: [PATCH 092/107] Fix git_model.FindBranchesByRepoAndBranchName

When a logged in user with no repositories visits their dashboard, it will
display a search box that lists their own repositories.

This is served by the `repo.SearchRepos` handler, which in turn calls
`commitstatus_service.FindReposLastestCommitStatuses()` with an empty
repo list.

That, in turn, will call `git_model.FindBranchesByRepoAndBranchName()`,
with an empty map. With no map, `FindBranchesByRepoAndBranchName()` ends
up querying the entire `branch` table, because no conditions were set
up.

Armed with a gazillion repo & commit shas, we return to
`FindReposLastestCommitStatuses`, and promptly call
`git_model.GetLatestCommitStatusForPairs`, which constructs a monstrous
query with so many placeholders that the database tells us to go
somewhere else, and flips us off. At least on instances the size of
Codeberg. On smaller instances, it will eventually return, and throw
away all the data, and return an empty set, having performed all this
for naught.

We fix this by short-circuiting `FindBranchesByRepoAndBranchName`, and
returning fast if our inputs are empty.

A test case is included.

Fixes #3521.

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
---
 models/git/branch_list.go       | 3 +++
 models/git/branch_test.go       | 9 +++++++++
 release-notes/8.0.0/fix/3570.md | 1 +
 3 files changed, 13 insertions(+)
 create mode 100644 release-notes/8.0.0/fix/3570.md

diff --git a/models/git/branch_list.go b/models/git/branch_list.go
index 980bd7b4c9..493611f217 100644
--- a/models/git/branch_list.go
+++ b/models/git/branch_list.go
@@ -116,6 +116,9 @@ func FindBranchNames(ctx context.Context, opts FindBranchOptions) ([]string, err
 }
 
 func FindBranchesByRepoAndBranchName(ctx context.Context, repoBranches map[int64]string) (map[int64]string, error) {
+	if len(repoBranches) == 0 {
+		return nil, nil
+	}
 	cond := builder.NewCond()
 	for repoID, branchName := range repoBranches {
 		cond = cond.Or(builder.And(builder.Eq{"repo_id": repoID}, builder.Eq{"name": branchName}))
diff --git a/models/git/branch_test.go b/models/git/branch_test.go
index b8ea663e81..3aa578f44b 100644
--- a/models/git/branch_test.go
+++ b/models/git/branch_test.go
@@ -183,3 +183,12 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) {
 	assert.NoError(t, err)
 	assert.NotNil(t, deletedBranch)
 }
+
+func TestFindBranchesByRepoAndBranchName(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+
+	// With no repos or branches given, we find no branches.
+	branches, err := git_model.FindBranchesByRepoAndBranchName(db.DefaultContext, map[int64]string{})
+	assert.NoError(t, err)
+	assert.Len(t, branches, 0)
+}
diff --git a/release-notes/8.0.0/fix/3570.md b/release-notes/8.0.0/fix/3570.md
new file mode 100644
index 0000000000..1e860eb958
--- /dev/null
+++ b/release-notes/8.0.0/fix/3570.md
@@ -0,0 +1 @@
+Fixed an issue that resulted in excessive and unnecessary database queries when a user with no repositories was viewing their dashboard.

From 33cd8446d3675796939c7204e31814c0c0bbcbab Mon Sep 17 00:00:00 2001
From: Gergely Nagy <forgejo@gergo.csillger.hu>
Date: Tue, 30 Apr 2024 21:32:23 +0200
Subject: [PATCH 093/107] Performance improvement for
 FindReposLastestCommitStatuses

If `commitstatus_service.FindReposLastestCommitStatuses` receives no
repos in its params, short-circuit, and return early, without performing
any potentially expensive work.

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
---
 services/repository/commitstatus/commitstatus.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/services/repository/commitstatus/commitstatus.go b/services/repository/commitstatus/commitstatus.go
index bd40a75dc8..1944f11f03 100644
--- a/services/repository/commitstatus/commitstatus.go
+++ b/services/repository/commitstatus/commitstatus.go
@@ -127,6 +127,9 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
 
 // FindReposLastestCommitStatuses loading repository default branch latest combinded commit status with cache
 func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) {
+	if len(repos) == 0 {
+		return nil, nil
+	}
 	results := make([]*git_model.CommitStatus, len(repos))
 	for i, repo := range repos {
 		if cv := getCommitStatusCache(repo.ID, repo.DefaultBranch); cv != nil {

From 15978d4d07a9f4f0e047a8c02f5436cabe854db3 Mon Sep 17 00:00:00 2001
From: Renovate Bot <forgejo-renovate-action@forgejo.org>
Date: Wed, 1 May 2024 06:02:32 +0000
Subject: [PATCH 094/107] Update ghcr.io/visualon/renovate Docker tag to
 v37.330.1

---
 .forgejo/workflows/renovate.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml
index 6212b8ef0b..6ce7141004 100644
--- a/.forgejo/workflows/renovate.yml
+++ b/.forgejo/workflows/renovate.yml
@@ -22,7 +22,7 @@ jobs:
 
     runs-on: docker
     container:
-      image: ghcr.io/visualon/renovate:37.323.3
+      image: ghcr.io/visualon/renovate:37.330.1
 
     steps:
       - name: Load renovate repo cache

From 9a581e70a1b1e92ae851127e5b75d827a93e9401 Mon Sep 17 00:00:00 2001
From: Renovate Bot <forgejo-renovate-action@forgejo.org>
Date: Wed, 1 May 2024 06:03:44 +0000
Subject: [PATCH 095/107] Update dependency vitest to v1.5.3

---
 package-lock.json | 66 +++++++++++++++++++++++------------------------
 package.json      |  2 +-
 2 files changed, 34 insertions(+), 34 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 8f7021caec..74500ac6e6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -96,7 +96,7 @@
         "svgo": "3.2.0",
         "updates": "16.0.1",
         "vite-string-plugin": "1.2.0",
-        "vitest": "1.5.2"
+        "vitest": "1.5.3"
       },
       "engines": {
         "node": ">= 18.0.0"
@@ -2545,13 +2545,13 @@
       }
     },
     "node_modules/@vitest/expect": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.2.tgz",
-      "integrity": "sha512-rf7MTD1WCoDlN3FfYJ9Llfp0PbdtOMZ3FIF0AVkDnKbp3oiMW1c8AmvRZBcqbAhDUAvF52e9zx4WQM1r3oraVA==",
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.3.tgz",
+      "integrity": "sha512-y+waPz31pOFr3rD7vWTbwiLe5+MgsMm40jTZbQE8p8/qXyBX3CQsIXRx9XK12IbY7q/t5a5aM/ckt33b4PxK2g==",
       "dev": true,
       "dependencies": {
-        "@vitest/spy": "1.5.2",
-        "@vitest/utils": "1.5.2",
+        "@vitest/spy": "1.5.3",
+        "@vitest/utils": "1.5.3",
         "chai": "^4.3.10"
       },
       "funding": {
@@ -2559,12 +2559,12 @@
       }
     },
     "node_modules/@vitest/runner": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.2.tgz",
-      "integrity": "sha512-7IJ7sJhMZrqx7HIEpv3WrMYcq8ZNz9L6alo81Y6f8hV5mIE6yVZsFoivLZmr0D777klm1ReqonE9LyChdcmw6g==",
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.3.tgz",
+      "integrity": "sha512-7PlfuReN8692IKQIdCxwir1AOaP5THfNkp0Uc4BKr2na+9lALNit7ub9l3/R7MP8aV61+mHKRGiqEKRIwu6iiQ==",
       "dev": true,
       "dependencies": {
-        "@vitest/utils": "1.5.2",
+        "@vitest/utils": "1.5.3",
         "p-limit": "^5.0.0",
         "pathe": "^1.1.1"
       },
@@ -2600,9 +2600,9 @@
       }
     },
     "node_modules/@vitest/snapshot": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.2.tgz",
-      "integrity": "sha512-CTEp/lTYos8fuCc9+Z55Ga5NVPKUgExritjF5VY7heRFUfheoAqBneUlvXSUJHUZPjnPmyZA96yLRJDP1QATFQ==",
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.3.tgz",
+      "integrity": "sha512-K3mvIsjyKYBhNIDujMD2gfQEzddLe51nNOAf45yKRt/QFJcUIeTQd2trRvv6M6oCBHNVnZwFWbQ4yj96ibiDsA==",
       "dev": true,
       "dependencies": {
         "magic-string": "^0.30.5",
@@ -2626,9 +2626,9 @@
       }
     },
     "node_modules/@vitest/spy": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.2.tgz",
-      "integrity": "sha512-xCcPvI8JpCtgikT9nLpHPL1/81AYqZy1GCy4+MCHBE7xi8jgsYkULpW5hrx5PGLgOQjUpb6fd15lqcriJ40tfQ==",
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.3.tgz",
+      "integrity": "sha512-Llj7Jgs6lbnL55WoshJUUacdJfjU2honvGcAJBxhra5TPEzTJH8ZuhI3p/JwqqfnTr4PmP7nDmOXP53MS7GJlg==",
       "dev": true,
       "dependencies": {
         "tinyspy": "^2.2.0"
@@ -2638,9 +2638,9 @@
       }
     },
     "node_modules/@vitest/utils": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.2.tgz",
-      "integrity": "sha512-sWOmyofuXLJ85VvXNsroZur7mOJGiQeM0JN3/0D1uU8U9bGFM69X1iqHaRXl6R8BwaLY6yPCogP257zxTzkUdA==",
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.3.tgz",
+      "integrity": "sha512-rE9DTN1BRhzkzqNQO+kw8ZgfeEBCLXiHJwetk668shmNBpSagQxneT5eSqEBLP+cqSiAeecvQmbpFfdMyLcIQA==",
       "dev": true,
       "dependencies": {
         "diff-sequences": "^29.6.3",
@@ -12257,9 +12257,9 @@
       }
     },
     "node_modules/vite-node": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.2.tgz",
-      "integrity": "sha512-Y8p91kz9zU+bWtF7HGt6DVw2JbhyuB2RlZix3FPYAYmUyZ3n7iTp8eSyLyY6sxtPegvxQtmlTMhfPhUfCUF93A==",
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.3.tgz",
+      "integrity": "sha512-axFo00qiCpU/JLd8N1gu9iEYL3xTbMbMrbe5nDp9GL0nb6gurIdZLkkFogZXWnE8Oyy5kfSLwNVIcVsnhE7lgQ==",
       "dev": true,
       "dependencies": {
         "cac": "^6.7.14",
@@ -12339,16 +12339,16 @@
       }
     },
     "node_modules/vitest": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.2.tgz",
-      "integrity": "sha512-l9gwIkq16ug3xY7BxHwcBQovLZG75zZL0PlsiYQbf76Rz6QGs54416UWMtC0jXeihvHvcHrf2ROEjkQRVpoZYw==",
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.3.tgz",
+      "integrity": "sha512-2oM7nLXylw3mQlW6GXnRriw+7YvZFk/YNV8AxIC3Z3MfFbuziLGWP9GPxxu/7nRlXhqyxBikpamr+lEEj1sUEw==",
       "dev": true,
       "dependencies": {
-        "@vitest/expect": "1.5.2",
-        "@vitest/runner": "1.5.2",
-        "@vitest/snapshot": "1.5.2",
-        "@vitest/spy": "1.5.2",
-        "@vitest/utils": "1.5.2",
+        "@vitest/expect": "1.5.3",
+        "@vitest/runner": "1.5.3",
+        "@vitest/snapshot": "1.5.3",
+        "@vitest/spy": "1.5.3",
+        "@vitest/utils": "1.5.3",
         "acorn-walk": "^8.3.2",
         "chai": "^4.3.10",
         "debug": "^4.3.4",
@@ -12362,7 +12362,7 @@
         "tinybench": "^2.5.1",
         "tinypool": "^0.8.3",
         "vite": "^5.0.0",
-        "vite-node": "1.5.2",
+        "vite-node": "1.5.3",
         "why-is-node-running": "^2.2.2"
       },
       "bin": {
@@ -12377,8 +12377,8 @@
       "peerDependencies": {
         "@edge-runtime/vm": "*",
         "@types/node": "^18.0.0 || >=20.0.0",
-        "@vitest/browser": "1.5.2",
-        "@vitest/ui": "1.5.2",
+        "@vitest/browser": "1.5.3",
+        "@vitest/ui": "1.5.3",
         "happy-dom": "*",
         "jsdom": "*"
       },
diff --git a/package.json b/package.json
index aa233959b9..0de030a3ef 100644
--- a/package.json
+++ b/package.json
@@ -95,7 +95,7 @@
     "svgo": "3.2.0",
     "updates": "16.0.1",
     "vite-string-plugin": "1.2.0",
-    "vitest": "1.5.2"
+    "vitest": "1.5.3"
   },
   "browserslist": ["defaults"]
 }

From bb5395cd29c1772964585938de0293e41462fd28 Mon Sep 17 00:00:00 2001
From: Renovate Bot <forgejo-renovate-action@forgejo.org>
Date: Mon, 29 Apr 2024 10:03:56 +0000
Subject: [PATCH 096/107] Update dependency vue to v3.4.26

---
 package-lock.json | 106 +++++++++++++++++++++++-----------------------
 package.json      |   2 +-
 2 files changed, 54 insertions(+), 54 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 74500ac6e6..38d8689f16 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -54,7 +54,7 @@
         "tributejs": "5.1.3",
         "uint8-to-base64": "0.2.0",
         "vanilla-colorful": "0.7.2",
-        "vue": "3.4.24",
+        "vue": "3.4.26",
         "vue-bar-graph": "2.0.0",
         "vue-chartjs": "5.3.1",
         "vue-loader": "17.4.2",
@@ -2668,36 +2668,36 @@
       }
     },
     "node_modules/@vue/compiler-core": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.24.tgz",
-      "integrity": "sha512-vbW/tgbwJYj62N/Ww99x0zhFTkZDTcGh3uwJEuadZ/nF9/xuFMC4693P9r+3sxGXISABpDKvffY5ApH9pmdd1A==",
+      "version": "3.4.26",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.26.tgz",
+      "integrity": "sha512-N9Vil6Hvw7NaiyFUFBPXrAyETIGlQ8KcFMkyk6hW1Cl6NvoqvP+Y8p1Eqvx+UdqsnrnI9+HMUEJegzia3mhXmQ==",
       "dependencies": {
         "@babel/parser": "^7.24.4",
-        "@vue/shared": "3.4.24",
+        "@vue/shared": "3.4.26",
         "entities": "^4.5.0",
         "estree-walker": "^2.0.2",
         "source-map-js": "^1.2.0"
       }
     },
     "node_modules/@vue/compiler-dom": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.24.tgz",
-      "integrity": "sha512-4XgABML/4cNndVsQndG6BbGN7+EoisDwi3oXNovqL/4jdNhwvP8/rfRMTb6FxkxIxUUtg6AI1/qZvwfSjxJiWA==",
+      "version": "3.4.26",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.26.tgz",
+      "integrity": "sha512-4CWbR5vR9fMg23YqFOhr6t6WB1Fjt62d6xdFPyj8pxrYub7d+OgZaObMsoxaF9yBUHPMiPFK303v61PwAuGvZA==",
       "dependencies": {
-        "@vue/compiler-core": "3.4.24",
-        "@vue/shared": "3.4.24"
+        "@vue/compiler-core": "3.4.26",
+        "@vue/shared": "3.4.26"
       }
     },
     "node_modules/@vue/compiler-sfc": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.24.tgz",
-      "integrity": "sha512-nRAlJUK02FTWfA2nuvNBAqsDZuERGFgxZ8sGH62XgFSvMxO2URblzulExsmj4gFZ8e+VAyDooU9oAoXfEDNxTA==",
+      "version": "3.4.26",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.26.tgz",
+      "integrity": "sha512-It1dp+FAOCgluYSVYlDn5DtZBxk1NCiJJfu2mlQqa/b+k8GL6NG/3/zRbJnHdhV2VhxFghaDq5L4K+1dakW6cw==",
       "dependencies": {
         "@babel/parser": "^7.24.4",
-        "@vue/compiler-core": "3.4.24",
-        "@vue/compiler-dom": "3.4.24",
-        "@vue/compiler-ssr": "3.4.24",
-        "@vue/shared": "3.4.24",
+        "@vue/compiler-core": "3.4.26",
+        "@vue/compiler-dom": "3.4.26",
+        "@vue/compiler-ssr": "3.4.26",
+        "@vue/shared": "3.4.26",
         "estree-walker": "^2.0.2",
         "magic-string": "^0.30.10",
         "postcss": "^8.4.38",
@@ -2713,57 +2713,57 @@
       }
     },
     "node_modules/@vue/compiler-ssr": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.24.tgz",
-      "integrity": "sha512-ZsAtr4fhaUFnVcDqwW3bYCSDwq+9Gk69q2r/7dAHDrOMw41kylaMgOP4zRnn6GIEJkQznKgrMOGPMFnLB52RbQ==",
+      "version": "3.4.26",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.26.tgz",
+      "integrity": "sha512-FNwLfk7LlEPRY/g+nw2VqiDKcnDTVdCfBREekF8X74cPLiWHUX6oldktf/Vx28yh4STNy7t+/yuLoMBBF7YDiQ==",
       "dependencies": {
-        "@vue/compiler-dom": "3.4.24",
-        "@vue/shared": "3.4.24"
+        "@vue/compiler-dom": "3.4.26",
+        "@vue/shared": "3.4.26"
       }
     },
     "node_modules/@vue/reactivity": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.24.tgz",
-      "integrity": "sha512-nup3fSYg4i4LtNvu9slF/HF/0dkMQYfepUdORBcMSsankzRPzE7ypAFurpwyRBfU1i7Dn1kcwpYsE1wETSh91g==",
+      "version": "3.4.26",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.26.tgz",
+      "integrity": "sha512-E/ynEAu/pw0yotJeLdvZEsp5Olmxt+9/WqzvKff0gE67tw73gmbx6tRkiagE/eH0UCubzSlGRebCbidB1CpqZQ==",
       "dependencies": {
-        "@vue/shared": "3.4.24"
+        "@vue/shared": "3.4.26"
       }
     },
     "node_modules/@vue/runtime-core": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.24.tgz",
-      "integrity": "sha512-c7iMfj6cJMeAG3s5yOn9Rc5D9e2/wIuaozmGf/ICGCY3KV5H7mbTVdvEkd4ZshTq7RUZqj2k7LMJWVx+EBiY1g==",
+      "version": "3.4.26",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.26.tgz",
+      "integrity": "sha512-AFJDLpZvhT4ujUgZSIL9pdNcO23qVFh7zWCsNdGQBw8ecLNxOOnPcK9wTTIYCmBJnuPHpukOwo62a2PPivihqw==",
       "dependencies": {
-        "@vue/reactivity": "3.4.24",
-        "@vue/shared": "3.4.24"
+        "@vue/reactivity": "3.4.26",
+        "@vue/shared": "3.4.26"
       }
     },
     "node_modules/@vue/runtime-dom": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.24.tgz",
-      "integrity": "sha512-uXKzuh/Emfad2Y7Qm0ABsLZZV6H3mAJ5ZVqmAOlrNQRf+T5mxpPGZBfec1hkP41t6h6FwF6RSGCs/gd8WbuySQ==",
+      "version": "3.4.26",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.26.tgz",
+      "integrity": "sha512-UftYA2hUXR2UOZD/Fc3IndZuCOOJgFxJsWOxDkhfVcwLbsfh2CdXE2tG4jWxBZuDAs9J9PzRTUFt1PgydEtItw==",
       "dependencies": {
-        "@vue/runtime-core": "3.4.24",
-        "@vue/shared": "3.4.24",
+        "@vue/runtime-core": "3.4.26",
+        "@vue/shared": "3.4.26",
         "csstype": "^3.1.3"
       }
     },
     "node_modules/@vue/server-renderer": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.24.tgz",
-      "integrity": "sha512-H+DLK4sQF6sRgzKyofmlEVBIV/9KrQU6HIV7nt6yIwSGGKvSwlV8pqJlebUKLpbXaNHugdSfAbP6YmXF69lxow==",
+      "version": "3.4.26",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.26.tgz",
+      "integrity": "sha512-xoGAqSjYDPGAeRWxeoYwqJFD/gw7mpgzOvSxEmjWaFO2rE6qpbD1PC172YRpvKhrihkyHJkNDADFXTfCyVGhKw==",
       "dependencies": {
-        "@vue/compiler-ssr": "3.4.24",
-        "@vue/shared": "3.4.24"
+        "@vue/compiler-ssr": "3.4.26",
+        "@vue/shared": "3.4.26"
       },
       "peerDependencies": {
-        "vue": "3.4.24"
+        "vue": "3.4.26"
       }
     },
     "node_modules/@vue/shared": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.24.tgz",
-      "integrity": "sha512-BW4tajrJBM9AGAknnyEw5tO2xTmnqgup0VTnDAMcxYmqOX0RG0b9aSUGAbEKolD91tdwpA6oCwbltoJoNzpItw=="
+      "version": "3.4.26",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.26.tgz",
+      "integrity": "sha512-Fg4zwR0GNnjzodMt3KRy2AWGMKQXByl56+4HjN87soxLNU9P5xcJkstAlIeEF3cU6UYOzmJl1tV0dVPGIljCnQ=="
     },
     "node_modules/@vue/test-utils": {
       "version": "2.4.5",
@@ -12413,15 +12413,15 @@
       }
     },
     "node_modules/vue": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.24.tgz",
-      "integrity": "sha512-NPdx7dLGyHmKHGRRU5bMRYVE+rechR+KDU5R2tSTNG36PuMwbfAJ+amEvOAw7BPfZp5sQulNELSLm5YUkau+Sg==",
+      "version": "3.4.26",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.26.tgz",
+      "integrity": "sha512-bUIq/p+VB+0xrJubaemrfhk1/FiW9iX+pDV+62I/XJ6EkspAO9/DXEjbDFoe8pIfOZBqfk45i9BMc41ptP/uRg==",
       "dependencies": {
-        "@vue/compiler-dom": "3.4.24",
-        "@vue/compiler-sfc": "3.4.24",
-        "@vue/runtime-dom": "3.4.24",
-        "@vue/server-renderer": "3.4.24",
-        "@vue/shared": "3.4.24"
+        "@vue/compiler-dom": "3.4.26",
+        "@vue/compiler-sfc": "3.4.26",
+        "@vue/runtime-dom": "3.4.26",
+        "@vue/server-renderer": "3.4.26",
+        "@vue/shared": "3.4.26"
       },
       "peerDependencies": {
         "typescript": "*"
diff --git a/package.json b/package.json
index 0de030a3ef..40c19c5a22 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
     "tributejs": "5.1.3",
     "uint8-to-base64": "0.2.0",
     "vanilla-colorful": "0.7.2",
-    "vue": "3.4.24",
+    "vue": "3.4.26",
     "vue-bar-graph": "2.0.0",
     "vue-chartjs": "5.3.1",
     "vue-loader": "17.4.2",

From f11957b8f40b04bce31616e32e55b2b87cb0dba0 Mon Sep 17 00:00:00 2001
From: Earl Warren <contact@earl-warren.org>
Date: Wed, 1 May 2024 09:32:01 +0200
Subject: [PATCH 097/107] chore(renovate): vue patch releases can be automerged

---
 renovate.json | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/renovate.json b/renovate.json
index 95efafe763..89025178c3 100644
--- a/renovate.json
+++ b/renovate.json
@@ -72,6 +72,7 @@
     {
       "description": "Split minor and patch updates",
       "matchDepNames": [
+        "vue",
         "github.com/urfave/cli/v2",
         "swagger-ui-dist"
       ],
@@ -80,6 +81,7 @@
     {
       "description": "Automerge patch updates",
       "matchDepNames": [
+        "vue",
         "github.com/urfave/cli/v2",
         "swagger-ui-dist"
       ],

From 632a274b8f32a02c68b5724d6b16f0c2cf4a6905 Mon Sep 17 00:00:00 2001
From: Gergely Nagy <forgejo@gergo.csillger.hu>
Date: Tue, 30 Apr 2024 11:21:41 +0200
Subject: [PATCH 098/107] Fix Issue watching / unwatching on the web ui

When subscribing or unsubscribing to/from an issue on the web ui, the
request was posted to a route handled by `repo.IssueWatch`. This
function used `ctx.Req.PostForm.Get()`, erroneously.

`request.PostForm` is *only* available if `request.ParseForm()` has been
called before it. The function in question did not do that. Under some
circumstances, something, somewhere did end up calling `ParseForm()`,
but not in every scenario.

Since we do not need to check for multiple values, the easiest fix here
is to use `ctx.Req.PostFormValue`, which will call `ParseForm()` if
necessary.

Fixes #3516.

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
---
 release-notes/8.0.0/fix/3562.md | 1 +
 routers/web/repo/issue_watch.go | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 release-notes/8.0.0/fix/3562.md

diff --git a/release-notes/8.0.0/fix/3562.md b/release-notes/8.0.0/fix/3562.md
new file mode 100644
index 0000000000..8099056205
--- /dev/null
+++ b/release-notes/8.0.0/fix/3562.md
@@ -0,0 +1 @@
+Fixed a bug where subscribing to or unsubscribing from an issue in a repository with no code produced an internal server error.
diff --git a/routers/web/repo/issue_watch.go b/routers/web/repo/issue_watch.go
index c8d7187b8e..5cff9f4ddd 100644
--- a/routers/web/repo/issue_watch.go
+++ b/routers/web/repo/issue_watch.go
@@ -46,7 +46,7 @@ func IssueWatch(ctx *context.Context) {
 		return
 	}
 
-	watch, err := strconv.ParseBool(ctx.Req.PostForm.Get("watch"))
+	watch, err := strconv.ParseBool(ctx.Req.PostFormValue("watch"))
 	if err != nil {
 		ctx.ServerError("watch is not bool", err)
 		return

From 8cc5d5dc784b11b07b21daa6f46a29c263b94806 Mon Sep 17 00:00:00 2001
From: Gergely Nagy <forgejo@gergo.csillger.hu>
Date: Tue, 30 Apr 2024 01:35:40 +0200
Subject: [PATCH 099/107] tests: Support creating a declarative repo without
 AutoInit

To be able to easily test cases where the repository does not have any
code, where the git repo itself is completely uninitialized, lets
support a case where the `AutoInit` property is false.

For the sake of backwards compatibility, if the option is not set either
way, it will default to `true`.

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
---
 tests/integration/integration_test.go | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go
index f5b87231f3..f6daf0d146 100644
--- a/tests/integration/integration_test.go
+++ b/tests/integration/integration_test.go
@@ -692,6 +692,7 @@ type DeclarativeRepoOptions struct {
 	DisabledUnits optional.Option[[]unit_model.Type]
 	Files         optional.Option[[]*files_service.ChangeRepoFile]
 	WikiBranch    optional.Option[string]
+	AutoInit      optional.Option[bool]
 }
 
 func CreateDeclarativeRepoWithOptions(t *testing.T, owner *user_model.User, opts DeclarativeRepoOptions) (*repo_model.Repository, string, func()) {
@@ -706,11 +707,18 @@ func CreateDeclarativeRepoWithOptions(t *testing.T, owner *user_model.User, opts
 		repoName = gouuid.NewString()
 	}
 
+	var autoInit bool
+	if opts.AutoInit.Has() {
+		autoInit = opts.AutoInit.Value()
+	} else {
+		autoInit = true
+	}
+
 	// Create the repository
 	repo, err := repo_service.CreateRepository(db.DefaultContext, owner, owner, repo_service.CreateRepoOptions{
 		Name:          repoName,
 		Description:   "Temporary Repo",
-		AutoInit:      true,
+		AutoInit:      autoInit,
 		Gitignores:    "",
 		License:       "WTFPL",
 		Readme:        "Default",
@@ -742,6 +750,7 @@ func CreateDeclarativeRepoWithOptions(t *testing.T, owner *user_model.User, opts
 	// Add files, if any.
 	var sha string
 	if opts.Files.Has() {
+		assert.True(t, autoInit, "Files cannot be specified if AutoInit is disabled")
 		files := opts.Files.Value()
 
 		resp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, owner, &files_service.ChangeRepoFilesOptions{

From 21911bfe57a899736987f621f3970a8734d9395f Mon Sep 17 00:00:00 2001
From: Gergely Nagy <forgejo@gergo.csillger.hu>
Date: Tue, 30 Apr 2024 13:06:31 +0200
Subject: [PATCH 100/107] Add a test case for unsubscribing from an issue

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
---
 tests/integration/issue_test.go | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go
index 49d1cbb016..e09656c4ee 100644
--- a/tests/integration/issue_test.go
+++ b/tests/integration/issue_test.go
@@ -22,6 +22,7 @@ import (
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/indexer/issues"
+	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/references"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
@@ -876,3 +877,23 @@ body:
 		})
 	})
 }
+
+func TestIssueUnsubscription(t *testing.T) {
+	onGiteaRun(t, func(t *testing.T, u *url.URL) {
+		defer tests.PrepareTestEnv(t)()
+
+		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+		repo, _, f := CreateDeclarativeRepoWithOptions(t, user, DeclarativeRepoOptions{
+			AutoInit: optional.Some(false),
+		})
+		defer f()
+		session := loginUser(t, user.Name)
+
+		issueURL := testNewIssue(t, session, user.Name, repo.Name, "Issue title", "Description")
+		req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/watch", issueURL), map[string]string{
+			"_csrf": GetCSRF(t, session, issueURL),
+			"watch": "0",
+		})
+		session.MakeRequest(t, req, http.StatusOK)
+	})
+}

From a050b546b962e33aa3c1fe5d4281216f667de247 Mon Sep 17 00:00:00 2001
From: Earl Warren <contact@earl-warren.org>
Date: Tue, 30 Apr 2024 11:24:10 +0200
Subject: [PATCH 101/107] [skip ci] docs(release-notes): 7.0.2

---
 RELEASE-NOTES.md                | 18 ++++++++++++++++++
 release-notes/8.0.0/fix/3504.md |  1 -
 release-notes/8.0.0/fix/3562.md |  1 -
 release-notes/8.0.0/fix/3570.md |  1 -
 4 files changed, 18 insertions(+), 3 deletions(-)
 delete mode 100644 release-notes/8.0.0/fix/3504.md
 delete mode 100644 release-notes/8.0.0/fix/3562.md
 delete mode 100644 release-notes/8.0.0/fix/3570.md

diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 844c0621fe..6d583f0eff 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -8,6 +8,23 @@ A [patch or minor release](https://semver.org/spec/v2.0.0.html) (e.g. upgrading
 
 - [8.0.0](/release-notes/8.0.0/)
 
+## 7.0.2
+
+This is a bug fix release. See the documentation for more information on the [upgrade procedure](https://forgejo.org/docs/v7.0/admin/upgrade/).
+
+In addition to the following notable bug fixes, you can browse the [full list of commits](https://codeberg.org/forgejo/forgejo/compare/v7.0.1...v7.0.2) included in this release.
+
+* **Bug fixes:**
+  * [PR](https://codeberg.org/forgejo/forgejo/pulls/3562): a v7.0.0 regression where subscribing to or unsubscribing from an issue in a repository with no code produced an internal server error.
+  * [PR](https://codeberg.org/forgejo/forgejo/issues/3559): a v7.0.0 regression makes all the refs sent in Gitea webhooks to be full refs and might break Woodpecker CI pipelines triggered on tag (`CI_COMMIT_TAG` contained the full ref). This issue [has been fixed](https://github.com/woodpecker-ci/woodpecker/pull/3664) in the `main` branch of Woodpecker CI as well.
+  * [PR](https://codeberg.org/forgejo/forgejo/pulls/3555): the webhook branch filter wrongly applied the match on the full ref for branch creation and deletion (wrongly skipping events).
+  * [PR](https://codeberg.org/forgejo/forgejo/pulls/3537): toggling the WIP state of a pull request is possible from the sidebar, but not from the footer.
+  * [PR](https://codeberg.org/forgejo/forgejo/pulls/3565): when mentioning a user, the markup post-processor does not handle the case where the mentioned user does not exist: it tries to skip to the next node, which in turn, ended up skipping the rest of the line.
+  * [PR](https://codeberg.org/forgejo/forgejo/pulls/3570): excessive and unnecessary database queries when a user with no repositories is viewing their dashboard.
+  * [PR](https://codeberg.org/forgejo/forgejo/pulls/3580): duplicate status check contexts show in the branch protection settings.
+  * [PR](https://codeberg.org/forgejo/forgejo/pulls/3497): profile info fails to render german singular translation.
+  * [PR](https://codeberg.org/forgejo/forgejo/pulls/3504): inline attachments of [incoming emails](https://forgejo.org/docs/v7.0/user/incoming/) (as they occur for example with Apple Mail) are not attached to comments.
+
 ## 7.0.1
 
 This is a bug fix release. See the documentation for more information on the [upgrade procedure](https://forgejo.org/docs/v7.0/admin/upgrade/).
@@ -34,6 +51,7 @@ $ git -C forgejo log --oneline --no-merges origin/v1.21/forgejo..origin/v7.0/for
   * Running the [`forgejo doctor check --fix`](https://forgejo.org/docs/v7.0/admin/command-line/#doctor-check) CLI command or setting [`[cron.gc_lfs].ENABLED=true`](https://forgejo.org/docs/v7.0/admin/config-cheat-sheet/#cron---garbage-collect-lfs-pointers-in-repositories-crongc_lfs) (the default is `false`) will corrupt the LFS storage. The workaround is to not run the doctor CLI command and disable the `cron.gc_lfs`. This regression will be [fixed in 7.0.1](https://codeberg.org/forgejo/forgejo/issues/3438).
   * The [`forgejo admin user create`](https://forgejo.org/docs/v7.0/admin/command-line/#admin-user-create) CLI command [requires a password](https://codeberg.org/forgejo/forgejo/commit/b122c6ef8b9254120432aed373cbe075331132ac) change by default when creating the first user and the `--admin` flag is not specified. The `--must-change-password=false` argument must be given to not require a password change. This regression will be [fixed in 7.0.1](https://codeberg.org/forgejo/forgejo/issues/3399).
 * **Breaking changes requiring manual intervention:**
+  * [Forgejo webhooks](https://codeberg.org/forgejo/forgejo/issues/3055) now always send full refs (starting with `refs/`) instead of sending short refs in some cases. This new behavior may require changes when the receiving end assumes a short ref will be received (for instance some versions of Woodpecker CI when receiving webhook payloads when a tag is set).
   * [MySQL 8.0 or PostgreSQL 12](https://codeberg.org/forgejo/forgejo/commit/e94f9fcafdcf284561e7fb33f60156a69c4ad6a5) are the minimum supported versions. The database must be migrated before upgrading. The requirements regarding SQLite did not change.
   * The `per_page` parameter is [no longer a synonym for `limit`](https://codeberg.org/forgejo/forgejo/commit/0aab2d38a7d91bc8caff332e452364468ce52d9a) in the [/repos/{owner}/{repo}/releases](https://code.forgejo.org/api/swagger/#/repository/repoListReleases) API endpoint.
   * The date format of the `created` and `last_update` fields of the [`/repos/{owner}/{repo}/push_mirrors`](https://code.forgejo.org/api/swagger/#/repository/repoListPushMirrors) and [/repos/{owner}/{repo}/push_mirrors](https://code.forgejo.org/api/swagger/#/repository/repoAddPushMirror) API endpoint changed [to be timestamps instead of numbers](https://codeberg.org/forgejo/forgejo/commit/0ee7cbf725f45650136be45f8e0f74d395f73b5c).
diff --git a/release-notes/8.0.0/fix/3504.md b/release-notes/8.0.0/fix/3504.md
deleted file mode 100644
index 8ece1557ab..0000000000
--- a/release-notes/8.0.0/fix/3504.md
+++ /dev/null
@@ -1 +0,0 @@
-Fixed that inline attachments of emails (as they occur for example with Apple Mail) are not attached to comments.
diff --git a/release-notes/8.0.0/fix/3562.md b/release-notes/8.0.0/fix/3562.md
deleted file mode 100644
index 8099056205..0000000000
--- a/release-notes/8.0.0/fix/3562.md
+++ /dev/null
@@ -1 +0,0 @@
-Fixed a bug where subscribing to or unsubscribing from an issue in a repository with no code produced an internal server error.
diff --git a/release-notes/8.0.0/fix/3570.md b/release-notes/8.0.0/fix/3570.md
deleted file mode 100644
index 1e860eb958..0000000000
--- a/release-notes/8.0.0/fix/3570.md
+++ /dev/null
@@ -1 +0,0 @@
-Fixed an issue that resulted in excessive and unnecessary database queries when a user with no repositories was viewing their dashboard.

From 4a2959b3ec214a954d6b144f0fb9efb848129c8c Mon Sep 17 00:00:00 2001
From: varp0n <tom@gkstn.de>
Date: Wed, 1 May 2024 16:28:44 +0000
Subject: [PATCH 102/107] FIX gogs migration if gogs is hosted at a subpath
 (#3572)

Also add a test for GogsDownloaderFactory.New() to make sure
that the URL of the source repository is parsed correctly.

When the source gogs instance is hosted at a subpath like `https://git.example.com/gogs/<username>/<reponame>` the migration fails.
This PR fixes that.

Co-authored-by: hecker <tomas.hecker@gmail.com>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3572
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: varp0n <tom@gkstn.de>
Co-committed-by: varp0n <tom@gkstn.de>
---
 release-notes/8.0.0/3572.md      |  1 +
 services/migrations/gogs.go      | 15 ++++--
 services/migrations/gogs_test.go | 84 ++++++++++++++++++++++++++++++++
 3 files changed, 96 insertions(+), 4 deletions(-)
 create mode 100644 release-notes/8.0.0/3572.md

diff --git a/release-notes/8.0.0/3572.md b/release-notes/8.0.0/3572.md
new file mode 100644
index 0000000000..c79f9e30fc
--- /dev/null
+++ b/release-notes/8.0.0/3572.md
@@ -0,0 +1 @@
+Fix gogs migration if gogs is hosted at a subpath
diff --git a/services/migrations/gogs.go b/services/migrations/gogs.go
index 72c52d180b..b31d05fa73 100644
--- a/services/migrations/gogs.go
+++ b/services/migrations/gogs.go
@@ -38,17 +38,24 @@ func (f *GogsDownloaderFactory) New(ctx context.Context, opts base.MigrateOption
 		return nil, err
 	}
 
-	baseURL := u.Scheme + "://" + u.Host
 	repoNameSpace := strings.TrimSuffix(u.Path, ".git")
 	repoNameSpace = strings.Trim(repoNameSpace, "/")
 
 	fields := strings.Split(repoNameSpace, "/")
-	if len(fields) < 2 {
+	numFields := len(fields)
+	if numFields < 2 {
 		return nil, fmt.Errorf("invalid path: %s", repoNameSpace)
 	}
 
-	log.Trace("Create gogs downloader. BaseURL: %s RepoOwner: %s RepoName: %s", baseURL, fields[0], fields[1])
-	return NewGogsDownloader(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, fields[0], fields[1]), nil
+	repoOwner := fields[numFields-2]
+	repoName := fields[numFields-1]
+
+	u.Path = ""
+	u = u.JoinPath(fields[:numFields-2]...)
+	baseURL := u.String()
+
+	log.Trace("Create gogs downloader. BaseURL: %s RepoOwner: %s RepoName: %s", baseURL, repoOwner, repoName)
+	return NewGogsDownloader(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, repoOwner, repoName), nil
 }
 
 // GitServiceType returns the type of git service
diff --git a/services/migrations/gogs_test.go b/services/migrations/gogs_test.go
index 610af183de..ca02b4317b 100644
--- a/services/migrations/gogs_test.go
+++ b/services/migrations/gogs_test.go
@@ -137,3 +137,87 @@ func TestGogsDownloadRepo(t *testing.T) {
 	_, _, err = downloader.GetPullRequests(1, 3)
 	assert.Error(t, err)
 }
+
+func TestGogsDownloaderFactory_New(t *testing.T) {
+	tests := []struct {
+		name      string
+		args      base.MigrateOptions
+		baseURL   string
+		repoOwner string
+		repoName  string
+		wantErr   bool
+	}{
+		{
+			name: "Gogs_at_root",
+			args: base.MigrateOptions{
+				CloneAddr:    "https://git.example.com/user/repo.git",
+				AuthUsername: "username",
+				AuthPassword: "password",
+				AuthToken:    "authtoken",
+			},
+			baseURL:   "https://git.example.com/",
+			repoOwner: "user",
+			repoName:  "repo",
+			wantErr:   false,
+		},
+		{
+			name: "Gogs_at_sub_path",
+			args: base.MigrateOptions{
+				CloneAddr:    "https://git.example.com/subpath/user/repo.git",
+				AuthUsername: "username",
+				AuthPassword: "password",
+				AuthToken:    "authtoken",
+			},
+			baseURL:   "https://git.example.com/subpath",
+			repoOwner: "user",
+			repoName:  "repo",
+			wantErr:   false,
+		},
+		{
+			name: "Gogs_at_2nd_sub_path",
+			args: base.MigrateOptions{
+				CloneAddr:    "https://git.example.com/sub1/sub2/user/repo.git",
+				AuthUsername: "username",
+				AuthPassword: "password",
+				AuthToken:    "authtoken",
+			},
+			baseURL:   "https://git.example.com/sub1/sub2",
+			repoOwner: "user",
+			repoName:  "repo",
+			wantErr:   false,
+		},
+		{
+			name: "Gogs_URL_too_short",
+			args: base.MigrateOptions{
+				CloneAddr:    "https://git.example.com/repo.git",
+				AuthUsername: "username",
+				AuthPassword: "password",
+				AuthToken:    "authtoken",
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			f := &GogsDownloaderFactory{}
+			opts := base.MigrateOptions{
+				CloneAddr:    tt.args.CloneAddr,
+				AuthUsername: tt.args.AuthUsername,
+				AuthPassword: tt.args.AuthPassword,
+				AuthToken:    tt.args.AuthToken,
+			}
+			got, err := f.New(context.Background(), opts)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GogsDownloaderFactory.New() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			} else if err != nil {
+				return
+			}
+
+			assert.IsType(t, &GogsDownloader{}, got)
+			assert.EqualValues(t, tt.baseURL, got.(*GogsDownloader).baseURL)
+			assert.EqualValues(t, tt.repoOwner, got.(*GogsDownloader).repoOwner)
+			assert.EqualValues(t, tt.repoName, got.(*GogsDownloader).repoName)
+		})
+	}
+}

From d096a21da6a5ed14ea1bac832cce065d609d8546 Mon Sep 17 00:00:00 2001
From: 0ko <0ko@noreply.codeberg.org>
Date: Wed, 1 May 2024 18:29:42 +0000
Subject: [PATCH 103/107] Fix inconsistent required field (#3583)

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3583
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Reviewed-by: Otto <otto@codeberg.org>
---
 release-notes/8.0.0/3583.md                           | 1 +
 templates/user/settings/applications_oauth2_list.tmpl | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 release-notes/8.0.0/3583.md

diff --git a/release-notes/8.0.0/3583.md b/release-notes/8.0.0/3583.md
new file mode 100644
index 0000000000..0f5fdc8f60
--- /dev/null
+++ b/release-notes/8.0.0/3583.md
@@ -0,0 +1 @@
+Settings: OAuth2 applications: Consistently check input on client side
diff --git a/templates/user/settings/applications_oauth2_list.tmpl b/templates/user/settings/applications_oauth2_list.tmpl
index c75cbd532e..74cdac49c0 100644
--- a/templates/user/settings/applications_oauth2_list.tmpl
+++ b/templates/user/settings/applications_oauth2_list.tmpl
@@ -59,7 +59,7 @@
 		</div>
 		<div class="field {{if .Err_RedirectURI}}error{{end}}">
 			<label for="redirect-uris">{{ctx.Locale.Tr "settings.oauth2_redirect_uris"}}</label>
-			<textarea name="redirect_uris" id="redirect-uris"></textarea>
+			<textarea name="redirect_uris" id="redirect-uris" required></textarea>
 		</div>
 		<div class="field {{if .Err_ConfidentialClient}}error{{end}}">
 			<div class="ui checkbox">

From 8164ef9762b41eee52917147408a118926f285cd Mon Sep 17 00:00:00 2001
From: Gergely Nagy <forgejo@gergo.csillger.hu>
Date: Wed, 1 May 2024 12:36:09 +0200
Subject: [PATCH 104/107] markup: Allow cross references to contain URL query
 parameters too

Adjust the `anyHashPattern` to match URL query parameters too, and
adjust `fullHashPatternProcessor` accordingly.

Includes a test case, and an update to an existing one to account for
the new capture group.

Fixes #3548.

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
---
 modules/markup/html.go               |  8 ++++----
 modules/markup/html_internal_test.go | 11 +++++++++++
 modules/markup/html_test.go          |  6 ++++++
 3 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/modules/markup/html.go b/modules/markup/html.go
index 56f63b8a76..f73221a37f 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -54,7 +54,7 @@ var (
 	shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
 
 	// anySHA1Pattern splits url containing SHA into parts
-	anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~_%.a-zA-Z0-9/]+)?(#[-+~_%.a-zA-Z0-9]+)?`)
+	anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~_%.a-zA-Z0-9/]+)?(\?[-+~_%\.a-zA-Z0-9=&]+)?(#[-+~_%.a-zA-Z0-9]+)?`)
 
 	// comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash"
 	comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?`)
@@ -969,10 +969,10 @@ func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
 			subpath = node.Data[m[4]:m[5]]
 		}
 
-		// 4th capture group matches a optional url hash
+		// 5th capture group matches a optional url hash
 		hash := ""
-		if m[7] > 0 {
-			hash = node.Data[m[6]:m[7]][1:]
+		if m[9] > 0 {
+			hash = node.Data[m[8]:m[9]][1:]
 		}
 
 		start := m[0]
diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go
index e313be7040..917f280c73 100644
--- a/modules/markup/html_internal_test.go
+++ b/modules/markup/html_internal_test.go
@@ -403,28 +403,39 @@ func TestRegExp_anySHA1Pattern(t *testing.T) {
 		"https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js#L2703": {
 			"a644101ed04d0beacea864ce805e0c4f86ba1cd1",
 			"/test/unit/event.js",
+			"",
 			"#L2703",
 		},
 		"https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js": {
 			"a644101ed04d0beacea864ce805e0c4f86ba1cd1",
 			"/test/unit/event.js",
 			"",
+			"",
 		},
 		"https://github.com/jquery/jquery/commit/0705be475092aede1eddae01319ec931fb9c65fc": {
 			"0705be475092aede1eddae01319ec931fb9c65fc",
 			"",
 			"",
+			"",
 		},
 		"https://github.com/jquery/jquery/tree/0705be475092aede1eddae01319ec931fb9c65fc/src": {
 			"0705be475092aede1eddae01319ec931fb9c65fc",
 			"/src",
 			"",
+			"",
 		},
 		"https://try.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2": {
 			"d8a994ef243349f321568f9e36d5c3f444b99cae",
 			"",
+			"",
 			"#diff-2",
 		},
+		"https://codeberg.org/forgejo/forgejo/src/commit/949ab9a5c4cac742f84ae5a9fa186f8d6eb2cdc0/RELEASE-NOTES.md?display=source&w=1#L7-L9": {
+			"949ab9a5c4cac742f84ae5a9fa186f8d6eb2cdc0",
+			"/RELEASE-NOTES.md",
+			"?display=source&w=1",
+			"#L7-L9",
+		},
 	}
 
 	for k, v := range testCases {
diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go
index a1a99c1a7f..cfd1a66a18 100644
--- a/modules/markup/html_test.go
+++ b/modules/markup/html_test.go
@@ -127,6 +127,12 @@ func TestRender_CrossReferences(t *testing.T) {
 	test(
 		util.URLJoin(markup.TestAppURL, "gogitea", "some-repo-name", "issues", "12345"),
 		`<p><a href="`+util.URLJoin(markup.TestAppURL, "gogitea", "some-repo-name", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/some-repo-name#12345</a></p>`)
+
+	sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d"
+	urlWithQuery := util.URLJoin(markup.TestAppURL, "forgejo", "some-repo-name", "commit", sha, "README.md") + "?display=source#L1-L5"
+	test(
+		urlWithQuery,
+		`<p><a href="`+urlWithQuery+`" rel="nofollow"><code>`+sha[:10]+`/README.md (L1-L5)</code></a></p>`)
 }
 
 func TestMisc_IsSameDomain(t *testing.T) {

From b08aef967ede128e231b66fedfcbb068ca06a590 Mon Sep 17 00:00:00 2001
From: Gergely Nagy <forgejo@gergo.csillger.hu>
Date: Tue, 30 Apr 2024 11:55:46 +0200
Subject: [PATCH 105/107] Use PostFormValue instead of PostForm.Get

In `repo.RemoveDependency`, use `PostFormValue` instead of
`PostForm.Get`. The latter requires `ParseForm()` to be called prior,
and in this case, has no benefit over `PostFormValue` anyway (which
calls `ParseForm()` if necessary).

While this currently does not cause any issue as far as I can tell, it
feels like a bug lying in wait for the perfect opportunity. Lets squash
it before it can do harm.

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
---
 routers/web/repo/issue_dependency.go |  2 +-
 tests/integration/issue_test.go      | 88 ++++++++++++++++++++++++++++
 2 files changed, 89 insertions(+), 1 deletion(-)

diff --git a/routers/web/repo/issue_dependency.go b/routers/web/repo/issue_dependency.go
index e3b85ee638..66b38688ec 100644
--- a/routers/web/repo/issue_dependency.go
+++ b/routers/web/repo/issue_dependency.go
@@ -109,7 +109,7 @@ func RemoveDependency(ctx *context.Context) {
 	}
 
 	// Dependency Type
-	depTypeStr := ctx.Req.PostForm.Get("dependencyType")
+	depTypeStr := ctx.Req.PostFormValue("dependencyType")
 
 	var depType issues_model.DependencyType
 
diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go
index 49d1cbb016..27b95300db 100644
--- a/tests/integration/issue_test.go
+++ b/tests/integration/issue_test.go
@@ -15,6 +15,7 @@ import (
 	"testing"
 	"time"
 
+	auth_model "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
 	issues_model "code.gitea.io/gitea/models/issues"
 	repo_model "code.gitea.io/gitea/models/repo"
@@ -193,6 +194,93 @@ func TestNewIssue(t *testing.T) {
 	testNewIssue(t, session, "user2", "repo1", "Title", "Description")
 }
 
+func TestIssueDependencies(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+	session := loginUser(t, owner.Name)
+	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
+
+	repo, _, f := CreateDeclarativeRepoWithOptions(t, owner, DeclarativeRepoOptions{})
+	defer f()
+
+	createIssue := func(t *testing.T, title string) api.Issue {
+		t.Helper()
+
+		urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repo.Name)
+		req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{
+			Body:  "",
+			Title: title,
+		}).AddTokenAuth(token)
+		resp := MakeRequest(t, req, http.StatusCreated)
+
+		var apiIssue api.Issue
+		DecodeJSON(t, resp, &apiIssue)
+
+		return apiIssue
+	}
+	addDependency := func(t *testing.T, issue, dependency api.Issue) {
+		t.Helper()
+
+		urlStr := fmt.Sprintf("/%s/%s/issues/%d/dependency/add", owner.Name, repo.Name, issue.Index)
+		req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
+			"_csrf":         GetCSRF(t, session, fmt.Sprintf("/%s/%s/issues/%d", owner.Name, repo.Name, issue.Index)),
+			"newDependency": fmt.Sprintf("%d", dependency.Index),
+		})
+		session.MakeRequest(t, req, http.StatusSeeOther)
+	}
+	removeDependency := func(t *testing.T, issue, dependency api.Issue) {
+		t.Helper()
+
+		urlStr := fmt.Sprintf("/%s/%s/issues/%d/dependency/delete", owner.Name, repo.Name, issue.Index)
+		req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
+			"_csrf":              GetCSRF(t, session, fmt.Sprintf("/%s/%s/issues/%d", owner.Name, repo.Name, issue.Index)),
+			"removeDependencyID": fmt.Sprintf("%d", dependency.Index),
+			"dependencyType":     "blockedBy",
+		})
+		session.MakeRequest(t, req, http.StatusSeeOther)
+	}
+
+	assertHasDependency := func(t *testing.T, issueID, dependencyID int64, hasDependency bool) {
+		t.Helper()
+
+		urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/dependencies", owner.Name, repo.Name, issueID)
+		req := NewRequest(t, "GET", urlStr)
+		resp := MakeRequest(t, req, http.StatusOK)
+
+		var issues []api.Issue
+		DecodeJSON(t, resp, &issues)
+
+		if hasDependency {
+			assert.NotEmpty(t, issues)
+			assert.EqualValues(t, issues[0].Index, dependencyID)
+		} else {
+			assert.Empty(t, issues)
+		}
+	}
+
+	t.Run("Add dependency", func(t *testing.T) {
+		defer tests.PrintCurrentTest(t)()
+
+		issue1 := createIssue(t, "issue #1")
+		issue2 := createIssue(t, "issue #2")
+		addDependency(t, issue1, issue2)
+
+		assertHasDependency(t, issue1.Index, issue2.Index, true)
+	})
+
+	t.Run("Remove dependency", func(t *testing.T) {
+		defer tests.PrintCurrentTest(t)()
+
+		issue1 := createIssue(t, "issue #1")
+		issue2 := createIssue(t, "issue #2")
+		addDependency(t, issue1, issue2)
+		removeDependency(t, issue1, issue2)
+
+		assertHasDependency(t, issue1.Index, issue2.Index, false)
+	})
+}
+
 func TestIssueCommentClose(t *testing.T) {
 	defer tests.PrepareTestEnv(t)()
 	session := loginUser(t, "user2")

From d50efa626af8cf422a36f7578974b927d834b976 Mon Sep 17 00:00:00 2001
From: JakobDev <jakobdev@gmx.de>
Date: Thu, 2 May 2024 15:51:27 +0000
Subject: [PATCH 106/107] Show repo count in blocked users tab (#3601)

Fixes #3595

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3601
Reviewed-by: Otto <otto@codeberg.org>
Co-authored-by: JakobDev <jakobdev@gmx.de>
Co-committed-by: JakobDev <jakobdev@gmx.de>
---
 routers/web/org/setting/blocked_users.go |   7 +
 templates/org/menu.tmpl                  |   4 +
 templates/user/overview/header.tmpl      |   2 +
 tests/integration/user_count_test.go     | 167 +++++++++++++++++++++++
 4 files changed, 180 insertions(+)
 create mode 100644 tests/integration/user_count_test.go

diff --git a/routers/web/org/setting/blocked_users.go b/routers/web/org/setting/blocked_users.go
index b23f5ba596..0c7f245c13 100644
--- a/routers/web/org/setting/blocked_users.go
+++ b/routers/web/org/setting/blocked_users.go
@@ -10,6 +10,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	user_model "code.gitea.io/gitea/models/user"
+	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	"code.gitea.io/gitea/services/context"
 	user_service "code.gitea.io/gitea/services/user"
 )
@@ -27,6 +28,12 @@ func BlockedUsers(ctx *context.Context) {
 		return
 	}
 
+	err = shared_user.LoadHeaderCount(ctx)
+	if err != nil {
+		ctx.ServerError("LoadHeaderCount", err)
+		return
+	}
+
 	ctx.Data["BlockedUsers"] = blockedUsers
 
 	ctx.HTML(http.StatusOK, tplBlockedUsers)
diff --git a/templates/org/menu.tmpl b/templates/org/menu.tmpl
index 1860a3765a..212154995d 100644
--- a/templates/org/menu.tmpl
+++ b/templates/org/menu.tmpl
@@ -6,6 +6,7 @@
 				{{if .RepoCount}}
 					<div class="ui small label">{{.RepoCount}}</div>
 				{{end}}
+        <span hidden test-name="repository-count">{{.RepoCount}}</span>
 			</a>
 			{{if .CanReadProjects}}
 			<a class="{{if .PageIsViewProjects}}active {{end}}item" href="{{$.Org.HomeLink}}/-/projects">
@@ -13,6 +14,7 @@
 				{{if .ProjectCount}}
 					<div class="ui small label">{{.ProjectCount}}</div>
 				{{end}}
+				<span hidden test-name="project-count">{{.ProjectCount}}</span>
 			</a>
 			{{end}}
 			{{if and .IsPackageEnabled .CanReadPackages}}
@@ -31,6 +33,7 @@
 				<div class="ui small label">{{.NumMembers}}</div>
 			</a>
 			{{end}}
+			<span hidden test-name="member-count">{{.NumMembers}}</span>
 			{{if .IsOrganizationMember}}
 			<a class="{{if $.PageIsOrgTeams}}active {{end}}item" href="{{$.OrgLink}}/teams">
 				{{svg "octicon-people"}} {{ctx.Locale.Tr "org.teams"}}
@@ -39,6 +42,7 @@
 				{{end}}
 			</a>
 			{{end}}
+			<span hidden test-name="team-count">{{.NumTeams}}</span>
 			{{if .IsOrganizationOwner}}
 			<a id="settings-btn" class="{{if .PageIsOrgSettings}}active {{end}}right item" href="{{.OrgLink}}/settings">
 			{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
diff --git a/templates/user/overview/header.tmpl b/templates/user/overview/header.tmpl
index 275c4e295e..1ec83042a3 100644
--- a/templates/user/overview/header.tmpl
+++ b/templates/user/overview/header.tmpl
@@ -10,6 +10,7 @@
 			{{if .RepoCount}}
 				<div class="ui small label">{{.RepoCount}}</div>
 			{{end}}
+			<span hidden test-name="repository-count">{{.RepoCount}}</span>
 		</a>
 		{{if or .ContextUser.IsIndividual .CanReadProjects}}
 		<a href="{{.ContextUser.HomeLink}}/-/projects" class="{{if .PageIsViewProjects}}active {{end}}item">
@@ -17,6 +18,7 @@
 			{{if .ProjectCount}}
 				<div class="ui small label">{{.ProjectCount}}</div>
 			{{end}}
+			<span hidden test-name="project-count">{{.ProjectCount}}</span>
 		</a>
 		{{end}}
 		{{if and .IsPackageEnabled (or .ContextUser.IsIndividual .CanReadPackages)}}
diff --git a/tests/integration/user_count_test.go b/tests/integration/user_count_test.go
new file mode 100644
index 0000000000..c0837d57fd
--- /dev/null
+++ b/tests/integration/user_count_test.go
@@ -0,0 +1,167 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+	"fmt"
+	"net/http"
+	"strconv"
+	"testing"
+
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/models/organization"
+	project_model "code.gitea.io/gitea/models/project"
+	repo_model "code.gitea.io/gitea/models/repo"
+	"code.gitea.io/gitea/models/unittest"
+	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/optional"
+	"code.gitea.io/gitea/tests"
+
+	"github.com/PuerkitoBio/goquery"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+type userCountTest struct {
+	doer         *user_model.User
+	user         *user_model.User
+	session      *TestSession
+	repoCount    int64
+	projectCount int64
+	memberCount  int64
+	teamCount    int64
+}
+
+func (countTest *userCountTest) Init(t *testing.T, doerID, userID int64) {
+	countTest.doer = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doerID})
+	countTest.user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID})
+	countTest.session = loginUser(t, countTest.doer.Name)
+
+	var err error
+
+	countTest.repoCount, err = repo_model.CountRepository(db.DefaultContext, &repo_model.SearchRepoOptions{
+		Actor:       countTest.doer,
+		OwnerID:     countTest.user.ID,
+		Private:     true,
+		Collaborate: optional.Some(false),
+	})
+	require.NoError(t, err)
+
+	var projectType project_model.Type
+	if countTest.user.IsOrganization() {
+		projectType = project_model.TypeOrganization
+	} else {
+		projectType = project_model.TypeIndividual
+	}
+	countTest.projectCount, err = db.Count[project_model.Project](db.DefaultContext, project_model.SearchOptions{
+		OwnerID:  countTest.user.ID,
+		IsClosed: optional.Some(false),
+		Type:     projectType,
+	})
+	require.NoError(t, err)
+
+	if !countTest.user.IsOrganization() {
+		return
+	}
+
+	org := (*organization.Organization)(countTest.user)
+
+	isMember, err := org.IsOrgMember(db.DefaultContext, countTest.doer.ID)
+	require.NoError(t, err)
+
+	countTest.memberCount, err = organization.CountOrgMembers(db.DefaultContext, &organization.FindOrgMembersOpts{
+		OrgID:      org.ID,
+		PublicOnly: !isMember,
+	})
+	require.NoError(t, err)
+
+	teams, err := org.LoadTeams(db.DefaultContext)
+	require.NoError(t, err)
+
+	countTest.teamCount = int64(len(teams))
+}
+
+func (countTest *userCountTest) getCount(doc *goquery.Document, name string) (int64, error) {
+	selection := doc.Find(fmt.Sprintf("[test-name=\"%s\"]", name))
+
+	if selection.Length() != 1 {
+		return 0, fmt.Errorf("%s was not found", name)
+	}
+
+	return strconv.ParseInt(selection.Text(), 10, 64)
+}
+
+func (countTest *userCountTest) TestPage(t *testing.T, page string, orgLink bool) {
+	t.Run(page, func(t *testing.T) {
+		var userLink string
+
+		if orgLink {
+			userLink = countTest.user.OrganisationLink()
+		} else {
+			userLink = countTest.user.HomeLink()
+		}
+
+		req := NewRequestf(t, "GET", "%s/%s", userLink, page)
+		resp := countTest.session.MakeRequest(t, req, http.StatusOK)
+		htmlDoc := NewHTMLParser(t, resp.Body)
+
+		repoCount, err := countTest.getCount(htmlDoc.doc, "repository-count")
+		require.NoError(t, err)
+		assert.Equal(t, countTest.repoCount, repoCount)
+
+		projectCount, err := countTest.getCount(htmlDoc.doc, "project-count")
+		require.NoError(t, err)
+		assert.Equal(t, countTest.projectCount, projectCount)
+
+		if !countTest.user.IsOrganization() {
+			return
+		}
+
+		memberCount, err := countTest.getCount(htmlDoc.doc, "member-count")
+		require.NoError(t, err)
+		assert.Equal(t, countTest.memberCount, memberCount)
+
+		teamCount, err := countTest.getCount(htmlDoc.doc, "team-count")
+		require.NoError(t, err)
+		assert.Equal(t, countTest.teamCount, teamCount)
+	})
+}
+
+func TestFrontendHeaderCountUser(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	countTest := new(userCountTest)
+	countTest.Init(t, 2, 2)
+
+	countTest.TestPage(t, "", false)
+	countTest.TestPage(t, "?tab=repositories", false)
+	countTest.TestPage(t, "-/projects", false)
+	countTest.TestPage(t, "-/packages", false)
+	countTest.TestPage(t, "?tab=activity", false)
+	countTest.TestPage(t, "?tab=stars", false)
+}
+
+func TestFrontendHeaderCountOrg(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	countTest := new(userCountTest)
+	countTest.Init(t, 15, 17)
+
+	countTest.TestPage(t, "", false)
+	countTest.TestPage(t, "-/projects", false)
+	countTest.TestPage(t, "-/packages", false)
+	countTest.TestPage(t, "members", true)
+	countTest.TestPage(t, "teams", true)
+
+	countTest.TestPage(t, "settings", true)
+	countTest.TestPage(t, "settings/hooks", true)
+	countTest.TestPage(t, "settings/labels", true)
+	countTest.TestPage(t, "settings/applications", true)
+	countTest.TestPage(t, "settings/packages", true)
+	countTest.TestPage(t, "settings/actions/runners", true)
+	countTest.TestPage(t, "settings/actions/secrets", true)
+	countTest.TestPage(t, "settings/actions/variables", true)
+	countTest.TestPage(t, "settings/blocked_users", true)
+	countTest.TestPage(t, "settings/delete", true)
+}

From 787b16a7bef60ce53fcd7ccfd61acdf9e7b3c6e0 Mon Sep 17 00:00:00 2001
From: 0ko <0ko@noreply.codeberg.org>
Date: Thu, 2 May 2024 21:31:03 +0500
Subject: [PATCH 107/107] [THEME] fix text selection color

regression of https://codeberg.org/forgejo/forgejo/commit/c2280a20097b938315b7f00b1aa18a9f5891ef45
---
 release-notes/8.0.0/3608.md               | 1 +
 web_src/css/themes/theme-forgejo-dark.css | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)
 create mode 100644 release-notes/8.0.0/3608.md

diff --git a/release-notes/8.0.0/3608.md b/release-notes/8.0.0/3608.md
new file mode 100644
index 0000000000..1c3072422a
--- /dev/null
+++ b/release-notes/8.0.0/3608.md
@@ -0,0 +1 @@
+Fix text selection color
diff --git a/web_src/css/themes/theme-forgejo-dark.css b/web_src/css/themes/theme-forgejo-dark.css
index 42495c854b..95d35ddec0 100644
--- a/web_src/css/themes/theme-forgejo-dark.css
+++ b/web_src/css/themes/theme-forgejo-dark.css
@@ -141,6 +141,7 @@
   /* other colors */
   --color-gold: #b1983b;
   --color-white: #ffffff;
+  --color-pure-black: #000000;
   --color-diff-removed-word-bg: #783030;
   --color-diff-added-word-bg: #255c39;
   --color-diff-removed-row-bg: #432121;
@@ -304,7 +305,7 @@ i.grey.icon.icon.icon.icon {
 }
 ::selection {
   background: var(--steel-100) !important;
-  color: var(--color-white) !important;
+  color: var(--color-pure-black) !important;
 }
 strong.attention-important, svg.attention-important {
   color: var(--color-violet-light);