Compare commits

...

84 commits

Author SHA1 Message Date
famfo
4e40eede03 [v15.0/forgejo] fix(activitypub): only return public activities on request (#12533)
Manual backport of #12382

The endpoint returning individual activities was missing access control checks, since IDs are sequential, this is not ideal.

Fixes #12333

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12533
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-05-12 04:57:33 +02:00
Mathieu Fenniak
97a0ab9833 [v15.0/forgejo] 2026-05-12 security patches (#12494)
- fix: prevent git write to wiki repo from unauthorized user via git HTTP
- fix: prevent LFS authorization token from being used for read/write access after user's access is restricted from Forgejo
- fix: prevent scoped API access (OAuth tokens, Access tokens) from accessing resources beyond their permitted scope via non-API endpoints (e.g. /user/repo/raw/...)
- fix: implementing missing OAuth validation checks, improve protections against race conditions
- fix: prevent OAuth redirect URI spoofing via non-ascii case collision
- fix: strengthen Actions Artifact V4 signature algorithm against spoofing attacks

Co-authored-by: Derzsi Dániel <daniel@tohka.us>
Co-authored-by: jvoisin <julien.voisin@dustri.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12494
2026-05-12 04:54:28 +02:00
Renovate Bot
3330f75a2e Update dependency mermaid to v11.15.0 [SECURITY] (v15.0/forgejo) (#12531)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [mermaid](https://github.com/mermaid-js/mermaid) | [`11.13.0` → `11.15.0`](https://renovatebot.com/diffs/npm/mermaid/11.13.0/11.15.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/mermaid/11.15.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/mermaid/11.13.0/11.15.0?slim=true) |

---

### Mermaid Gantt Charts are vulnerable to an Infinite Loop DoS
[CVE-2026-41150](https://nvd.nist.gov/vuln/detail/CVE-2026-41150) / [GHSA-6m6c-36f7-fhxh](https://github.com/advisories/GHSA-6m6c-36f7-fhxh)

<details>
<summary>More information</summary>

#### Details
##### Impact

Mermaid v11.14.0 and earlier are vulnerable to a denial-of-service attack when rendering gantt charts, if they use the [`excludes` attribute](https://mermaid.js.org/syntax/gantt.html?#excludes) to exclude all dates.

Example:

```
gantt
  excludes monday,tuesday,wednesday,thursday,friday,saturday,sunday
  DoS :2025-01-01, 1d
```

`mermaid.parse` is unaffected, unless you then call the `ganttDb.getTasks()` (which is called when rendering a diagram).

##### Patches

This has been patched in:

- [v11.15.0](https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.15.0) (see [faafb5d49106dd32c367f3882505f2dd625aa30e](faafb5d491))
- [v10.9.6](https://github.com/mermaid-js/mermaid/releases/tag/v10.9.6) (see [a59ea56174712ee5430dfd5bc877cb5151f501a6](a59ea56174))

##### Workarounds

There are no workarounds available without updating to a newer version of mermaid.

#### Severity
- CVSS Score: 5.3 / 10 (Medium)
- Vector String: `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:L/SC:N/SI:N/SA:L`

#### References
- [https://github.com/mermaid-js/mermaid/security/advisories/GHSA-6m6c-36f7-fhxh](https://github.com/mermaid-js/mermaid/security/advisories/GHSA-6m6c-36f7-fhxh)
- [a59ea56174)
- [faafb5d491)
- [https://github.com/mermaid-js/mermaid](https://github.com/mermaid-js/mermaid)
- [https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.15.0](https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.15.0)
- [https://github.com/mermaid-js/mermaid/releases/tag/v10.9.6](https://github.com/mermaid-js/mermaid/releases/tag/v10.9.6)

This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-6m6c-36f7-fhxh) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### Mermaid: Improper sanitization of configuration leads to CSS injection
[CVE-2026-41159](https://nvd.nist.gov/vuln/detail/CVE-2026-41159) / [GHSA-87f9-hvmw-gh4p](https://github.com/advisories/GHSA-87f9-hvmw-gh4p)

<details>
<summary>More information</summary>

#### Details
##### Impact

Mermaid's default configuration allows injecting CSS that applies outside of the Mermaid diagram via the `fontFamily`, `themeCSS`, and `altFontFamily` configuration options.

Live demo: [mermaid.live](https://mermaid.live/edit#pako:eNpNjktLxDAUhf9KvFBR6JS-60QQfODKlUvJ5k6TtsEmKTHFGUP-u-mI6Nmdy3fOPR56wwVQSBIvtXSUeAaD0e4ZlZxPDChhcLxFfwiEauOuLq_9Afv30ZpVczpaITS5kGox1qF2gfSeBwYhJAnThAyz-ewntI68vG5-0z3Z7e7IA9OQwmglB-rsKlJQwircLPgNZeAmocTPAi4GXGfHgOkQYwvqN2PUbzJuGSegA84f0a0LRyeeJI4W_xChubCPcbQD2pwbgHo4Aq2aKmvbqq3zoiu7pizqFE6RybN9VFfFY1HWXRVS-Dr_zLObrt7_V_gGGXZlGg)

Example code:

```
%%{init: {"fontFamily": "x;a{b} :not(&){background:green !important} c{d}"}}%%
flowchart LR
    A --> B
```

The injected CSS exploits stylis's `&` (scope reference) handling. `:not(&)` escapes the `#mermaid-xxx` automatic scoping, applying styles to all page elements. Global at-rules (`@font-face`, `@keyframes`, `@counter-style`) are also injectable as stylis hoists them to top level.

This allows page defacement and DOM attribute exfiltration via CSS `:has()` selectors.

##### Patches

- [v11.15.0](https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.15.0) (see [64769738d5b59211e1decb471ffbaca8afec51aa](64769738d5))
- [v10.9.6](https://github.com/mermaid-js/mermaid/releases/tag/v10.9.6) (see [a9d9f0d8eb790349121508688cd338253fd80d76](a9d9f0d8eb))

##### Workarounds

If you can't upgrade mermaid, you can set the [`secure`](https://mermaid.js.org/config/schema-docs/config.html#secure) config value in the mermaid config to avoid allowing diagrams to modify `fontFamily`, `themeCSS`, `altFontFamily`, and `themeVariables`.

Setting [`"securityLevel": "sandbox"`](https://mermaid.js.org/config/schema-docs/config.html#securitylevel)  will also prevent this.

##### Credits

Reported by @&#8203;zsxsoft on behalf of @&#8203;KeenSecurityLab

#### Severity
- CVSS Score: 5.3 / 10 (Medium)
- Vector String: `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:L/VA:N/SC:L/SI:L/SA:L`

#### References
- [https://github.com/mermaid-js/mermaid/security/advisories/GHSA-87f9-hvmw-gh4p](https://github.com/mermaid-js/mermaid/security/advisories/GHSA-87f9-hvmw-gh4p)
- [64769738d5)
- [a9d9f0d8eb)
- [https://github.com/mermaid-js/mermaid](https://github.com/mermaid-js/mermaid)
- [https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.15.0](https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.15.0)
- [https://github.com/mermaid-js/mermaid/releases/tag/v10.9.6](https://github.com/mermaid-js/mermaid/releases/tag/v10.9.6)

This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-87f9-hvmw-gh4p) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### Mermaid: Improper sanitization of `classDef` in state diagrams leads to HTML injection
[CVE-2026-41149](https://nvd.nist.gov/vuln/detail/CVE-2026-41149) / [GHSA-ghcm-xqfw-q4vr](https://github.com/advisories/GHSA-ghcm-xqfw-q4vr)

<details>
<summary>More information</summary>

#### Details
##### Impact

Under the default configuration, Mermaid state diagram's `classDef` allow DOM injection that escapes the SVG, although `<script>` tags are removed, preventing XSS.

##### Proof-of-concept

```
stateDiagram-v2
  classDef xss fill:red</style></svg><style>*{x:x;y:y;overflow:visible!important;contain:none!important;transform:none!important;filter:none!important;clip-path:none!important}</style><div style="x:x;y:y;color:red;font:5em/1 monospace;display:grid;place-items:center;z-index:2147483647;width:100vw;height:100vh;position:fixed;top:0;left:0;background:black">HACKED</div><svg><style>a:b
  [*] --> A:::xss
```

##### Patches

- [v11.15.0](https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.15.0) (see [37ff937f1da2e19f882fd1db01235db4d01f4056](37ff937f1d))
- [v10.9.6](https://github.com/mermaid-js/mermaid/releases/tag/v10.9.6) (see [4e2d512bf5bf6f9de1a8f0a48da78dc4d09ac4f3](4e2d512bf5))

##### Workarounds

If you can not update to a patched version, setting [`"securityLevel": "sandbox"`](https://mermaid.js.org/config/schema-docs/config.html#securitylevel)  will prevent this, by rendering the mermaid diagram in a sandboxed `<iframe>`.

##### Credits

Thanks to @&#8203;zsxsoft from @&#8203;KeenSecurityLab for reporting this vulnerability.

#### Severity
- CVSS Score: 5.3 / 10 (Medium)
- Vector String: `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:L/VA:N/SC:L/SI:L/SA:L`

#### References
- [https://github.com/mermaid-js/mermaid/security/advisories/GHSA-ghcm-xqfw-q4vr](https://github.com/mermaid-js/mermaid/security/advisories/GHSA-ghcm-xqfw-q4vr)
- [37ff937f1d)
- [4e2d512bf5)
- [https://github.com/mermaid-js/mermaid](https://github.com/mermaid-js/mermaid)
- [https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.15.0](https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.15.0)
- [https://github.com/mermaid-js/mermaid/releases/tag/v10.9.6](https://github.com/mermaid-js/mermaid/releases/tag/v10.9.6)
- [https://mermaid.js.org/config/schema-docs/config.html#securitylevel](https://mermaid.js.org/config/schema-docs/config.html#securitylevel)

This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-ghcm-xqfw-q4vr) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### Mermaid: Improper sanitization of `classDefs` in diagrams leads to CSS injection
[CVE-2026-41148](https://nvd.nist.gov/vuln/detail/CVE-2026-41148) / [GHSA-xcj9-5m2h-648r](https://github.com/advisories/GHSA-xcj9-5m2h-648r)

<details>
<summary>More information</summary>

#### Details
##### Details

The state diagram and any other diagram type that routes user-controlled style strings through createCssStyles parser for Mermaid v11.14.0 and earlier captures `classDef` values with an unrestricted regex:

```jison
// packages/mermaid/src/diagrams/state/parser/stateDiagram.jison:83
<CLASSDEFID>[^\n]*   { this.popState(); return 'CLASSDEF_STYLEOPTS' }
```

The value passes unsanitized through `addStyleClass()` -> `createCssStyles()` -> `style.innerHTML` (mermaidAPI.ts:418). A `}` in the value closes the generated CSS selector, and everything after becomes a new CSS rule on the page.

##### PoC

```
stateDiagram-v2
      classDef x }*{ background-image: url("http://media.giphy.com/media/SggILpMXO7Xt6/giphy.gif")}
```

Live demo:
<https://mermaid.live/edit#pako:eNpFjzFvgzAQhf-KdVNbEcBgMHhtlkqtOnSJKi8ONsYKBmRMlRTx3-skanvTfbp7996t0IxSAYPZC6_2Rmgn7O4rQ00v5nmvWnRG29OKjqI5aTcug9wZK7RiaHH9A4fO-4kliVXSiFibqbvEzWjvnHxo_fI6vR3e6cGXyX2qTcvhcYMItDMSmHeLisAqZ8UVYeUDQhx8p6ziwEIrhTtx4MNVM4nhcxztrywE0h2wVvRzoGWS_z_8rahBKvcckntgmN5OAFvhDIzUNCZZQXCR5nVaZkUEF2BVFpOcEkoxxhUuyRbB980yjStapKHqoKFlhvPtB7BFZEU>

##### Patches

This has been patched in:

- [v11.15.0](https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.15.0) (see [e9b0f34d8d82a6260077764ee45e1d7d90957a0f](e9b0f34d8d))
- [v10.9.6](https://github.com/mermaid-js/mermaid/releases/tag/v10.9.6) (see [8fead23c59166b7bab6a39eac81acebee2859102](8fead23c59))

##### Workarounds

Setting [`"securityLevel": "sandbox"`](https://mermaid.js.org/config/schema-docs/config.html#securitylevel)  will prevent this, by rendering the mermaid diagram in a sandboxed `<iframe>`.

##### Impact

 Enables page defacement, user tracking via `url()` callbacks, and DOM attribute exfiltration via CSS `:has()` selectors.

#### Severity
- CVSS Score: 5.3 / 10 (Medium)
- Vector String: `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:L/VA:N/SC:L/SI:L/SA:L`

#### References
- [https://github.com/mermaid-js/mermaid/security/advisories/GHSA-xcj9-5m2h-648r](https://github.com/mermaid-js/mermaid/security/advisories/GHSA-xcj9-5m2h-648r)
- [8fead23c59)
- [e9b0f34d8d)
- [https://github.com/mermaid-js/mermaid](https://github.com/mermaid-js/mermaid)
- [https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.15.0](https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.15.0)
- [https://github.com/mermaid-js/mermaid/releases/tag/v10.9.6](https://github.com/mermaid-js/mermaid/releases/tag/v10.9.6)
- [https://mermaid.js.org/config/schema-docs/config.html#securitylevel](https://mermaid.js.org/config/schema-docs/config.html#securitylevel)

This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-xcj9-5m2h-648r) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### Release Notes

<details>
<summary>mermaid-js/mermaid (mermaid)</summary>

### [`v11.15.0`](https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.15.0)

[Compare Source](https://github.com/mermaid-js/mermaid/compare/mermaid@11.14.0...mermaid@11.15.0)

##### Minor Changes

- [#&#8203;7174](https://github.com/mermaid-js/mermaid/pull/7174) [`0aca217`](0aca21739c) Thanks [@&#8203;milesspencer35](https://github.com/milesspencer35)! - feat(sequence): Add support for decimal start and increment values in the `autonumber` directive

- [#&#8203;7512](https://github.com/mermaid-js/mermaid/pull/7512) [`8e17492`](8e17492f73) Thanks [@&#8203;aruncveli](https://github.com/aruncveli)! - feat(flowchart): add datastore shape

  In Data flow diagrams, a datastore/warehouse/file/database is used to represent data persistence. It is denoted by a rectangle with only top and bottom borders, and can be used in flowcharts with `A@{ shape: datastore, label: "Datastore" }`.

- [#&#8203;6440](https://github.com/mermaid-js/mermaid/pull/6440) [`9ad8dde`](9ad8dde6d0) Thanks [@&#8203;yordis](https://github.com/yordis), [@&#8203;lgazo](https://github.com/lgazo)! - feat: add Event Modeling diagram

- [#&#8203;7707](https://github.com/mermaid-js/mermaid/pull/7707) [`27db774`](27db774627) Thanks [@&#8203;txmxthy](https://github.com/txmxthy)! - feat(architecture): expose four fcose layout knobs for `architecture-beta` diagrams (`nodeSeparation`, `idealEdgeLengthMultiplier`, `edgeElasticity`, `numIter`) so authors can tune layout density and spread overlapping siblings without changing diagram source

- [#&#8203;7604](https://github.com/mermaid-js/mermaid/pull/7604) [`bf9502f`](bf9502fb60) Thanks [@&#8203;M-a-c](https://github.com/M-a-c)! - feat(class): add nested namespace support for class diagrams via dot notation and syntactic nesting

  If you have namespaces in class diagrams that use `.`s already and want to render them without nesting (≤v11.14.0 behaviour), you can use set `class.hierarchicalNamespaces=false` in your mermaid config:

  ```yaml
  config:
    class:
      hierarchicalNamespaces: false
  ```

- [#&#8203;7272](https://github.com/mermaid-js/mermaid/pull/7272) [`88cdd3d`](88cdd3dc0a) Thanks [@&#8203;xinbenlv](https://github.com/xinbenlv)! - feat(sankey): add outlined label style, configurable nodeWidth/nodePadding, and custom node colors

##### Patch Changes

- [#&#8203;7737](https://github.com/mermaid-js/mermaid/pull/7737) [`e9b0f34`](e9b0f34d8d) Thanks [@&#8203;ashishjain0512](https://github.com/ashishjain0512)! - fix: prevent unbalanced CSS styles in classDefs

- [#&#8203;7737](https://github.com/mermaid-js/mermaid/pull/7737) [`37ff937`](37ff937f1d) Thanks [@&#8203;ashishjain0512](https://github.com/ashishjain0512)! - fix: create CSS styles using the CSSOM

  This removes some invalid CSS and normalizes some CSS formatting.

- [#&#8203;7508](https://github.com/mermaid-js/mermaid/pull/7508) [`bfe60cc`](bfe60cc67b) Thanks [@&#8203;biiab](https://github.com/biiab)! - fix(stateDiagram): `end note` now only closes a note when used on a new line

- [#&#8203;7737](https://github.com/mermaid-js/mermaid/pull/7737) [`faafb5d`](faafb5d491) Thanks [@&#8203;ashishjain0512](https://github.com/ashishjain0512)! - fix(gantt): add iteration limit for `excludes` field

- [#&#8203;7737](https://github.com/mermaid-js/mermaid/pull/7737) [`65f8be2`](65f8be2a42) Thanks [@&#8203;ashishjain0512](https://github.com/ashishjain0512)! - fix: disallow some CSS at-rules in custom CSS

- [#&#8203;7726](https://github.com/mermaid-js/mermaid/pull/7726) [`1502f32`](1502f32f3c) Thanks [@&#8203;aloisklink](https://github.com/aloisklink)! - fix(wardley): fix unnecessary sanitization of text

- [#&#8203;7578](https://github.com/mermaid-js/mermaid/pull/7578) [`1f98db8`](1f98db8e32) Thanks [@&#8203;Gaston202](https://github.com/Gaston202)! - fix(class): self-referential class multiplicity labels no longer rendered multiple times

  Fixes [#&#8203;7560](https://github.com/mermaid-js/mermaid/issues/7560). Resolves an issue where cardinality labels on self-referential class relationships were rendered three times due to edge splitting in the dagre layout. The fix ensures that each sub-edge only carries its relevant label positions.

- [#&#8203;7592](https://github.com/mermaid-js/mermaid/pull/7592) [`2343e38`](2343e38498) Thanks [@&#8203;knsv-bot](https://github.com/knsv-bot)! - fix(sequence): add background box behind alt/else section title labels in sequence diagrams

- [#&#8203;7589](https://github.com/mermaid-js/mermaid/pull/7589) [`7fb9509`](7fb9509b8b) Thanks [@&#8203;NYCU-Chung](https://github.com/NYCU-Chung)! - fix(block): prevent column widths from shrinking when mixing different column spans

- [#&#8203;7632](https://github.com/mermaid-js/mermaid/pull/7632) [`3f9e0f1`](3f9e0f15be) Thanks [@&#8203;ekiauhce](https://github.com/ekiauhce)! - fix(sequence): correct messageAlign label position for right-to-left arrows in sequence diagrams

- [#&#8203;7642](https://github.com/mermaid-js/mermaid/pull/7642) [`7a8fb85`](7a8fb8532c) Thanks [@&#8203;tractorjuice](https://github.com/tractorjuice)! - fix(wardley): allow hyphens in unquoted component names

  Multi-word names containing hyphens — e.g. `real-time processing`, `end-user`, `on-call engineer` — now parse without quoting, bringing the grammar in line with the OnlineWardleyMaps (OWM) convention. `A->B` (no-space arrow) still tokenises correctly.

- [#&#8203;7523](https://github.com/mermaid-js/mermaid/pull/7523) [`5144ed4`](5144ed4b13) Thanks [@&#8203;darshanr0107](https://github.com/darshanr0107)! - fix(block): Arrow blocks in block-beta diagrams not spanning the specified number of columns when using `:n` syntax.

- [#&#8203;7262](https://github.com/mermaid-js/mermaid/pull/7262) [`13d9bfa`](13d9bfa474) Thanks [@&#8203;darshanr0107](https://github.com/darshanr0107)! - fix(block): Ensure block diagram hexagon blocks respect column spanning syntax

- [#&#8203;7684](https://github.com/mermaid-js/mermaid/pull/7684) [`e14bb88`](e14bb88bdb) Thanks [@&#8203;aloisklink](https://github.com/aloisklink)! - fix: loosen `uuid` dependency range to allow v14

  Mermaid does not use any of the vulnerable code in CVE-2026-41907,
  but this allows users to silence any `npm audit` alerts on it.

- [#&#8203;7633](https://github.com/mermaid-js/mermaid/pull/7633) [`9217c0d`](9217c0d8b2) Thanks [@&#8203;Felix-Garci](https://github.com/Felix-Garci)! - fix(block): add support for all arrow types in block diagrams

- [#&#8203;7587](https://github.com/mermaid-js/mermaid/pull/7587) [`5e7eb62`](5e7eb62e3a) Thanks [@&#8203;MaddyGuthridge](https://github.com/MaddyGuthridge)! - chore: drop lodash-es in favour of es-toolkit

- [#&#8203;7693](https://github.com/mermaid-js/mermaid/pull/7693) [`afaf306`](afaf306238) Thanks [@&#8203;dull-bird](https://github.com/dull-bird)! - fix(quadrant-chart): allow CJK, emoji, Latin-1 accented characters, and other non-ASCII text in unquoted axis/quadrant/point labels.

  Previously the lexer only matched ASCII `[A-Za-z]+` for text tokens, even though the grammar referenced `UNICODE_TEXT`. Bare Chinese, Japanese, Korean, emoji, and accented Latin characters in labels caused a parse error. Added a `[^\x00-\x7F]+` lexer rule to emit `UNICODE_TEXT` and included it in the `alphaNumToken` grammar rule.

  Fixes [#&#8203;7120](https://github.com/mermaid-js/mermaid/issues/7120).

- [#&#8203;7737](https://github.com/mermaid-js/mermaid/pull/7737) [`4755553`](4755553d5f) Thanks [@&#8203;ashishjain0512](https://github.com/ashishjain0512)! - fix: improve D3 types for mermaidAPI funcs

- [#&#8203;7737](https://github.com/mermaid-js/mermaid/pull/7737) [`6476973`](64769738d5) Thanks [@&#8203;ashishjain0512](https://github.com/ashishjain0512)! - fix: handle `&` when namespacing CSS rules

- [#&#8203;7520](https://github.com/mermaid-js/mermaid/pull/7520) [`8c1a0c1`](8c1a0c1fd1) Thanks [@&#8203;RodrigojndSantos](https://github.com/RodrigojndSantos)! - fix(stateDiagram): comments starting with one `%` are no longer treated as comments

  Switch to using two `%%` if you want to write a comment.

- Updated dependencies \[[`7a8fb85`](7a8fb8532c), [`675a64c`](675a64ca0e)]:
  - [@&#8203;mermaid-js/parser](https://github.com/mermaid-js/parser)@&#8203;1.1.1

### [`v11.14.0`](https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.14.0)

[Compare Source](https://github.com/mermaid-js/mermaid/compare/mermaid@11.13.0...mermaid@11.14.0)

Thanks to our awesome mermaid community that contributed to this release: [@&#8203;ashishjain0512](https://github.com/ashishjain0512), [@&#8203;tractorjuice](https://github.com/tractorjuice), [@&#8203;autofix-ci\[bot\]](https://github.com/autofix-ci%5Bbot%5D), [@&#8203;aloisklink](https://github.com/aloisklink), [@&#8203;knsv](https://github.com/knsv), [@&#8203;kibanana](https://github.com/kibanana), [@&#8203;chandershekhar22](https://github.com/chandershekhar22), [@&#8203;khalil](https://github.com/khalil), [@&#8203;ytatsuno](https://github.com/ytatsuno), [@&#8203;sidharthv96](https://github.com/sidharthv96), [@&#8203;github-actions\[bot\]](https://github.com/github-actions%5Bbot%5D), [@&#8203;dripcoding](https://github.com/dripcoding), [@&#8203;knsv-bot](https://github.com/knsv-bot), [@&#8203;jeroensmink98](https://github.com/jeroensmink98), [@&#8203;Alex9583](https://github.com/Alex9583), [@&#8203;GhassenS](https://github.com/GhassenS), [@&#8203;omkarht](https://github.com/omkarht), [@&#8203;darshanr0107](https://github.com/darshanr0107), [@&#8203;leentaylor](https://github.com/leentaylor), [@&#8203;lee-treehouse](https://github.com/lee-treehouse), [@&#8203;veeceey](https://github.com/veeceey), [@&#8203;turntrout](https://github.com/turntrout), [@&#8203;Mermaid-Chart](https://github.com/Mermaid-Chart), [@&#8203;BambioGaming](https://github.com/BambioGaming), Claude

### Releases

#### [@&#8203;mermaid-js/examples](https://github.com/mermaid-js/examples)@&#8203;1.2.0

##### Minor Changes

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - add new TreeView diagram

#### mermaid\@&#8203;11.14.0

##### Minor Changes

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - Add Wardley Maps diagram type (beta)

  Adds Wardley Maps as a new diagram type to Mermaid (available as `wardley-beta`). Wardley Maps are visual representations of business strategy that help map value chains and component evolution.

  Features:

  - Component positioning with \[visibility, evolution] coordinates (OWM format)
  - Anchors for users/customers
  - Multiple link types: dependencies, flows, labeled links
  - Evolution arrows and trend indicators
  - Custom evolution stages with optional dual labels
  - Custom stage widths using [@&#8203;boundary](https://github.com/boundary) notation
  - Pipeline components with visibility inheritance
  - Annotations, notes, and visual elements
  - Source strategy markers: build, buy, outsource, market
  - Inertia indicators
  - Theme integration

  Implementation includes parser, D3.js renderer, unit tests, E2E tests, and comprehensive documentation.

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look styling for state diagrams

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look support for sequence diagrams with drop shadows, and enhanced styling

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: add `randomize` config option for architecture diagrams, defaulting to `false` for deterministic layout

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - feat: Add option to change timeline direction

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - Fix duplicate SVG element IDs when rendering multiple diagrams on the same page. Internal element IDs (nodes, edges, markers, clusters) are now prefixed with the diagram's SVG element ID across all diagram types. Custom CSS or JS using exact ID selectors like `#arrowhead` should use attribute-ending selectors like `[id$="-arrowhead"]` instead.

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look styling for ER diagrams

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look styling for requirement diagrams

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - feat: add theme support for data label colour in xy chart

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - feat: implement neo look styling for mindmap diagrams

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - feat: implement neo look for mermaid flowchart diagrams

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - feat: implement neo look and themes for class diagram

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - feat: add showDataLabelOutsideBar option for xy chart

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look support for timeline diagram with drop shadows, additoinal redux themes and enhanced styling

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look and themes for gitGraph diagram

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - add new TreeView diagram

##### Patch Changes

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - add link to ishikawa diagram on mermaid.js.org

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - docs: document valid duration token formats in gantt.md

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - fix: ER diagram parsing when using "1" as entity identifier on right side

  The parser was incorrectly tokenizing the second "1" in patterns like `a many to 1 1:` because the lookahead rule only checked for alphabetic characters after whitespace, not digits. Added a new lookahead pattern `"1"(?=\s+[0-9])` to correctly identify the cardinality alias before a numeric entity name.

  Fixes [#&#8203;7472](https://github.com/mermaid-js/mermaid/issues/7472)

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: scope cytoscape label style mapping to edges with labels to prevent console warnings

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - fix: support inline annotation syntax in class diagrams (class Shape <<interface>>)

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: Align branch label background with text for multi-line labels in LR GitGraph layout

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - fix: preserve cause hierarchy when ishikawa effect is indented more than causes

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - refactor: remove unused createGraphWithElements function and add regression test for open edge arrowheads

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: Prevent long pie chart titles from being clipped by expanding the viewBox

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - fix: prevent sequence diagram hang when "as" is used without a trailing space in participant declarations

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: warn when `style` statement targets a non-existent node in flowcharts

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - fix: group state diagram SVG children under single root <g> element

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - fix: Allow :::className syntax inside composite state blocks

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) Thanks [@&#8203;aloisklink](https://github.com/aloisklink), [@&#8203;BambioGaming](https://github.com/BambioGaming)! - fix: prevent escaping `<` and `&` when `htmlLabels: false`

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: treemap title and labels use theme-aware colors for dark backgrounds

- Updated dependencies \[[`efe218a`](efe218a47f)]:
  - [@&#8203;mermaid-js/parser](https://github.com/mermaid-js/parser)@&#8203;1.1.0

#### [@&#8203;mermaid-js/parser](https://github.com/mermaid-js/parser)@&#8203;1.1.0

##### Minor Changes

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - add new TreeView diagram

#### [@&#8203;mermaid-js/tiny](https://github.com/mermaid-js/tiny)@&#8203;11.14.0

##### Minor Changes

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - Add Wardley Maps diagram type (beta)

  Adds Wardley Maps as a new diagram type to Mermaid (available as `wardley-beta`). Wardley Maps are visual representations of business strategy that help map value chains and component evolution.

  Features:

  - Component positioning with \[visibility, evolution] coordinates (OWM format)
  - Anchors for users/customers
  - Multiple link types: dependencies, flows, labeled links
  - Evolution arrows and trend indicators
  - Custom evolution stages with optional dual labels
  - Custom stage widths using [@&#8203;boundary](https://github.com/boundary) notation
  - Pipeline components with visibility inheritance
  - Annotations, notes, and visual elements
  - Source strategy markers: build, buy, outsource, market
  - Inertia indicators
  - Theme integration

  Implementation includes parser, D3.js renderer, unit tests, E2E tests, and comprehensive documentation.

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - feat: implement neo look styling for state diagrams

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look support for sequence diagrams with drop shadows, and enhanced styling

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - feat: add `randomize` config option for architecture diagrams, defaulting to `false` for deterministic layout

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: Add option to change timeline direction

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) - Fix duplicate SVG element IDs when rendering multiple diagrams on the same page. Internal element IDs (nodes, edges, markers, clusters) are now prefixed with the diagram's SVG element ID across all diagram types. Custom CSS or JS using exact ID selectors like `#arrowhead` should use attribute-ending selectors like `[id$="-arrowhead"]` instead.

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look styling for ER diagrams

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look styling for requirement diagrams

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: add theme support for data label colour in xy chart

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look styling for mindmap diagrams

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look for mermaid flowchart diagrams

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look and themes for class diagram

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: add showDataLabelOutsideBar option for xy chart

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look support for timeline diagram with drop shadows, additoinal redux themes and enhanced styling

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - feat: implement neo look and themes for gitGraph diagram

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - add new TreeView diagram

##### Patch Changes

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - add link to ishikawa diagram on mermaid.js.org

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - docs: document valid duration token formats in gantt.md

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: ER diagram parsing when using "1" as entity identifier on right side

  The parser was incorrectly tokenizing the second "1" in patterns like `a many to 1 1:` because the lookahead rule only checked for alphabetic characters after whitespace, not digits. Added a new lookahead pattern `"1"(?=\s+[0-9])` to correctly identify the cardinality alias before a numeric entity name.

  Fixes [#&#8203;7472](https://github.com/mermaid-js/mermaid/issues/7472)

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: scope cytoscape label style mapping to edges with labels to prevent console warnings

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: support inline annotation syntax in class diagrams (class Shape <<interface>>)

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: Align branch label background with text for multi-line labels in LR GitGraph layout

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: preserve cause hierarchy when ishikawa effect is indented more than causes

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - refactor: remove unused createGraphWithElements function and add regression test for open edge arrowheads

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: Prevent long pie chart titles from being clipped by expanding the viewBox

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: prevent sequence diagram hang when "as" is used without a trailing space in participant declarations

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: warn when `style` statement targets a non-existent node in flowcharts

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: group state diagram SVG children under single root <g> element

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: Allow :::className syntax inside composite state blocks

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f) Thanks [@&#8203;aloisklink](https://github.com/aloisklink), [@&#8203;BambioGaming](https://github.com/BambioGaming)! - fix: prevent escaping `<` and `&` when `htmlLabels: false`

- [#&#8203;7526](https://github.com/mermaid-js/mermaid/pull/7526) [`efe218a`](efe218a47f)  - fix: treemap title and labels use theme-aware colors for dark backgrounds

- Updated dependencies \[[`efe218a`](efe218a47f)]:
  - [@&#8203;mermaid-js/parser](https://github.com/mermaid-js/parser)@&#8203;1.1.0

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - ""
- Automerge
  - Between 12:00 AM and 03:59 AM (`* 0-3 * * *`)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNzAuMjAiLCJ1cGRhdGVkSW5WZXIiOiI0My4xNzAuMjAiLCJ0YXJnZXRCcmFuY2giOiJ2MTUuMC9mb3JnZWpvIiwibGFiZWxzIjpbImRlcGVuZGVuY3ktdXBncmFkZSIsInRlc3Qvbm90LW5lZWRlZCJdfQ==-->

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12531
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
2026-05-12 03:56:15 +02:00
Mathieu Fenniak
75c3da0f92 [v15.0/forgejo] chore: PGP sign .well-known/security.txt [skip ci] (#12503)
Manual backport of #12502.

Sign the distributed version of `.well-known/security.txt`, just like https://forgejo.org/.well-known/security.txt is signed.

```
$ gpg --verify ./security.txt
gpg: Signature made Sat 09 May 2026 05:59:29 PM MDT
gpg:                using EDDSA key 1B638BDF10969D627926B8D9F585D0F99E1FB56F
gpg: Good signature from "Forgejo Security <security@forgejo.org>" [unknown]
Primary key fingerprint: 1B63 8BDF 1096 9D62 7926  B8D9 F585 D0F9 9E1F B56F
```

In the future this signature will have to be updated before the key expires; but as the expiry is already documented in the file this isn't significantly different than the current state.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12503
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-05-11 04:49:13 +02:00
limiting-factor
51866ad6b8 [v15.0/forgejo] fix: in actions_service cancelJobsForRun is bugous use killRun instead (#12492)
The conflict resolution is explained in the "Conflict" section of the commit message. I used `cherry-pick -x`. Here is the conflict for information (simple one).

```diff
unmerged   services/actions/schedule_tasks.go
@@@ -22,8 -22,7 +22,12 @@@ import

  	"code.forgejo.org/forgejo/runner/v12/act/jobparser"
  	act_model "code.forgejo.org/forgejo/runner/v12/act/model"
++<<<<<<< HEAD
 +	"github.com/robfig/cron/v3"
 +	"xorm.io/builder"
++=======
+ 	"github.com/gdgvda/cron"
++>>>>>>> b6af380324 (fix: in actions_service cancelJobsForRun is bugous use killRun instead)
  )

  // StartScheduleTasks start the task
```

---

**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12366

The cancelJobsForRun function is redundant with the killRun function
and has bugs:

- It does not use a transaction and may fail in a non-recoverable way
- It does not update the commit status of the run
- It does not set NeedRemoval to false if needed

Remove the cancelJobsForRun function and use killRun instead (fixing
forgejo/forgejo#12386). Both calls are covered by existing tests:

- TestCancelPreviousJobs
- TestCancelPreviousWithConcurrencyGroup

A new integration test TestActionsPullRequestTrustPushCancel is added
to verify that the NeedApproval field is set to false whenever a run
is cancelled (fixing forgejo/forgejo#12350).

Closes forgejo/forgejo#12350
Closes forgejo/forgejo#12386

(cherry picked from commit b6af380324)

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12492
Reviewed-by: Andreas Ahlenstorf <aahlenst@noreply.codeberg.org>
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
2026-05-09 21:00:52 +02:00
Renovate Bot
ed2a3d8681 Update module golang.org/x/net to v0.53.0 [SECURITY] (v15.0/forgejo) (#12465)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12465
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-05-08 07:57:07 +02:00
forgejo-backport-action
2c5695cd12 [v15.0/forgejo] fix: Prevent unremovable review requests after submitting pending reviews (#12470)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12302

Some notes:
- I didn't write integration tests because it's a pure bugfix that addresses implementation details of the model layer.
  - I can see interpretations of "it involves interactions with a live Forgejo server" that would cover this PR, but they don't make sense to me in context.
  - If they are expected, please let me know!
- I didn't add anything to the documentation because it's a pure bugfix - the system should always have worked this way
  - there's no value in confusing people trying to figure out how the system works now with how it didn't work in the past
- However, there IS value in informing people who may have gotten bitten by this in the past, so I think a release note makes sense
- These fixes are closely related, and the changes small, so I decided to make just one PR.
  - From a user perspective, this is just one issue, and I think in terms of release notes, it makes more sense to have just this one.
  - But I can split it up if that's preferred, ofc
- Technically, fixing only one of the underlying issues would be enough. Since this is a case of invalid states being representable, it makes sense to both try to prevent it happening in the first place, and deal with it gracefully if it does happen.
  - At the very least, fixing #12245 is required unless we want to live with data generated in the past being broken

Fixes #12243
Fixes #12245

Co-authored-by: Thomas Kolar <thomas.kolar@uni-ak.ac.at>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12470
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-05-08 07:45:15 +02:00
forgejo-backport-action
e2eb0b4a86 [v15.0/forgejo] [pagure] ensure moving all commits in a pull request (#12467)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12433

While the changes were conveyed in the pull request in its entirety, the commit
history of a pull request having more than one commit was bugged and the log
would have shown just the presence of the most recent commit event, having the
entire changes contained in a pull request.

This is a problem that was mostly noticed in the closed pull request, so it is
not as bad as it looks. Even then, if we are migrating closed pull requests, we
should do it the right way. We do not want to retain these pull requests for
archival purposes if they are not accurate.

Signed-off-by: Akashdeep Dhar <akashdeep.dhar@gmail.com>

Fixes https://forge.fedoraproject.org/forge/forge/issues/556

Co-authored-by: Akashdeep Dhar <akashdeep.dhar@gmail.com>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12467
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-05-08 07:31:49 +02:00
forgejo-backport-action
a1222ebb5b [v15.0/forgejo] refactor: clarify four different outputs that authentication methods provide (#12468)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12231

#12202 began a refactor of Forgejo's authentication implementations by providing structured data on an authentication success.  However, error cases were maintained as-is in that refactor, leaving a complex situation: what does returning an error from an authentication method mean?; does it mean that the authentication failed, or that a server error occurred?  Can another authentication still be tried?

This PR changes authentication methods so that they can return one of four things:
- `AuthenticationSuccess` with an authentication result.
- `AuthenticationNotAttempted` which indicates that no credentials relevant for this authentication method were presented.  If every method returned `AuthenticationNotAttempted`, then you would have an unauthenticated access.
- `AuthenticationAttemptedIncorrectCredential` which indicates that credentials were present and failed validation -- a situation indicating a `401 Unauthorized`.
- `AuthenticationError` which indicates that an internal server error occurred and failed authentication -- indicating a `500 Internal Server Error`.

This paves the way for one more refactor coming next: `basic.go` and `oauth2.go` perform 3-4 different authentications each (access tokens, oauth JWTs, actions tokens, actions JWTs, and username/password).  With the capability to return these more precise responses, these authentication methods can be split up into separate logic that isn't intertwined together.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12468
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-05-08 07:31:33 +02:00
forgejo-backport-action
0aa1b45956 [v15.0/forgejo] refactor: change authentication to return structured data (#12462)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12202

Currently authentication methods return information in two forms: they return who was authenticated as a `*user_model.User`, and then they insert key-values into `ctx.Data` which has critical impact on how the authenticated request is treated.  This PR changes the authentication methods to return structured data in the form of an `AuthenticationResult`, with all the key-value information in `ctx.Data` being moved into methods on the `AuthenticationResult` interface.

Authentication workflows in Forgejo are a real mess.  This is the first step in trying to clean it up and make the code predictable and reasonable, and is both follow-up work that was identified from the repo-specific access tokens (where the `"ApiTokenReducer"` key-value was added), and is pre-requisite work to future JWT enhancements that are [being discussed](https://codeberg.org/forgejo/forgejo/issues/3571#issuecomment-13268004).

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12462
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-05-08 04:07:32 +02:00
forgejo-backport-action
9381a425f9 [v15.0/forgejo] fix: paginate team members list (#12461)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12447

Fixes #12103.

Paginate the list of team members on the page for that team.

Co-authored-by: Antonin Delpeuch <antonin@delpeuch.eu>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12461
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-05-08 02:43:47 +02:00
Renovate Bot
91a44affa4 Update go toolchain directive to v1.26.3 (v15.0/forgejo) (#12456) 2026-05-07 21:57:50 +02:00
forgejo-backport-action
1b7b1e4fe5 [v15.0/forgejo] fix: make package cleanup work again (#12452)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12446

- Regression of forgejo/forgejo!11776 (and forgejo/forgejo!11881)
- Scope of the transaction is moved to a per-package cleanup rule basis.
This is also a enhancement for scaling (already deployed on Codeberg for a while).
- Package cleanup is now run with `RetryTx`, because rebuilding
  repository files runs `RetryTx` and it could indicate to retry the whole
  transaction.
- Previously it would error and say running `RetryTx` in a
  transaction was not possible, this is now possible. Nested `RetryTx` is
  always allowed, matching of which errors to retry is still the responsible
  of the inner `RetryTx`.

Co-authored-by: Gusted <postmaster@gusted.xyz>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12452
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-05-07 19:44:33 +02:00
forgejo-backport-action
80476238ab [v15.0/forgejo] fix: cleanup data before migration retry (#12422)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12370

In the case you hit some API error (Github ratelimit was often a problem) or the instance restarted in the middle of your migration, you would be left with data on the disk and/or database. Upon retrying the migration the migration code would (rightfully) fail because it's trying to migrate stuff that already exists.

This was hit so often on Codeberg it was better to force people to delete and start whole migration process again: 28ee60c91f

Delete the repository data before retrying to solve this.

Co-authored-by: Gusted <postmaster@gusted.xyz>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12422
2026-05-06 15:31:51 +02:00
forgejo-backport-action
d3dd397001 [v15.0/forgejo] fix: get tag must return the tag signature instead of commit signature (#12395)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12351

## Fix: `GET /api/v1/repos/{owner}/{repo}/git/tags/{sha}` returns empty verification for signed tags

### Problem

When an annotated tag is signed (GPG or SSH) but the underlying commit is **not** signed, the API endpoint `GET /repos/{owner}/{repo}/git/tags/{sha}` returns an empty `verification.signature` field.

This is because `ToAnnotatedTag` was calling `ToVerification(ctx, c)` with the **commit** object, which checks the commit's signature — not the tag's own signature. Since the commit is unsigned, the API returns `signature: ""` and `verified: false`.

This causes issues for tools that rely on the tag signature from the API to validate that a tag push event is from a trusted source.

### Fix

`ToAnnotatedTag` now checks if the tag has its own signature (`t.Signature != nil`). If so, it uses `ParseTagWithSignature` to verify the tag's signature and populates the `verification` field from the tag. Otherwise, it falls back to the commit signature (existing behavior for unsigned/lightweight tags).

Co-authored-by: steven.guiheux <steven.guiheux@ovhcloud.com>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12395
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
2026-05-03 06:38:17 +02:00
forgejo-backport-action
b2c9c14dea [v15.0/forgejo] fix: set repo_id for migrated attachment (#12362)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12357

Was not required until ce0a376723 added extra checks which did require `repo_id` of the attachment to be set correctly.

Co-authored-by: Gusted <postmaster@gusted.xyz>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12362
2026-05-01 03:42:32 +02:00
forgejo-backport-action
4f44317622 [v15.0/forgejo] fix(oauth): only accept refresh tokens as refresh tokens (#12354)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12291

`handleRefreshToken` never checked `token.Type == TypeRefreshToken`. When
`InvalidateRefreshTokens` is disabled, an access token could be submitted as a
`refresh_token` and exchanged for a new token pair.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

(can be removed for JavaScript changes)

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Co-authored-by: jvoisin <jvoisin@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12354
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
2026-04-30 20:10:43 +02:00
forgejo-backport-action
64c30d8034 [v15.0/forgejo] Update https://data.forgejo.org/forgejo/forgejo-build-publish action to v5.6.0 (forgejo) (#12317)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12156

Co-authored-by: Renovate Bot <bot@kriese.eu>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12317
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
2026-04-29 08:26:24 +02:00
Mathieu Fenniak
fdd794abe3 [v15.0/forgejo] fix: verify PR author has write access to head to support allow maintainers edit (#12293)
Backport: https://codeberg.org/forgejo/forgejo/pulls/12292

When a pull request is opened, the author is able to mark that pull request to "Allow edits from maintainers", which grants the maintainers of the pull request's repo access to edit the pull request branch contents.  It is possible to create a pull request where the pull request author does not have the ability to edit the pull request branch.  Due to a missing security check for this case, maintainers of the pull request repo would be granted the ability to edit the pull request branch, even if the author of the pull request did not have that ability.  By exploiting this missing security check, a user can edit any branch in a repository if they're able to fork that repository.  The issue is being fixed by restricting the scope of "Allow edits from maintainers" to only grant that access if the pull request author also had access to edit the branch.

Thanks to Arvin Shivram of Brutecat Security for discovering and responsibly disclosing the vulnerability.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12293
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
2026-04-29 05:28:56 +02:00
0ko
b05e3eb55f merge commit: [v15.0/forgejo] i18n: backport of translations from Codeberg Translate (#12306)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12306
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
2026-04-28 20:31:57 +02:00
0ko
0c1f3851da [v15.0/forgejo] i18n: backport of translations from Codeberg Translate
Translation updates that were relevant to v15 branch were picked from this commit:
5e5ad79d10

Changes to strings that are only present in the newer branches were not picked.

Below is a list of co-authors of the ported commit. It may contain co-authors who's changes were not picked due to only being relevant to newer branches.

Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: AshyPinguin <ashypinguin@noreply.codeberg.org>
Co-authored-by: Benedikt Straub <benedikt-straub@web.de>
Co-authored-by: Codeberg Translate <translate@codeberg.org>
Co-authored-by: Fjuro <fjuro@noreply.codeberg.org>
Co-authored-by: Goudarz Jafari <goudarz.jafari@gmail.com>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-authored-by: Lauri Lepik <laurilepik@noreply.codeberg.org>
Co-authored-by: Lzebulon <lzebulon@noreply.codeberg.org>
Co-authored-by: PatoFlamejanteTV <patoflamejantetv@noreply.codeberg.org>
Co-authored-by: SomeTr <sometr@noreply.codeberg.org>
Co-authored-by: TAGerritsen <tagerritsen@noreply.codeberg.org>
Co-authored-by: Tamil <tamil@noreply.codeberg.org>
Co-authored-by: Wuzzy <wuzzy@disroot.org>
Co-authored-by: arifpedia <arifpedia@gmail.com>
Co-authored-by: artnay <artnay@noreply.codeberg.org>
Co-authored-by: augustd <augustd@noreply.codeberg.org>
Co-authored-by: fserrador <fserrador@noreply.codeberg.org>
Co-authored-by: gallegonovato <gallegonovato@noreply.codeberg.org>
Co-authored-by: mahlzahn <mahlzahn@posteo.de>
Co-authored-by: rdeavila <rdeavila@noreply.codeberg.org>
Co-authored-by: universish <universish@noreply.codeberg.org>
Co-authored-by: vmtj <vmtj@noreply.codeberg.org>
Co-authored-by: xtex <xtexchooser@duck.com>
2026-04-28 22:27:43 +05:00
forgejo-backport-action
2b692f2f5c [v15.0/forgejo] fix: allow viewing Actions run triggered by deleted user (#12272)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12271

Fixes #9371.  Manually reproduced and tested by setting `action_run.triggering_user_id` to a non-existent user ID.  Manually tested that runs can be cancelled in this state as well.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

- I added test coverage for Go changes...
  - [x] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12272
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
2026-04-26 16:00:57 +02:00
Renovate Bot
483b99889b Update dependency postcss to v8.5.10 [SECURITY] (v15.0/forgejo) (#12255)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12255
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-04-24 19:56:09 +02:00
Renovate Bot
eedf05f897 Update module github.com/jackc/pgx/v5 to v5.9.2 [SECURITY] (v15.0/forgejo) (#12235)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12235
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-04-23 16:04:34 +02:00
forgejo-backport-action
ab9856c92d [v15.0/forgejo] fix: compare branches with names diff or patch (#12233)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12227

Closes: Codeberg/Community#2538
Regression of: !5385

Co-authored-by: Robert Wolff <mahlzahn@posteo.de>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12233
Reviewed-by: Robert Wolff <mahlzahn@posteo.de>
2026-04-23 01:28:54 +02:00
forgejo-backport-action
2bd5fcbd2a [v15.0/forgejo] fix: resolve outer workflow call to success, not failure, on inner job skip (#12229)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12224

If one or more of a workflow expansion's inner jobs are status "skipped", consider that as a success, rather than a failure.  Fixes https://code.forgejo.org/forgejo/runner/issues/1490.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

- I added test coverage for Go changes...
  - [x] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12229
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
2026-04-22 20:40:54 +02:00
Renovate Bot
d3b0d7f8c2 Update module golang.org/x/image to v0.39.0 (v15.0/forgejo) (#12218)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12218
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-04-22 00:03:55 +02:00
forgejo-backport-action
f47ed4c45e [v15.0/forgejo] fix: secret name-prefix regex (#12216)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12213

Fixes: #12212
Sorry for this bug, I introduced it by not testing !10682 better. Now the `forbiddenPrefixPattern`-regex is compliant to the docu:
```
It cannot start with FORGEJO_, GITEA_, GITHUB_, or a number.
```

Co-authored-by: zokki <zokki.softwareschmiede@gmail.com>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12216
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-21 20:52:57 +02:00
forgejo-backport-action
2ba190f562 [v15.0/forgejo] fix(ui): allow creating files with name starting with dash (#12215)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12214

Closes: #12204

The underlying git option was already changed in git 2.0.0 to use format `<mode>,<object>,<path>`. See ec160ae12b.

Co-authored-by: Robert Wolff <mahlzahn@posteo.de>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12215
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-21 20:34:37 +02:00
forgejo-backport-action
7aa4b29d56 [v15.0/forgejo] fix: CodeMirror e2e test (#12199)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12151

I tried a lot, but this seems to work. I know it is ugly, but checking and waiting after every action seems to make it stable. At least it succeeded five times in a row and the CI seemed to be under load due to the dependency updates. Maybe it is worth a try...

Co-authored-by: Beowulf <beowulf@beocode.eu>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12199
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-20 14:23:22 +02:00
forgejo-backport-action
4a97de08f4 [v15.0/forgejo] fix(i18n): don't log harmless missing translations as errors (#12185)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12183

Followup to https://codeberg.org/forgejo/forgejo/pulls/6203

Currently it is logging an error wherever a template is rendered in language that doesn't have all plural strings covered. For example, Esperanto isn't well maintained.

Since more plural strings were migrated in v15 to new format, these errors became much more common. However, for all languages but the base one (English) they are completely harmless and just indicate an incomplete translation.

However, for base (English) they indicate a bug in either template or en-US.json, which should be still logged as an error.

The error is being logged by `LookupPluralByForm`, which is called by `TrPluralStringAllForms` and (`TrPluralString` through `LookupPluralByCount`). I originally intended to just pass log func directly to `LookupPluralByForm` from both, but since `TrPluralString` isn't calling `LookupPluralByForm` directly, it didn't look clean, so I went with passing a flag around instead and implemented logging logic in `LookupPluralByForm` itself.

I little concern is with that the so-called "default lang" is configurable, and if it is configured to something with less than 100% completion, it will cause fallback bugs, as well as a lot of logging of this as an error. But this is why changing "default lang" is a bad idea in the first place, and broken fallbacks should be greater concern than junk in the logs.

Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12185
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-19 01:46:40 +02:00
Renovate Bot
b57ea8239e Update github.com/go-git/go-git/v5 (indirect) to v5.18.0 [SECURITY] (v15.0/forgejo) (#12177)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) | `v5.17.1` → `v5.18.0` | ![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fgo-git%2fgo-git%2fv5/v5.18.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fgo-git%2fgo-git%2fv5/v5.17.1/v5.18.0?slim=true) |

---

### go-git: Credential leak via cross-host redirect in smart HTTP transport
[GHSA-3xc5-wrhm-f963](https://github.com/advisories/GHSA-3xc5-wrhm-f963)

<details>
<summary>More information</summary>

#### Details
##### Impact
`go-git` may leak HTTP authentication credentials when following redirects during smart-HTTP clone and fetch operations.

If a remote repository responds to the initial `/info/refs` request with a redirect to a different host, go-git updates the session endpoint to the redirected location and reuses the original authentication for subsequent requests. This can result in the credentials (e.g. Authorization headers) being sent to an unintended host.

An attacker controlling or influencing the redirect target can capture these credentials and potentially reuse them to access the victim’s repositories or other resources, depending on the scope of the credential.

**Clients using `go-git` exclusively with trusted remotes (for example, GitHub or GitLab), and over a secure HTTPS connection, are not affected by this issue.** The risk arises when interacting with untrusted or misconfigured Git servers, or when using unsecured HTTP connections, which is not recommended. Such configurations also expose clients to a broader class of security risks beyond this issue, including credential interception and tampering of repository data.

##### Patches
Users should upgrade to `v5.18.0`, or `v6.0.0-alpha.2`, in order to mitigate this vulnerability. Versions prior to v5 are likely to be affected, users are recommended to upgrade to a supported `go-git` version.

The patched versions add support for configuring [followRedirects](https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpfollowRedirects). In line with upstream behaviour, the default is now `initial`, while users can opt into `FollowRedirects` or `NoFollowRedirects` programmatically.

##### Credit
Thanks to the 3 separate reports from @&#8203;celinke97, @&#8203;N0zoM1z0 and @&#8203;AyushParkara. Thanks for finding and reporting this issue privately to the `go-git` project. 🙇

#### Severity
- CVSS Score: 4.7 / 10 (Medium)
- Vector String: `CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N`

#### References
- [https://github.com/go-git/go-git/security/advisories/GHSA-3xc5-wrhm-f963](https://github.com/go-git/go-git/security/advisories/GHSA-3xc5-wrhm-f963)
- [https://github.com/go-git/go-git](https://github.com/go-git/go-git)

This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-3xc5-wrhm-f963) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### Release Notes

<details>
<summary>go-git/go-git (github.com/go-git/go-git/v5)</summary>

### [`v5.18.0`](https://github.com/go-git/go-git/releases/tag/v5.18.0)

[Compare Source](https://github.com/go-git/go-git/compare/v5.17.2...v5.18.0)

#### What's Changed

- plumbing: transport/http, Add support for followRedirects policy by [@&#8203;pjbgf](https://github.com/pjbgf) in [#&#8203;2004](https://github.com/go-git/go-git/pull/2004)

**Full Changelog**: <https://github.com/go-git/go-git/compare/v5.17.2...v5.18.0>

### [`v5.17.2`](https://github.com/go-git/go-git/releases/tag/v5.17.2)

[Compare Source](https://github.com/go-git/go-git/compare/v5.17.1...v5.17.2)

#### What's Changed

- build: Update module github.com/go-git/go-git/v5 to v5.17.1 \[SECURITY] (releases/v5.x) by [@&#8203;go-git-renovate](https://github.com/go-git-renovate)\[bot] in [#&#8203;1941](https://github.com/go-git/go-git/pull/1941)
- dotgit: skip writing pack files that already exist on disk by [@&#8203;pjbgf](https://github.com/pjbgf) in [#&#8203;1944](https://github.com/go-git/go-git/pull/1944)

⚠️ This release fixes a bug ([#&#8203;1942](https://github.com/go-git/go-git/issues/1942)) that blocked some users from upgrading to `v5.17.1`. Thanks [@&#8203;pskrbasu](https://github.com/pskrbasu) for reporting it. 🙇

**Full Changelog**: <https://github.com/go-git/go-git/compare/v5.17.1...v5.17.2>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - ""
- Automerge
  - Between 12:00 AM and 03:59 AM (`* 0-3 * * *`)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMTEuMCIsInVwZGF0ZWRJblZlciI6IjQzLjExMS4wIiwidGFyZ2V0QnJhbmNoIjoidjE1LjAvZm9yZ2VqbyIsImxhYmVscyI6WyJkZXBlbmRlbmN5LXVwZ3JhZGUiLCJ0ZXN0L25vdC1uZWVkZWQiXX0=-->

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12177
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: Renovate Bot <bot@kriese.eu>
Co-committed-by: Renovate Bot <bot@kriese.eu>
2026-04-18 05:09:31 +02:00
forgejo-backport-action
7c50c5e684 [v15.0/forgejo] fix: always include files set to be detectable for language stats (#12171)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11685

- The documentation has the correct behavior about `linguist-detectable`: In cases where a file should be considered for language statistics, regardless of its category, the linguist-detectable attribute can be used.
- This patch follows that behavior by not skipping the file even if some heuristic would've said to skip the file.
- Document the conditions in more natural language.
- Resolves forgejo/forgejo#11248

Co-authored-by: Gusted <postmaster@gusted.xyz>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12171
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-18 01:59:11 +02:00
forgejo-backport-action
facbdef3c1 [v15.0/forgejo] Exclude SSH certificate principals from output when viewing user's SSH keys (#12166)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12079

Fixes #11590

When viewing a user's SSH keys, SSH principals are now excluded from the output.  This would previously either result in a panic in [OmitEmail](cfd4d53e32/models/asymkey/ssh_key.go (L67)), if the principal name didn't contain any spaces, or truncate the principal name, if it did contain spaces.

The TestExportUserSSHKeys test was also updated and fails if the fix(commit cfcbc33af0) is reverted.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing
  - [x] `make test`
  - [x] `make test-sqlite#TestExportUserSSHKeys`

I have also manually tested the change.

The full integration tests(`make test-sqlite`) report some errors, but I get the same errors without this PR(tested on commit [6a5dda7116](6a5dda7116)).

I have not tested with the other database backends.

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

<!--start release-notes-assistant-->

## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- Bug fixes
  - [PR](https://codeberg.org/forgejo/forgejo/pulls/12166): <!--number 12166 --><!--line 0 --><!--description RXhjbHVkZSBTU0ggY2VydGlmaWNhdGUgcHJpbmNpcGFscyBmcm9tIG91dHB1dCB3aGVuIHZpZXdpbmcgdXNlcidzIFNTSCBrZXlz-->Exclude SSH certificate principals from output when viewing user's SSH keys<!--description-->
<!--end release-notes-assistant-->

Co-authored-by: Alec Walsh <code@alecwalsh.name>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12166
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-17 21:34:12 +02:00
Mathieu Fenniak
081941c0fb [v15.0/forgejo] chore: bump xorm to v1.3.9-forgejo.11 (#12154)
**Backport**: #12153

Should fix intermittent test failures in Forgejo's integration test suite, in [`TestPackageDebianConcurrent`](https://codeberg.org/forgejo-integration/forgejo/actions/runs/16661/jobs/3/attempt/1#jobstep-5-1271), where this error is occurring.  Backported to v15 as the same test is present there, to keep the LTS tests healthy.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12154
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
2026-04-17 02:28:45 +02:00
forgejo-backport-action
0403a38217 [v15.0/forgejo] fix: make /repos/search?uid=-2 return zero results, no repos with that owner (#12150)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12144

API calls to `.../api/v1/repos/search?uid=-2&archived=false` currently do not apply the filter `uid` because of the negative value.  This can occur when APIs are interacting with `${{ forgejo.token }}` and believe they're operating as the Forgejo Actions user, which has UID -2.

In combination with the security checks that occur in the `/repos/search` API to validate that repositories accessed are visible to the user, this can result in 500 error responses when a more correct expectation would be to receive no repositories:

da8898822c/routers/api/v1/repo/repo.go (L237-L242)

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12150
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-16 21:00:50 +02:00
forgejo-backport-action
b7319ea4a6 [v15.0/forgejo] fix: continued API response processing after error in /repos/search API (#12147)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12143

Prevent continued execution of some APIs with error responses that didn't correctly interrupt execution, resulting in bizarre outputs and possibly leaking secure data:

```
> GET /api/v1/repos/search?uid=-2&archived=false HTTP/2
> Host: example.org
> user-agent: curl/7.88.1
> accept: */*
> authorization: bearer ***
>
< HTTP/2 500
< server: nginx
< date: Thu, 16 Apr 2026 14:20:09 GMT
< content-type: application/json;charset=utf-8
< cache-control: max-age=0, private, must-revalidate, no-transform
< x-content-type-options: nosniff
< x-frame-options: SAMEORIGIN
<
{"message":"","url":"https://example.org/api/swagger"}
{"message":"","url":"https://example.org/api/swagger"}
{"message":"","url":"https://example.org/api/swagger"}
{"message":"","url":"https://example.org/api/swagger"}
{"message":"","url":"https://example.org/api/swagger"}
{"message":"","url":"https://example.org/api/swagger"}
{"message":"","url":"https://example.org/api/swagger"}
{"message":"","url":"https://example.org/api/swagger"}
{"message":"","url":"https://example.org/api/swagger"}
{"message":"","url":"https://example.org/api/swagger"}
{"message":"","url":"https://example.org/api/swagger"}
{"message":"","url":"https://example.org/api/swagger"}
{"message":"","url":"https://example.org/api/swagger"}
{"message":"","url":"https://example.org/api/swagger"}
{"ok":true,"data":[{"id":68,"owner":{"id":1,"login":"mfenniak", ...
```

As these errors only occur on situations that shouldn't be reproducible (minus software bugs), test automation isn't practical.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [ ] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12147
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-16 19:05:38 +02:00
forgejo-backport-action
f25747d0f6 [v15.0/forgejo] chore(Dockerfile.rootless): update shadowed env variables (#12137)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11720

This was missed in https://codeberg.org/forgejo/forgejo/pulls/11098.

See https://github.com/go-gitea/gitea/pull/17846 for why this was added in the first place.

Note that this is not backwards compatible. For users with a custom `app.ini`-config this won't work. But it also didn't work with the previous config. This change only aligns it with the default app.ini-path.

I guess this needs some more discussion.

Co-authored-by: jaylinski <jaylinski@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12137
Reviewed-by: Beowulf <beowulf@beocode.eu>
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-16 10:44:47 +02:00
forgejo-backport-action
acfea14f20 [v15.0/forgejo] chore: fix TestMirrorPull on older git (2.34.1) installation (#12136)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12134

`TestMirrorPull` is currently failing when run on git 2.34.1 in the `testing-integration.yml` workflow: https://codeberg.org/forgejo-integration/forgejo/actions/runs/16661/jobs/1/attempt/1#jobstep-5-2539  Began to fail after #11909 when additional checks on pull mirror configuration was added.

This PR addresses the issue and has been manually tested against the same git version:
```
$ git --version
git version 2.34.1

$ make test-sqlite#TestMirrorPull 2>&1
...
=== TestMirrorPull/migrate_from_repo_config_credentials (tests/integration/mirror_pull_test.go:238)
PASS
```

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12136
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-16 03:15:48 +02:00
forgejo-backport-action
1fe8a91202 [v15.0/forgejo] chore: fix cookie name comments in example ini (#12132)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12131

See: https://codeberg.org/forgejo/forgejo/pulls/10645#issuecomment-13135707

Co-authored-by: Beowulf <beowulf@beocode.eu>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12132
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-15 23:26:05 +02:00
Beowulf
7530fac1a5 [v15.0/forgejo] i18n: backport of translations from Codeberg Translate (#12129)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12129
Reviewed-by: Beowulf <beowulf@beocode.eu>
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
2026-04-15 22:43:16 +02:00
forgejo-backport-action
a6b29a65a8 [v15.0/forgejo] fix: improve runner list and details view (#12130)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12113

- shrink runner list width (use icons, move details link to runner name)
- add owner to runner details on admin view
- #11516 removed a lot details which makes it much harder for an admin to find a specific runner

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12130
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-15 22:07:25 +02:00
0ko
7b90e4bdec [v15.0/forgejo] i18n: backport of translations from Codeberg Translate
Translation updates that were relevant to v15 branch were picked from this commit:
88a0551f54

Changes to strings that are only present in the v16 branch were not picked.

Below is a list of co-authors of the ported commit. It may contain co-authors who's changes were not picked due to only being relevant to v16.

Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@noreply.codeberg.org>
Co-authored-by: AshyPinguin <ashypinguin@noreply.codeberg.org>
Co-authored-by: Atalanttore <atalanttore@noreply.codeberg.org>
Co-authored-by: Benedikt Straub <benedikt-straub@web.de>
Co-authored-by: Codeberg Translate <translate@codeberg.org>
Co-authored-by: Coral Pink <coral.pink@disr.it>
Co-authored-by: Edgarsons <edgarsons@noreply.codeberg.org>
Co-authored-by: Fjuro <fjuro@noreply.codeberg.org>
Co-authored-by: Lzebulon <lzebulon@noreply.codeberg.org>
Co-authored-by: Shadow_Glider <shadow_glider@noreply.codeberg.org>
Co-authored-by: SomeTr <sometr@noreply.codeberg.org>
Co-authored-by: Vyxie <kitakita@disroot.org>
Co-authored-by: Wuzzy <wuzzy@disroot.org>
Co-authored-by: Zughy <zughy@noreply.codeberg.org>
Co-authored-by: alissonlauffer <alissonlauffer@noreply.codeberg.org>
Co-authored-by: artnay <artnay@noreply.codeberg.org>
Co-authored-by: augustd <augustd@noreply.codeberg.org>
Co-authored-by: bahrom04 <bahrom04@noreply.codeberg.org>
Co-authored-by: bittin <bittin@noreply.codeberg.org>
Co-authored-by: butterflyoffire <butterflyoffire@noreply.codeberg.org>
Co-authored-by: cirilla <cirilla@noreply.codeberg.org>
Co-authored-by: hanklank <hanklank@noreply.codeberg.org>
Co-authored-by: justbispo <justbispo@noreply.codeberg.org>
Co-authored-by: kwoot <kwoot@noreply.codeberg.org>
Co-authored-by: mahlzahn <mahlzahn@posteo.de>
Co-authored-by: michi-onl <michi-onl@noreply.codeberg.org>
Co-authored-by: mkljczk <mkljczk@noreply.codeberg.org>
Co-authored-by: ospalh <ospalh@noreply.codeberg.org>
Co-authored-by: pakus <pakus@noreply.codeberg.org>
Co-authored-by: pixelcode <pixelcode@noreply.codeberg.org>
Co-authored-by: vmtj <vmtj@noreply.codeberg.org>
Co-authored-by: xtex <xtexchooser@duck.com>
2026-04-15 22:37:40 +05:00
0ko
83d5137efd [v15.0/forgejo] i18n: backport of translations from Codeberg Translate
Translation updates that were relevant to v15 branch were picked from this commit:
728936ccd9

Changes to strings that are only present in the v16 branch were not picked.

Below is a list of co-authors of the ported commit. It may contain co-authors who's changes were not picked due to only being relevant to v16.

Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: AndreiSerban <andreiserban@noreply.codeberg.org>
Co-authored-by: AshyPinguin <ashypinguin@noreply.codeberg.org>
Co-authored-by: Benedikt Straub <benedikt-straub@web.de>
Co-authored-by: Codeberg Translate <translate@codeberg.org>
Co-authored-by: Fjuro <fjuro@noreply.codeberg.org>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Lzebulon <lzebulon@noreply.codeberg.org>
Co-authored-by: SomeTr <sometr@noreply.codeberg.org>
Co-authored-by: Wuzzy <wuzzy@disroot.org>
Co-authored-by: Yago Raña Gayoso <yago.rana.gayoso@gmail.com>
Co-authored-by: bittin <bittin@noreply.codeberg.org>
Co-authored-by: dyniec <dyniec@noreply.codeberg.org>
Co-authored-by: hanklank <hanklank@noreply.codeberg.org>
Co-authored-by: justbispo <justbispo@noreply.codeberg.org>
Co-authored-by: krisfremen <krisfremen@noreply.codeberg.org>
Co-authored-by: mahlzahn <mahlzahn@posteo.de>
Co-authored-by: main_void <main_void@noreply.codeberg.org>
Co-authored-by: markinosags <markinosags@noreply.codeberg.org>
Co-authored-by: sindrenm <sindrenm@noreply.codeberg.org>
Co-authored-by: vitoravelino <vitoravelino@noreply.codeberg.org>
Co-authored-by: vmtj <vmtj@noreply.codeberg.org>
Co-authored-by: xtex <xtexchooser@duck.com>
Co-authored-by: yeager <yeager@noreply.codeberg.org>
2026-04-15 22:37:01 +05:00
0ko
e7e0c18841 [v15.0/forgejo] fix(ui): a few small runners UI fixes (#12114)
Followup to https://codeberg.org/forgejo/forgejo/pulls/11516.

v15-specific backport of https://codeberg.org/forgejo/forgejo/pulls/12115 fixing all i18n strings in-place.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12114
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
2026-04-14 18:50:08 +02:00
forgejo-backport-action
36bf4722a2 [v15.0/forgejo] i18n(mailer): Fix special usage of .Locale in admin_new_user (#12112)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12009

This PR is in reaction to https://codeberg.org/forgejo/forgejo/issues/1711 .

Co-authored-by: Έλλεν Εμίλια Άννα Zscheile <fogti+devel@ytrizja.de>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12112
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-14 07:22:44 +02:00
Mathieu Fenniak
c8156fbc60 [v15.0/forgejo] Revert "Improve repo file list table semantics for screen readers (#12031)" (#12094)
This reverts commit d76d6f24ce / #12031 to address the problem in #12082.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12094
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
2026-04-12 03:25:15 +02:00
forgejo-backport-action
3f65795f4d [v15.0/forgejo] fix: prevent jobs with unknown needs from running (#12077)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12046

If Forgejo encounters an Actions workflow with unknown jobs in a needs definition, Forgejo will ignore those and run the job anyway. That is bad. For example, releases could be published without any testing because the name of the testing job was misspelt.

Workflow that demonstrates the problem:

```yaml
on:
  push:
  workflow_dispatch:
jobs:
  build:
    runs-on: debian
    steps:
      - run: |
          echo "OK"
  test:
    runs-on: debian
    needs: [does-not-exist]
    steps:
      - run: |
          echo "OK"
```

Now, before a workflow is run, Forgejo will check whether all jobs referenced in `needs` exist. If any of them does not, it raises a pre-execution error which fails the workflow immediately. It also displays an appropriate error to the user, for example:

```
Workflow was not executed due to an error that blocked the execution attempt.
Job with ID test references unknown jobs in `needs`: does-not-exist.
```

Futhermore, workflows with pre-execution errors can no longer be rerun, which was previously possible.

Original issue: https://code.forgejo.org/forgejo/runner/issues/977.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

(can be removed for JavaScript changes)

- I added test coverage for Go changes...
  - [x] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Tests for JavaScript changes

(can be removed for Go changes)

- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [ ] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12077
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-10 18:22:49 +02:00
forgejo-backport-action
f777d93ebd [v15.0/forgejo] fix: display runner version on details page (#12063)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12059

Display the version of Forgejo Runner on the runner's detail page. That is useful for diagnostics.

Originally, the version was displayed on the overview page, but removed in https://codeberg.org/forgejo/forgejo/pulls/11516 due to space constraints. It should have been moved to the details page, but that never happened.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

(can be removed for JavaScript changes)

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Tests for JavaScript changes

(can be removed for Go changes)

- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [x] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12063
Reviewed-by: Andreas Ahlenstorf <aahlenst@noreply.codeberg.org>
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-09 17:57:49 +02:00
forgejo-backport-action
0af89931e2 [v15.0/forgejo] Revert "fix: add challenge for HTTP Basic Authentication to container registry" (#12060)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12058

This reverts commit 79ed45d39a.

Testing has shown that it breaks Docker 26 which is the version included in Debian Trixie.

It was originally introduced with https://codeberg.org/forgejo/forgejo/pulls/11678.

Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12060
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-09 13:08:58 +02:00
forgejo-backport-action
085d449bc5 [v15.0/forgejo] Preserve focus on star/unstar & watch/unwatch buttons after click (#12033)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11932

Fixes https://codeberg.org/forgejo/forgejo/issues/11880.

Adding `hx-on::after-settle="this.querySelector('button').focus()"` restores focus after the content has been swapped and the DOM has been setled. I tried `hx-on::after-swap` first since it's mentioned more often in https://github.com/bigskysoftware/htmx/issues/1869, but it didn't work.

The demo attached in `focus.mp4` runs through a series of repeated clicks on both buttons. You can hear the screen reader announce the button's new label when focus is restored.

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [ ] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

<!--start release-notes-assistant-->

## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- Bug fixes
  - [PR](https://codeberg.org/forgejo/forgejo/pulls/12033): <!--number 12033 --><!--line 0 --><!--description UHJlc2VydmUgZm9jdXMgb24gc3Rhci91bnN0YXIgJiB3YXRjaC91bndhdGNoIGJ1dHRvbnMgYWZ0ZXIgY2xpY2s=-->Preserve focus on star/unstar & watch/unwatch buttons after click<!--description-->
<!--end release-notes-assistant-->

Co-authored-by: Henry Catalini Smith <henry@catalinismith.se>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12033
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-08 21:47:06 +02:00
forgejo-backport-action
d76d6f24ce [v15.0/forgejo] Improve repo file list table semantics for screen readers (#12031)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11846

https://codeberg.org/forgejo/forgejo/issues/11116

To understand the impact of this you really need to listen to the before and after screen recordings attached. https://codeberg.org/forgejo/forgejo/issues/11116 is a really great bug report, and I was surprised by how disorienting this actually was when testing manually compared to my expectation after reading the issue. This is an impactful improvement!

This is my first time adding new translation strings. Excited to learn more about that if I've guessed wrong about how to do it.

To summarise, what we're doing here is as follows.

1. Address the core issue by changing the existing `<th>` elements to `<td>` so that screen readers stop semantically associating them with each row and reading them out for every table cell.
2. Replace them with real `<th>` elements that communicate the true semantic role of each column.
3. Add a `<caption>`. This serves a dual purpose: it gives the table an accessible name which improves the navigability of the page, and it gives us a place to explain to the user that the first row of the table is a little bit different because it's the latest commit rather than a file in the repo.
4. Visually hide the new caption and headings so that only screen reader users get them.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for JavaScript changes

- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [ ] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

<!--start release-notes-assistant-->

## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- Features
  - [PR](https://codeberg.org/forgejo/forgejo/pulls/12031): <!--number 12031 --><!--line 0 --><!--description SW1wcm92ZSByZXBvIGZpbGUgbGlzdCB0YWJsZSBzZW1hbnRpY3MgZm9yIHNjcmVlbiByZWFkZXJz-->Improve repo file list table semantics for screen readers<!--description-->
<!--end release-notes-assistant-->

Co-authored-by: Henry Catalini Smith <henry@catalinismith.se>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12031
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-08 21:14:39 +02:00
forgejo-backport-action
50a30eb54f [v15.0/forgejo] fix: incorrect identification of outdated run attempts (#12044)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12021

Since https://codeberg.org/forgejo/forgejo/pulls/11750, the attempt number of a Forgejo Actions job is set eagerly. When an job is ultimately not run, for example, because its `needs` weren't satisfied, it leads to discontinuous attempt numbers of completed attempts that the component for viewing action logs could not handle. This has been rectified by actually determining the number of the last attempt.

Resolves https://codeberg.org/forgejo/forgejo/issues/11994.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

(can be removed for JavaScript changes)

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Tests for JavaScript changes

(can be removed for Go changes)

- I added test coverage for JavaScript changes...
  - [x] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12044
Reviewed-by: Andreas Ahlenstorf <aahlenst@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-08 21:12:56 +02:00
forgejo-backport-action
3e17afc266 [v15.0/forgejo] fix(doctor): remove broken mergebase check (#12040)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12023

Fixes https://codeberg.org/forgejo/forgejo/issues/6163
Fixes https://codeberg.org/forgejo/forgejo/issues/3343

The merge base doctor check & fix was broken and could introduce irreversible "fixes" to wrong merge bases for PRs using the `fast-forward` and `rebase-and-merge` strategies.

The mergebase fix was originally introduced in a migration [0] to fix an existing issue [1] in the merge code in 2020.
Later added as a doctor command without explanation [2].

We decided to remove this check, as there is no apparent reason for it to still be necessary or any PR merge base state being out of sync with the current implementation.
It does more harm to keep the code in and there is no way to fix `fast-forward` and `rebase-and-merge` PRs, due to their merge implementation.

`fast-forward`: The git state inherently cannot reconstruct a merge base in this scenario by design.
`rebase-and-merge`: Is rebased on a temporary repository clone and thus might receive a different merge base, depending on how far the target branch is ahead.

[0]: 4a2b76d9c8
[1]: 4a2b76d9c8
[2]: d26885e2bf (diff-84d6d60112991392d6ba2cae4cd919fb3ee8afb8)

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Co-authored-by: Saibotk <git@saibotk.de>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12040
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-08 21:09:40 +02:00
forgejo-backport-action
437aa7f4a1 [v15.0/forgejo] fix: prevent actions workflows from generating OIDC tokens if not authorized in workflow (#12038)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12030

When using Forgejo's `enable-openid-connect: true`, a URL is generated into the actions under `$ACTIONS_ID_TOKEN_REQUEST_URL` that can be used to generate a JWT for accessing third-party resources authenticated as the action executing in this server on this repo.  However, the endpoint of that url (`.../idtoken`) had unintentionally missed a `return` on an internal server error, and was missing a check that the action actually had `enable-openid-connect: true` on it.  As a result, it was possible to generate a JWT for accessing third-party resources from an action that wasn't expected to be generating JWTs.

In terms of real-world vulnerability, the most likely risk is that the JWT could be generated from a forked pull request.  By not using the `$ACTIONS_ID_TOKEN_REQUEST_URL` and instead going directly to the `.../idtoken` endpoint, and parsing a generated JWT response that will be mixed with an error response, it's possible to retrieve a JWT in a forked pull request.  It would require a slight misconfiguration on a third-party system to allow that JWT access, but it's a plausible risk.

As this is a feature in Forgejo 15 that hasn't been released, it will be fixed in-public.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

(can be removed for JavaScript changes)

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.
    - Feature is not yet released.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12038
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-08 17:08:17 +02:00
Renovate Bot
72c9acee10 Update dependency go to v1.26.2 (v15.0/forgejo) (#12029)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12029
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: Renovate Bot <bot@kriese.eu>
Co-committed-by: Renovate Bot <bot@kriese.eu>
2026-04-08 15:34:17 +02:00
forgejo-backport-action
825c2a1744 [v15.0/forgejo] test: fix intermittent test failure in TestPackageDebianConcurrent (#11998)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11997

Fixes #11968.

Adds deadlocks to the package `RetryTx` operations, and bumps the attempt count to 3.  Technically this affects production code, not just test code, but the resulting failure is only likely to occur in highly concurrent operations when uploading packages to the debian registry for the first time for a user, which is more of a test artifact than a production likelihood.

Manually tested by modifying the `Makefile` to add the `-test.count=25` option to the test command.  This failed consistently on my dev system before this change, failed consistently after the deadlock err was added, and then succeeded consistently (multiple runs) after both changes were combined, giving me confidence that the intermittent failure is squashed.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
      - Fixing a test failure, so no new tests added, but they already failed.
- I ran...
  - [x] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11998
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-06 03:39:53 +02:00
Mathieu Fenniak
a4ec71067c [v15.0/forgejo] chore(deps): bump xorm to v1.3.9-forgejo.10 (#11992) (#11996)
Backport #11992.  As I'm intending to fix the intermittently failing test (#11968), I'd like to backport that so we don't have an intermittent failing test in the LTS, and this is a requirement.

Brings [deadlock error type](https://code.forgejo.org/xorm/xorm/pulls/95), which should allow fixing #11968.

(cherry picked from commit 15b4c5efe8)

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11992
Reviewed-by: Andreas Ahlenstorf <aahlenst@noreply.codeberg.org>
Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11996
Reviewed-by: Otto <otto@codeberg.org>
2026-04-06 02:45:39 +02:00
forgejo-backport-action
d4f7b536bc [v15.0/forgejo] fix: missing syntax dialog rounded corners (#11987)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11945

Fixes #11299

Co-authored-by: grangelouis <grangelouis@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11987
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-06 02:44:05 +02:00
forgejo-backport-action
1176b58f28 [v15.0/forgejo] refactor: reduce code duplication when accessing DefaultMaxInSize (#12000)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11999

`DefaultMaxInSize` is an internal parameter for limiting the size of `field IN (...)` clauses in DB queries, which is a reasonable thing to do -- in addition to the errors noted when [originally introduced](https://github.com/go-gitea/gitea/pull/4594), there are technical limits that apply to each of PostgreSQL, MySQL, and SQLite which would prevent an unbounded size for a query like this.  However: the size is incredibly small at 50, and, the implementation of `DefaultMaxInSize` is really wasteful with copy-and-paste coding.

This PR:
- introduces `GetByIDs` which fetches a `map[int64]*Model` from the database for an array of ID values, while respecting `IN` clause size limits
- introduces `GetByFieldIn` which fetches a `map[int64][]*Model` from the database for an array of field values, while respecting `IN` clause size limits
- uses `slices.Chunk` for other locations where queries are too complex for these implementations
- bumps the `DefaultMaxInSize` parameter from 50 to 500, a conservative increase well under known limits, but 10x the current value:
    - PostgreSQL supports up to 1GB query text size with 65,535 parameters, but I've experienced performance degradation at high value counts
    - MySQL supports 64MB query text size without known limits of parameter count
    - SQLite supports 32,766 parameters in a query

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

- I added test coverage for Go changes...
  - [x] in their respective `*_test.go` for unit tests.
      - Refactored functions are assumed to be covered by existing tests to some extent; that assumption is probably wrong but the changes here are relatively easily reviewed for correctness as well.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12000
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-05 22:53:13 +02:00
forgejo-backport-action
397c8755f2 [v15.0/forgejo] perf: bulk load resolvers & reactions on pull request comments (#11995)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11988

Optimize loading pull request review comments, which currently perform separate database queries for each comment in order to load the resolver of the comment, and the reactions on that comment, and the users on each reaction of the comments.

I stumbled across this ugly code, which enticed me to look into this:

80d840c128/routers/web/repo/pull.go (L1107-L1120)

It appeared to load the attachments from each comment on the pull request review page in separate database queries.  It turned out to be a noop, as the attachments are already loaded in bulk:

80d840c128/models/issues/comment_code.go (L120-L122)

but the `findCodeComments` method loads the "resolver doer" and the reactions one-by-one for each comment.  So I fixed that instead, and removed the ineffective deeply nested for loop.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

- I added test coverage for Go changes...
  - [x] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11995
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-05 17:30:39 +02:00
forgejo-backport-action
4ca6b703af [v15.0/forgejo] feat: support timezone in scheduled workflows (#11986)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11851

GitHub recently added the ability to [specify a time zone for scheduled workflows](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#onschedule), thereby making it possible to run scheduled workflows at a certain local time, no matter whether daylight saving time (DST) is currently active or not. Example copied from GitHub's documentation:

```yaml
on:
  schedule:
    - cron: '30 5 * * 1-5'
      timezone: "America/New_York"
```

The workflow would run at 05:30 each morning in the America/New_York timezone every Monday through Friday. `timezone` accepts IANA time zone names. If `timezone` is absent, `Etc/UTC` is used. GitHub runs workflows that were scheduled during DST jumps forward, for example, between 2 o'clock and 3 o'clock, directly after the clock jumped forward. In this case, that would be 3 o'clock.

Forgejo already supports time zones by prepending cron schedules with `TZ=<zone-id>` or `CRON_TZ=<zone-id>`:

```yaml
on:
  schedule:
    - cron: 'CRON_TZ=America/New_York 30 5 * * 1-5'
```

However, that capability is not documented. Workflows that are scheduled to run during DST changes are skipped when the clock jumps forward and run twice when it jumps backward.

This two-part PR adds support for `timezone` to improve compatibility with GitHub. `TZ` and `CRON_TZ` continue working. When both `timezone` and `TZ` or `CRON_TZ` are present, `timezone` takes precedence. When neither `timezone` nor `TZ` nor `CRON_TZ` are present, `Etc/UTC` is used as before. Because `TZ` and `CRON_TZ` were already supported by Forgejo before GitHub introduced `timezone`, `timezone` behaves during DST changes as previous versions of Forgejo, thereby deviating from GitHub. That means that workflows that are scheduled to run during DST changes are skipped when the clock jumps forward. And they run twice when it jumps backwards. However, it is generally recommended not to schedule workflows during the time of day when DST changes occur.

This part of the PR integrates the [workflow validation and parsing of the `timezone` field](https://code.forgejo.org/forgejo/runner/pulls/1454) supplied by Forgejo Runner.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

(can be removed for JavaScript changes)

- I added test coverage for Go changes...
  - [x] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Tests for JavaScript changes

(can be removed for Go changes)

- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [x] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
    - https://codeberg.org/forgejo/docs/pulls/1853
- [ ] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

<!--start release-notes-assistant-->

## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- Features
  - [PR](https://codeberg.org/forgejo/forgejo/pulls/11851): <!--number 11851 --><!--line 0 --><!--description c3VwcG9ydCBgdGltZXpvbmVgIGluIHNjaGVkdWxlZCB3b3JrZmxvd3M=-->support `timezone` in scheduled workflows<!--description-->
<!--end release-notes-assistant-->

Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11986
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-04 19:16:35 +02:00
forgejo-backport-action
06888ca34a [v15.0/forgejo] fix: store pull mirror creds encrypted with keying (#11984)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11909

Fixes #9629.

New pull mirrors have credentials stored encrypted in the database, the same as push mirrors, rather than in the repository's `config` file.  `git fetch` on the pull mirror is updated to use the credential store.  Pull mirrors will have their credentials migrated to the encrypted storage in the database as they're synced or otherwise accessed via the web UI.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11984
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-04 14:47:05 +02:00
forgejo-backport-action
6d67717a21 [v15.0/forgejo] Add aria-labels to ensure watch and star buttons always have a text label (#11967)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11878

Fixes https://codeberg.org/forgejo/forgejo/issues/6621.

The attached screen recording `before.mp4` demos the problem as described by https://codeberg.org/forgejo/forgejo/issues/6621. And `after.mp4` is the fixed version.

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [ ] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Co-authored-by: Henry Catalini Smith <henry@catalinismith.se>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11967
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-03 21:20:36 +02:00
forgejo-backport-action
6f396c2001 [v15.0/forgejo] Add aria-label="Copy" to copy button (#11970)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11895

This copy button on the pull request page lacks an accessible name. You can hear the screen reader announce it as just "button" in the screen recording `button.mp4`, and then hear the amended version in `copy.mp4` where it's announced as "copy, button".

The most relevant WCAG success criteria here is [1.1.1 Non-text content](https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [ ] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Co-authored-by: Henry Catalini Smith <henry@catalinismith.se>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11970
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-03 19:12:51 +02:00
forgejo-backport-action
7822ed2030 [v15.0/forgejo] Add aria-current="page" to active navbar items (#11969)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11887

By setting `aria-current="page"` on the active navbar item we make the information about which one corresponds to the current page available in a non-visual way. Both the attached screen recordings were produced on http://localhost:3000/pulls, so the "Pull requests" link is the active one. In `before.mp4` all the links are announced identically, and in `after.mp4` the "Pull requests" link is announced like this.

> current page, visited, link, Pull requests

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [ ] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Co-authored-by: Henry Catalini Smith <henry@catalinismith.se>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11969
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-03 18:53:23 +02:00
forgejo-backport-action
0b0aa6170f [v15.0/forgejo] Make label dropdown menu items with .tw-hidden unselectable (#11966)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11858

Fixes https://codeberg.org/forgejo/forgejo/issues/9894.

The dropdown menu items are being hidden with `.tw-hidden`. The Fomentic dropdown  makes items with `.disabled` and `.filtered` unselectable by default but can be [easily configured](https://fomantic-ui.com/modules/dropdown.html#/settings) to broaden this selector.

In the before & after GIFs attached, there is an archived label between "duplicate" and "help wanted". In the before GIF, focus disappears momentarily between the two, which is when the hidden, archived label has been programmatically focused by Fomentic. In the after GIF, focus hops instantaneously between the two selectable labels because of the broader `unselectable` selector.

### Tests for JavaScript changes

(can be removed for Go changes)

- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https
- [ ]
- [ ] ://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [ ] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Co-authored-by: Henry Catalini Smith <henry@catalinismith.se>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11966
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-03 18:45:32 +02:00
forgejo-backport-action
8b81d86c38 [v15.0/forgejo] fix: superfluous increment of ActionTask attempt breaks job view (#11964)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11956

https://codeberg.org/forgejo/forgejo/pulls/11750 missed a place where the attempt number is incremented independently. This caused the job view to break when running a reusable workflow with workflow expansion.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

(can be removed for JavaScript changes)

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Tests for JavaScript changes

(can be removed for Go changes)

- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11964
Reviewed-by: Andreas Ahlenstorf <aahlenst@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-03 18:29:31 +02:00
forgejo-backport-action
bbbdc3bf67 [v15.0/forgejo] enh: add suggestion to document reason for repository archival (#11950)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11375

Fixes #11370

<!--start release-notes-assistant-->

## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- User Interface features
  - [PR](https://codeberg.org/forgejo/forgejo/pulls/11375): <!--number 11375 --><!--line 0 --><!--description ZW5oOiBhZGQgc3VnZ2VzdGlvbiB0byBkb2N1bWVudCByZWFzb24gZm9yIHJlcG9zaXRvcnkgYXJjaGl2YWw=-->enh: add suggestion to document reason for repository archival<!--description-->
<!--end release-notes-assistant-->

Co-authored-by: Eloy <degeneloy@gmail.com>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11950
Reviewed-by: Beowulf <beowulf@beocode.eu>
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Reviewed-by: Robert Wolff <mahlzahn@posteo.de>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-02 22:10:21 +02:00
Gusted
607d031069 [v15.0/forgejo]: chore: add modernizer linter (#11949)
**Backport: !11936**

- Go has a suite of small linters that helps with modernizing Go code by using newer functions and catching small mistakes, https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize.
- Enable this linter in golangci-lint.
- There's also [`go fix`](https://go.dev/blog/gofix), which is not yet released as a linter in golangci-lint: https://github.com/golangci/golangci-lint/pull/6385

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11949
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-committed-by: Gusted <postmaster@gusted.xyz>
2026-04-02 16:54:46 +02:00
Renovate Bot
a32804bebe Update module github.com/golangci/golangci-lint/v2/cmd/golangci-lint to v2.11.4 (v15.0/forgejo) (#11948)
Co-authored-by: Renovate Bot <bot@kriese.eu>
Co-committed-by: Renovate Bot <bot@kriese.eu>
2026-04-02 03:29:58 +02:00
forgejo-backport-action
d60af095dd [v15.0/forgejo] fix: allow repository deletion when referenced by a repo-specific access token (#11933)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11927

Fixes #11919.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11933
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-01 17:35:43 +02:00
forgejo-backport-action
e919aedcec [v15.0/forgejo] fix: allow modals to be submitted multiple times (#11931)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11843

Fixes #11842.

The `once: true` was likely added to prevent multiple concurrent
submissions of the same form. This could still be worth preventing,
but I suspect it would require wrapping the supplied `onApprove`
callback with the corresponding logic, implemented manually, as I
am not aware of any native API to prevent concurrent executions of
callbacks.

## Checklist

### Tests for JavaScript changes

- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [x] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Antonin Delpeuch <antonin@delpeuch.eu>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11931
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-01 08:13:12 +02:00
forgejo-backport-action
00f9d01593 [v15.0/forgejo] ci: prevent usage of live application models & services in migrations (#11907)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11872

Prevent access to "current" application models and services from migrations via `golangci` config:

eg:
```
models/forgejo_migrations/v14a_ap-change-fedi-handle-structure.go:18:2: import 'forgejo.org/models/user' is not allowed from list 'migration-isolation': Migrations must not import application models. Application models will be the most recent schema for Forgejo, while migrations will be operating against the database schema that existed when they were authored. (depguard)
	user_model "forgejo.org/models/user"
	^
models/forgejo_migrations/v14a_ap-change-fedi-handle-structure.go:21:2: import 'forgejo.org/services/user' is not allowed from list 'migration-isolation': Migrations must not import application services. Application services will reference application models which will use the most recent schema for Forgejo, while migrations will be operating against the database schema that existed when they were authored. (depguard)
	user_service "forgejo.org/services/user"
```

Fixes an existing migration issue where it isn't possible to add a new column to the `User` table ([test errors that occur](https://codeberg.org/forgejo/forgejo/actions/runs/148633/jobs/10/attempt/1#jobstep-5-323)), but also guarantees that future migrations don't stumble into the same issue by inadvertently referencing live application code from historical migrations.

Originally identified and draft fix by @codecat w/ proposed fix in #11870.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Melissa Geels <melissa@nimble.tools>
Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11907
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-01 02:57:09 +02:00
forgejo-backport-action
2c59849072 [v15.0/forgejo] Fix @mention combobox semantics for screen reader accessibility (#11922)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11860

Fixes https://codeberg.org/forgejo/forgejo/issues/7668.

This was simpler to fix than my theory I posted on https://codeberg.org/forgejo/forgejo/issues/7668 about needing to patch the upstream package. When testing in Firefox with the developer console open and warnings enabled, I noticed a `Empty string passed to getElementById()` warning coming from `@github/combobox-nav` while attempting to manage the `aria-activedescendant` attribute. Then I found this in the [README for that project](https://github.com/github/combobox-nav).

> Markup requirements:
> - Each option needs to have role="option" and a unique id

This was easy to miss, as we're using `@github/text-expander-element` and the combobox-nav package is one of _its_ dependencies. Without a unique ID on each dropdown menu item, `@github/text-expander-element` is unable to set an appropriate `aria-activedescendant` attribute on the textarea. Once that's in place, the screen reader announcements come to life beautifully.

While working on it I noticed the emoji picker combobox was affected by the same problem and patched that as well.

Co-authored-by: Henry Catalini Smith <henry@catalinismith.se>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11922
Reviewed-by: Otto <otto@codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-01 02:17:05 +02:00
forgejo-backport-action
d42c66471a [v15.0/forgejo] fix: unique key violation in first-time concurrent debian package uploads to a user (#11906)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11881

Fixes an intermittent test failure in `TestPackageDebianConcurrent`, [example](https://codeberg.org/forgejo/forgejo/actions/runs/148747/jobs/9/attempt/1#jobstep-5-981), introduced by testing in #11776.  This one is caused by duplicate writes to `user_setting` to store a GPG key (questionable place for that...).

Confirmed reproduced in local testing and test now passes:
```
=== TestPackageDebianConcurrent (tests/test_utils.go:344)
=== TestPackageDebianConcurrent/Concurrent_Upload (tests/integration/api_packages_debian_test.go:334)
... other duplicate key violations ...
// TestPackageDebianConcurrent/Concurrent_Upload
	"2026/03/29 10:31:57 ...dels/user/setting.go:210:func1() [E] [Error SQL Query] INSERT INTO \"gtestschema\".\"user_setting\" (\"user_id\",\"setting_key\",\"setting_value\") VALUES ($1,$2,$3) RETURNING \"id\" [2 debian.key.private -----BEGIN PGP PRIVATE KEY BLOCK-----

...snip...
-----END PGP PRIVATE KEY BLOCK-----] - ERROR: duplicate key value violates unique constraint \"UQE_user_setting_key_userid\" (SQLSTATE 23505)",
PASS
```

No additional test required as it is already tripping a test failure.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. (already present and failing)
- I ran...
  - [x] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11906
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-03-31 05:32:57 +02:00
Renovate Bot
adebf2adac Update github.com/go-git/go-git/v5 (indirect) to v5.17.1 [SECURITY] (v15.0/forgejo) (#11900)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) | `v5.17.0` → `v5.17.1` | ![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fgo-git%2fgo-git%2fv5/v5.17.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fgo-git%2fgo-git%2fv5/v5.17.0/v5.17.1?slim=true) |

---

### go-git missing validation decoding Index v4 files leads to panic
[CVE-2026-33762](https://nvd.nist.gov/vuln/detail/CVE-2026-33762) / [GHSA-gm2x-2g9h-ccm8](https://github.com/advisories/GHSA-gm2x-2g9h-ccm8)

<details>
<summary>More information</summary>

#### Details
##### Impact

`go-git`’s index decoder for format version 4 fails to validate the path name prefix length before applying it to the previously decoded path name. A maliciously crafted index file can trigger an out-of-bounds slice operation, resulting in a runtime panic during normal index parsing.

This issue only affects Git index format version 4. Earlier formats (`go-git` supports only `v2` and `v3`) are not vulnerable to this issue.

An attacker able to supply a crafted `.git/index` file can cause applications using go-git to panic while reading the index. If the application does not recover from panics, this results in process termination, leading to a denial-of-service (DoS) condition.

Exploitation requires the ability to modify or inject a Git index file within the local repository in disk. This typically implies write access to the `.git` directory.

##### Patches

Users should upgrade to `v5.17.1`, or the latest `v6` [pseudo-version](https://go.dev/ref/mod#pseudo-versions), in order to mitigate this vulnerability.

##### Credit

go-git maintainers thank @&#8203;kq5y for finding and reporting this issue privately to the `go-git` project.

#### Severity
- CVSS Score: 2.8 / 10 (Low)
- Vector String: `CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:L`

#### References
- [https://github.com/go-git/go-git/security/advisories/GHSA-gm2x-2g9h-ccm8](https://github.com/go-git/go-git/security/advisories/GHSA-gm2x-2g9h-ccm8)
- [https://github.com/go-git/go-git](https://github.com/go-git/go-git)

This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-gm2x-2g9h-ccm8) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### go-git: Maliciously crafted idx file can cause asymmetric memory consumption
[CVE-2026-34165](https://nvd.nist.gov/vuln/detail/CVE-2026-34165) / [GHSA-jhf3-xxhw-2wpp](https://github.com/advisories/GHSA-jhf3-xxhw-2wpp)

<details>
<summary>More information</summary>

#### Details
##### Impact

A vulnerability has been identified in which a maliciously crafted `.idx` file can cause asymmetric memory consumption, potentially exhausting available memory and resulting in a Denial of Service (DoS) condition.

Exploitation requires write access to the local repository's `.git` directory, it order to create or alter existing `.idx` files.

##### Patches

Users should upgrade to `v5.17.1`, or the latest `v6` [pseudo-version](https://go.dev/ref/mod#pseudo-versions), in order to mitigate this vulnerability.

##### Credit

The go-git maintainers thank @&#8203;kq5y for finding and reporting this issue privately to the `go-git` project.

#### Severity
- CVSS Score: 5.0 / 10 (Medium)
- Vector String: `CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H`

#### References
- [https://github.com/go-git/go-git/security/advisories/GHSA-jhf3-xxhw-2wpp](https://github.com/go-git/go-git/security/advisories/GHSA-jhf3-xxhw-2wpp)
- [https://github.com/go-git/go-git](https://github.com/go-git/go-git)
- [https://github.com/go-git/go-git/releases/tag/v5.17.1](https://github.com/go-git/go-git/releases/tag/v5.17.1)

This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-jhf3-xxhw-2wpp) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### Release Notes

<details>
<summary>go-git/go-git (github.com/go-git/go-git/v5)</summary>

### [`v5.17.1`](https://github.com/go-git/go-git/releases/tag/v5.17.1)

[Compare Source](https://github.com/go-git/go-git/compare/v5.17.0...v5.17.1)

#### What's Changed

- build: Update module github.com/cloudflare/circl to v1.6.3 \[SECURITY] (releases/v5.x) by [@&#8203;go-git-renovate](https://github.com/go-git-renovate)\[bot] in [#&#8203;1930](https://github.com/go-git/go-git/pull/1930)
- \[v5] plumbing: format/index, Improve v4 entry name validation by [@&#8203;pjbgf](https://github.com/pjbgf) in [#&#8203;1935](https://github.com/go-git/go-git/pull/1935)
- \[v5] plumbing: format/idxfile, Fix version and fanout checks by [@&#8203;pjbgf](https://github.com/pjbgf) in [#&#8203;1937](https://github.com/go-git/go-git/pull/1937)

**Full Changelog**: <https://github.com/go-git/go-git/compare/v5.17.0...v5.17.1>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "" (UTC), Automerge - Between 12:00 AM and 03:59 AM ( * 0-3 * * * ) (UTC).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My45OS4xIiwidXBkYXRlZEluVmVyIjoiNDMuOTkuMSIsInRhcmdldEJyYW5jaCI6InYxNS4wL2Zvcmdlam8iLCJsYWJlbHMiOlsiZGVwZW5kZW5jeS11cGdyYWRlIiwidGVzdC9ub3QtbmVlZGVkIl19-->

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11900
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: Renovate Bot <bot@kriese.eu>
Co-committed-by: Renovate Bot <bot@kriese.eu>
2026-03-31 02:48:18 +02:00
Renovate Bot
e045fb9b77 Update dependency happy-dom to v20.8.9 [SECURITY] (v15.0/forgejo) (#11886)
Co-authored-by: Renovate Bot <bot@kriese.eu>
Co-committed-by: Renovate Bot <bot@kriese.eu>
2026-03-30 01:00:08 +02:00
forgejo-backport-action
a90e9b827c [v15.0/forgejo] feat: use --token-url in runner setup instructions (#11877)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11874

Use `--token-url` instead of `--token` in the runner setup instructions. `--token-url` is more secure. It was also decided [not to implement `--token`](https://code.forgejo.org/forgejo/runner/pulls/1457). The new instructions look as follows:

```
$ echo -n "a3bac733-079f-4917-ae9f-4acb99f1827b" > /path/to/runner-token
$ forgejo-runner daemon \
	--url http://192.168.178.62:3000/ \
	--uuid 5982831f-8ee7-42c7-abcc-49c7d6dba586 \
	--token-url file:///path/to/runner-token \
	--label docker:docker://node:lts
```

`--label` is also new because Forgejo Runner is inoperable when neither a runner configuration nor `--label` are present.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

(can be removed for JavaScript changes)

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Tests for JavaScript changes

(can be removed for Go changes)

- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [x] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11877
Reviewed-by: Andreas Ahlenstorf <aahlenst@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-03-29 19:28:34 +02:00
Renovate Bot
7a2bd542bd Update dependency happy-dom to v20.8.8 [SECURITY] (v15.0/forgejo) (#11839)
Co-authored-by: Renovate Bot <bot@kriese.eu>
Co-committed-by: Renovate Bot <bot@kriese.eu>
2026-03-27 06:49:10 +01:00
forgejo-backport-action
ebac8b38cb [v15.0/forgejo] fix: duplicate key violates unique constraint in concurrent debian package uploads (#11833)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11776

Fixes #11438.

Whenever a "unique constraint violation" error is encountered by package mutation, detect if a `xorm.ErrUniqueConstraintViolation` error occurs.  If it does, retry the entire transaction.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

(can be removed for JavaScript changes)

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [ ] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11833
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-03-27 01:36:18 +01:00
forgejo-backport-action
4230ba6ed0 [v15.0/forgejo] fix: out of synchronization error after interrupting a PR merge by user-agent disconnect (#11830)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11821

If the HTTP request to `/user/repo/pulls/N/merge` is cancelled by the user agent, don't stop work once we've passed validation and started to merge the PR.  Go will automatically cancel the context if the user-agent disconnects, but that can leave Forgejo in an inconsistent state -- the `git` command can be cancelled at an arbitrary location, the `branch` database table update may not be completed, timers may not be stopped, cross-references may not be populated, etc.

Added test `TestMergeHTTPRequestCancellation` stress-tests the fix by cancelling merge requests, and then verifying that the in-database repository state and in-repository database state are consistent.  I've verified that this test fails if the fix is removed -- the in-database commit and commit messages don't match the repository in all PRs.

This is a problem that likely affects other Forgejo endpoints.  For example, even the PR merge API would be impacted.  But this will be one of the most common real-world places for it to occur, so my thought is we'll see how well this fix works and what (if any) side-effects it has.  We can apply a similar pattern in other areas if they are identified as problems.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [ ] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11830
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-03-26 19:20:19 +01:00
forgejo-backport-action
0245410cdc [v15.0/forgejo] fix(api): package name in route not properly unescaped (#11829)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/11822

This pull fixes the issue described in https://codeberg.org/forgejo/forgejo/issues/11427 .

The api handler of link/unlink packages use escaped path params to find packages. It causes errors when it comes to npm packages, which contains characters like `@` and `/`.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

(can be removed for JavaScript changes)

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Tests for JavaScript changes

(can be removed for Go changes)

- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Co-authored-by: Guangxiong Lin <hi@gxlin.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11829
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-03-26 19:19:09 +01:00
Renovate Bot
88c4f035ea Update module golang.org/x/image to v0.38.0 [SECURITY] (v15.0/forgejo) (#11825)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) | [`v0.37.0` → `v0.38.0`](https://cs.opensource.google/go/x/image/+/refs/tags/v0.37.0...refs/tags/v0.38.0) | ![age](https://developer.mend.io/api/mc/badges/age/go/golang.org%2fx%2fimage/v0.38.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/golang.org%2fx%2fimage/v0.37.0/v0.38.0?slim=true) |

---

### OOM from malicious IFD offset in golang.org/x/image/tiff
[CVE-2026-33809](https://nvd.nist.gov/vuln/detail/CVE-2026-33809) / [GO-2026-4815](https://pkg.go.dev/vuln/GO-2026-4815)

<details>
<summary>More information</summary>

#### Details
A maliciously crafted TIFF file can cause image decoding to attempt to allocate up 4GiB of memory, causing either excessive resource consumption or an out-of-memory error.

#### Severity
Unknown

#### References
- [https://go.dev/cl/757660](https://go.dev/cl/757660)
- [https://go.dev/issue/78267](https://go.dev/issue/78267)

This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-4815) and the [Go Vulnerability Database](https://github.com/golang/vulndb) ([CC-BY 4.0](https://github.com/golang/vulndb#license)).
</details>

---

### Configuration

📅 **Schedule**: Branch creation - "" (UTC), Automerge - Between 12:00 AM and 03:59 AM ( * 0-3 * * * ) (UTC).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My44Ni4wIiwidXBkYXRlZEluVmVyIjoiNDMuODYuMCIsInRhcmdldEJyYW5jaCI6InYxNS4wL2Zvcmdlam8iLCJsYWJlbHMiOlsiZGVwZW5kZW5jeS11cGdyYWRlIiwidGVzdC9ub3QtbmVlZGVkIl19-->

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11825
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: Renovate Bot <bot@kriese.eu>
Co-committed-by: Renovate Bot <bot@kriese.eu>
2026-03-26 14:52:07 +01:00
523 changed files with 13669 additions and 4114 deletions

View file

@ -19,7 +19,6 @@ forgejo.org/models/auth
forgejo.org/models/db
TruncateBeans
TruncateBeansCascade
InTransaction
DumpTables
GetTableNames
extendBeansForCascade

View file

@ -164,7 +164,7 @@ jobs:
- name: build container & release
if: ${{ secrets.TOKEN != '' }}
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.5.1
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.6.0
with:
forgejo: "${{ env.GITHUB_SERVER_URL }}"
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
@ -183,7 +183,7 @@ jobs:
- name: build rootless container
if: ${{ secrets.TOKEN != '' }}
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.5.1
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.6.0
with:
forgejo: "${{ env.GITHUB_SERVER_URL }}"
owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"

View file

@ -44,7 +44,7 @@ jobs:
- uses: https://data.forgejo.org/actions/checkout@v6
- name: copy & sign
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.5.1
uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.6.0
with:
from-forgejo: ${{ vars.FORGEJO }}
to-forgejo: ${{ vars.FORGEJO }}

View file

@ -15,6 +15,7 @@ linters:
- govet
- importas
- ineffassign
- modernize
- nakedret
- nolintlint
- revive
@ -45,6 +46,25 @@ linters:
desc: use forgejo.org/modules/git instead, see https://codeberg.org/forgejo/forgejo/pulls/4941
- pkg: gopkg.in/yaml.v3
desc: use go.yaml.in/yaml instead, see https://codeberg.org/forgejo/forgejo/pulls/8956
migration-isolation:
list-mode: lax
files:
- "**/models/forgejo_migrations/**"
deny:
- pkg: "forgejo.org/models"
desc: >
Migrations must not import application models. Application models will be the most recent schema for
Forgejo, while migrations will be operating against the database schema that existed when they were
authored.
- pkg: "forgejo.org/services"
desc: >
Migrations must not import application services. Application services will reference application
models which will use the most recent schema for Forgejo, while migrations will be operating against the
database schema that existed when they were authored.
allow:
- "forgejo.org/models/db"
- "forgejo.org/models/gitea_migrations/base"
- "forgejo.org/models/gitea_migrations/test"
gocritic:
disabled-checks:
- ifElseChain
@ -333,21 +353,6 @@ linters:
- path: services/actions/trust.go
linters:
- nilnil
- path: services/auth/basic.go
linters:
- nilnil
- path: services/auth/httpsign.go
linters:
- nilnil
- path: services/auth/oauth2.go
linters:
- nilnil
- path: services/auth/reverseproxy.go
linters:
- nilnil
- path: services/auth/session.go
linters:
- nilnil
- path: services/contexttest/context_tests.go
linters:
- nilnil

View file

@ -39,7 +39,7 @@ XGO_VERSION := go-1.21.x
AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.6.1 # renovate: datasource=go
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2 # renovate: datasource=go
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.10.1 # renovate: datasource=go
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4 # renovate: datasource=go
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.2 # renovate: datasource=go
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest

View file

@ -60,9 +60,13 @@ func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.N
case tmplParser.NodeField:
nodeField := nodeCommand.Args[0].(*tmplParser.FieldNode)
if len(nodeField.Ident) != 2 || !(nodeField.Ident[0] == "locale" || nodeField.Ident[0] == "Locale") {
if len(nodeField.Ident) != 2 || nodeField.Ident[0] != "locale" {
return
}
resolvedPos := fset.PositionFor(token.Pos(nodeCommand.Pos), false)
if !strings.Contains(resolvedPos.Filename, "templates/mail/") {
handler.OnWarning(fset, token.Pos(nodeCommand.Pos), "encountered unexpected .locale usage")
}
funcname = nodeField.Ident[1]
case tmplParser.NodeVariable:

View file

@ -150,8 +150,8 @@ func runCert(ctx context.Context, c *cli.Command) error {
BasicConstraintsValid: true,
}
hosts := strings.Split(c.String("host"), ",")
for _, h := range hosts {
hosts := strings.SplitSeq(c.String("host"), ",")
for h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {

View file

@ -12,6 +12,7 @@ import (
"os"
"path"
"path/filepath"
"slices"
"strings"
"sync"
"time"
@ -83,11 +84,9 @@ func (o outputType) Join() string {
}
func (o *outputType) Set(value string) error {
for _, enum := range o.Enum {
if enum == value {
o.selected = value
return nil
}
if slices.Contains(o.Enum, value) {
o.selected = value
return nil
}
return fmt.Errorf("allowed values are %s", o.Join())
@ -250,8 +249,8 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
} else {
for _, suffix := range outputTypeEnum.Enum {
if strings.HasSuffix(fileName, "."+suffix) {
fileName = strings.TrimSuffix(fileName, "."+suffix)
if before, ok := strings.CutSuffix(fileName, "."+suffix); ok {
fileName = before
break
}
}
@ -330,14 +329,12 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
go dumpDatabase(ctx, archiveJobs, &wg, verbose)
if len(setting.CustomConf) > 0 {
wg.Add(1)
go func() {
defer wg.Done()
wg.Go(func() {
log.Info("Adding custom configuration file from %s", setting.CustomConf)
if err := addFile(archiveJobs, "app.ini", setting.CustomConf, verbose); err != nil {
fatal("Failed to include specified app.ini: %v", err)
}
}()
})
}
if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
@ -361,15 +358,13 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
log.Info("Skipping attachment data")
} else {
wg.Add(1)
go func() {
defer wg.Done()
wg.Go(func() {
if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
return addObject(archiveJobs, object, path.Join("data", "attachments", objPath), verbose)
}); err != nil {
fatal("Failed to dump attachments: %v", err)
}
}()
})
}
if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
@ -377,15 +372,13 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
} else if !setting.Packages.Enabled {
log.Info("Package registry not enabled - skipping")
} else {
wg.Add(1)
go func() {
defer wg.Done()
wg.Go(func() {
if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error {
return addObject(archiveJobs, object, path.Join("data", "packages", objPath), verbose)
}); err != nil {
fatal("Failed to dump packages: %v", err)
}
}()
})
}
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
@ -399,13 +392,11 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error {
log.Error("Failed to check if %s exists: %v", setting.Log.RootPath, err)
}
if isExist {
wg.Add(1)
go func() {
defer wg.Done()
wg.Go(func() {
if err := addRecursiveExclude(archiveJobs, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
fatal("Failed to include log: %v", err)
}
}()
})
}
}

View file

@ -143,8 +143,8 @@ func runDumpRepository(stdCtx context.Context, ctx *cli.Command) error {
opts.PullRequests = true
opts.ReleaseAssets = true
} else {
units := strings.Split(ctx.String("units"), ",")
for _, unit := range units {
units := strings.SplitSeq(ctx.String("units"), ",")
for unit := range units {
switch strings.ToLower(strings.TrimSpace(unit)) {
case "":
continue

View file

@ -457,7 +457,7 @@ INTERNAL_TOKEN =
;GLOBAL_TWO_FACTOR_REQUIREMENT = none
;;
;; Name of cookie used to store authentication information.
;COOKIE_REMEMBER_NAME = gitea_incredible
;COOKIE_REMEMBER_NAME = persistent
;;
;; Reverse proxy authentication header name of user name, email, and full name
;REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
@ -1895,7 +1895,7 @@ LEVEL = Info
;PROVIDER_CONFIG = data/sessions ; Relative paths will be made absolute against _`AppWorkPath`_.
;;
;; Session cookie name
;COOKIE_NAME = i_like_gitea
;COOKIE_NAME = session
;;
;; If you use session in https only: true or false. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
;COOKIE_SECURE =

View file

@ -9,7 +9,7 @@
# And place the original in /usr/lib/gitea with working files in /data/gitea
GITEA="/app/gitea/gitea"
WORK_DIR="/var/lib/gitea"
APP_INI="/etc/gitea/app.ini"
APP_INI="/var/lib/gitea/custom/conf/app.ini"
APP_INI_SET=""
for i in "$@"; do

26
go.mod
View file

@ -2,7 +2,7 @@ module forgejo.org
go 1.25.0
toolchain go1.26.1
toolchain go1.26.3
require (
code.forgejo.org/f3/gof3/v3 v3.11.15
@ -11,7 +11,7 @@ require (
code.forgejo.org/forgejo/go-rpmutils v1.0.0
code.forgejo.org/forgejo/levelqueue v1.0.0
code.forgejo.org/forgejo/reply v1.0.2
code.forgejo.org/forgejo/runner/v12 v12.7.3
code.forgejo.org/forgejo/runner/v12 v12.8.0
code.forgejo.org/go-chi/binding v1.0.1
code.forgejo.org/go-chi/cache v1.0.1
code.forgejo.org/go-chi/captcha v1.0.2
@ -67,7 +67,7 @@ require (
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/huandu/xstrings v1.5.0
github.com/inbucket/html2text v0.9.0
github.com/jackc/pgx/v5 v5.9.1
github.com/jackc/pgx/v5 v5.9.2
github.com/jhillyerd/enmime/v2 v2.2.0
github.com/json-iterator/go v1.1.12
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
@ -75,7 +75,7 @@ require (
github.com/klauspost/cpuid/v2 v2.2.11
github.com/markbates/goth v1.82.0
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.37
github.com/mattn/go-sqlite3 v1.14.42
github.com/meilisearch/meilisearch-go v0.36.0
github.com/mholt/archives v0.1.5
github.com/microcosm-cc/bluemonday v1.0.27
@ -102,13 +102,13 @@ require (
gitlab.com/gitlab-org/api/client-go v0.143.2
go.uber.org/mock v0.6.0
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/crypto v0.49.0
golang.org/x/image v0.37.0
golang.org/x/net v0.52.0
golang.org/x/crypto v0.50.0
golang.org/x/image v0.39.0
golang.org/x/net v0.53.0
golang.org/x/oauth2 v0.36.0
golang.org/x/sync v0.20.0
golang.org/x/sys v0.42.0
golang.org/x/text v0.35.0
golang.org/x/sys v0.43.0
golang.org/x/text v0.36.0
google.golang.org/protobuf v1.36.11
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
@ -175,7 +175,7 @@ require (
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.8.0 // indirect
github.com/go-git/go-git/v5 v5.17.0 // indirect
github.com/go-git/go-git/v5 v5.18.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-openapi/jsonpointer v0.22.4 // indirect
github.com/go-openapi/jsonreference v0.21.4 // indirect
@ -257,9 +257,9 @@ require (
go.uber.org/zap/exp v0.3.0 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/mod v0.34.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.42.0 // indirect
golang.org/x/tools v0.43.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
@ -274,4 +274,4 @@ replace github.com/gliderlabs/ssh => code.forgejo.org/forgejo/ssh v0.0.0-2024121
replace git.sr.ht/~mariusor/go-xsd-duration => code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078
replace xorm.io/xorm v1.3.9 => code.forgejo.org/xorm/xorm v1.3.9-forgejo.8
replace xorm.io/xorm v1.3.9 => code.forgejo.org/xorm/xorm v1.3.9-forgejo.11

64
go.sum
View file

@ -30,8 +30,8 @@ code.forgejo.org/forgejo/levelqueue v1.0.0 h1:9krYpU6BM+j/1Ntj6m+VCAIu0UNnne1/Uf
code.forgejo.org/forgejo/levelqueue v1.0.0/go.mod h1:fmG6zhVuqim2rxSFOoasgXO8V2W/k9U31VVYqLIRLhQ=
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U=
code.forgejo.org/forgejo/runner/v12 v12.7.3 h1:+thSawVfLeAZaWB6sYeUPvLj4lxYjCIDt/ktvkfX5Rs=
code.forgejo.org/forgejo/runner/v12 v12.7.3/go.mod h1:OO+Vy9Dww6WNV7GG/6VUWo/0WwXY+ASGlINmAfEA9Ws=
code.forgejo.org/forgejo/runner/v12 v12.8.0 h1:/MqOseYbsGaQ2qzepaZr3VyuqpESvSP/ZnC2aKfmU3g=
code.forgejo.org/forgejo/runner/v12 v12.8.0/go.mod h1:sgDAYfO4NJI1kUzGuD7klHuoFLQzWmZPw0erg7QlbJU=
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616 h1:kEZL84+02jY9RxXM4zHBWZ3Fml0B09cmP1LGkDsCfIA=
code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
code.forgejo.org/go-chi/binding v1.0.1 h1:coKNI+X1NzRN7X85LlrpvBRqk0TXpJ+ja28vusQWEuY=
@ -42,8 +42,8 @@ code.forgejo.org/go-chi/captcha v1.0.2 h1:vyHDPXkpjDv8bLO9NqtWzZayzstD/WpJ5xwEkA
code.forgejo.org/go-chi/captcha v1.0.2/go.mod h1:lxiPLcJ76UCZHoH31/Wbum4GUi2NgjfFZLrJkKv1lLE=
code.forgejo.org/go-chi/session v1.0.3 h1:ByJ9c/UC0AU57hxiGl53TXh+NdBOBwK/bhZ9jyadEwE=
code.forgejo.org/go-chi/session v1.0.3/go.mod h1:xzGtFrV/agCJoZCUhFDlqAr1he6BrAdqlaprKOB1W90=
code.forgejo.org/xorm/xorm v1.3.9-forgejo.8 h1:dsSKm2nus0NhHsqYxeuB3Gldk6TtlusD1CBGV6V1SS0=
code.forgejo.org/xorm/xorm v1.3.9-forgejo.8/go.mod h1:A7sFd3BFmRp20h6drSsCXgQRQdF8Vz8HuCSrzFS3m90=
code.forgejo.org/xorm/xorm v1.3.9-forgejo.11 h1:EXJVQ4feAFMkpFwtHAHuXkK6TLNYqabR7QTzqL8uObY=
code.forgejo.org/xorm/xorm v1.3.9-forgejo.11/go.mod h1:C18NeM/SazG/q4eqpWnoNks7GeCyRUNQIe2onniRViU=
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
code.superseriousbusiness.org/exif-terminator v0.11.1 h1:qnujLH4/Yk/CFtFMmtjozbdV6Ry5G3Q/E/mLlWm/gQI=
@ -284,8 +284,8 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66D
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM=
github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI=
github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM=
github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
@ -447,8 +447,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@ -520,8 +520,8 @@ github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkEN
github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg=
github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo=
github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/meilisearch/meilisearch-go v0.36.0 h1:N1etykTektXt5KPcSbhBO0d5Xx5NaKj4pJWEM7WA5dI=
github.com/meilisearch/meilisearch-go v0.36.0/go.mod h1:HBfHzKMxcSbTOvqdfuRA/yf6Vk9IivcwKocWRuW7W78=
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
@ -724,8 +724,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -734,12 +734,12 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.37.0 h1:ZiRjArKI8GwxZOoEtUfhrBtaCN+4b/7709dlT6SSnQA=
golang.org/x/image v0.37.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww=
golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -761,8 +761,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -793,8 +793,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -851,8 +851,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -862,8 +862,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -877,8 +877,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
@ -912,8 +912,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -995,14 +995,14 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
modernc.org/sqlite v1.48.2 h1:5CnW4uP8joZtA0LedVqLbZV5GD7F/0x91AXeSyjoh5c=
modernc.org/sqlite v1.48.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View file

@ -30,6 +30,7 @@ const (
ErrorCodeIncompleteWithMissingOutput
ErrorCodeIncompleteWithMissingMatrixDimension
ErrorCodeIncompleteWithUnknownCause
ErrorCodeUnknownJobInNeeds
)
func TranslatePreExecutionError(lang translation.Locale, run *ActionRun) string {
@ -69,6 +70,8 @@ func TranslatePreExecutionError(lang translation.Locale, run *ActionRun) string
return lang.TrString("actions.workflow.incomplete_with_missing_matrix_dimension", run.PreExecutionErrorDetails...)
case ErrorCodeIncompleteWithUnknownCause:
return lang.TrString("actions.workflow.incomplete_with_unknown_cause", run.PreExecutionErrorDetails...)
case ErrorCodeUnknownJobInNeeds:
return lang.TrString("actions.workflow.unknown_job_in_needs", run.PreExecutionErrorDetails...)
}
return fmt.Sprintf("<unsupported error: code=%v details=%#v", run.PreExecutionErrorCode, run.PreExecutionErrorDetails)
}

View file

@ -153,7 +153,9 @@ func (run *ActionRun) LoadAttributes(ctx context.Context) error {
if run.TriggerUser == nil {
u, err := user_model.GetPossibleUserByID(ctx, run.TriggerUserID)
if err != nil {
if user_model.IsErrUserNotExist(err) {
u = user_model.NewGhostUser()
} else if err != nil {
return err
}
run.TriggerUser = u
@ -255,6 +257,19 @@ func (run *ActionRun) IsDispatchedRun() bool {
return run.TriggerEvent == "workflow_dispatch"
}
// IsRunnable indicates whether this ActionRun can generally be run.
func (run *ActionRun) IsRunnable() bool {
return run.PreExecutionErrorCode == 0 && run.PreExecutionError == ""
}
// CanBeRerun indicates whether this ActionRun can be rerun.
func (run *ActionRun) CanBeRerun() bool {
if !run.IsRunnable() {
return false
}
return run.Status.IsDone()
}
func actionsCountOpenCacheKey(repoID int64) string {
return fmt.Sprintf("Actions:CountOpenActionRuns:%d", repoID)
}
@ -318,7 +333,7 @@ func UpdateRunApprovalByID(ctx context.Context, id int64, approval ApprovalType,
func GetRunsNotDoneByRepoIDAndPullRequestPosterID(ctx context.Context, repoID, pullRequestPosterID int64) ([]*ActionRun, error) {
var runs []*ActionRun
// performance relies on indexes on repo_id and status
if err := db.GetEngine(ctx).Where("repo_id=? AND pull_request_poster_id=?", repoID, pullRequestPosterID).And(builder.In("status", []Status{StatusUnknown, StatusWaiting, StatusRunning, StatusBlocked})).Find(&runs); err != nil {
if err := db.GetEngine(ctx).Where("repo_id=? AND pull_request_poster_id=?", repoID, pullRequestPosterID).And(builder.In("status", PendingStatuses())).Find(&runs); err != nil {
return nil, err
}
return runs, nil
@ -327,7 +342,7 @@ func GetRunsNotDoneByRepoIDAndPullRequestPosterID(ctx context.Context, repoID, p
func GetRunsNotDoneByRepoIDAndPullRequestID(ctx context.Context, repoID, pullRequestID int64) ([]*ActionRun, error) {
var runs []*ActionRun
// performance relies on indexes on repo_id and status
if err := db.GetEngine(ctx).Where("repo_id=? AND pull_request_id=?", repoID, pullRequestID).And(builder.In("status", []Status{StatusUnknown, StatusWaiting, StatusRunning, StatusBlocked})).Find(&runs); err != nil {
if err := db.GetEngine(ctx).Where("repo_id=? AND pull_request_id=?", repoID, pullRequestID).And(builder.In("status", PendingStatuses())).Find(&runs); err != nil {
return nil, err
}
return runs, nil

View file

@ -140,6 +140,15 @@ func (job *ActionRunJob) PrepareNextAttempt(initialStatus Status) error {
return nil
}
// CanBeRerun answers whether this ActionRunJob can be rerun. Returns true if it is done and the Run it belongs to
// is runnable. Returns false in all other cases, including when Run is nil.
func (job *ActionRunJob) CanBeRerun() bool {
if job.Run == nil || !job.Run.IsRunnable() {
return false
}
return job.Status.IsDone()
}
func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) {
var job ActionRunJob
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job)
@ -338,3 +347,17 @@ func (job *ActionRunJob) EnableOpenIDConnect() (bool, error) {
}
return jobWorkflow.EnableOpenIDConnect, nil
}
// AllNeedsExist checks whether this ActionRunJob's Needs can theoretically be met by comparing them with the supplied
// list of all job IDs that part of a particular workflow run. Returns the list of unknown job IDs found in Needs
// alongside an indicator whether the check was successful.
func (job *ActionRunJob) AllNeedsExist(allExistingJobIDs container.Set[string]) ([]string, bool) {
unknownJobIDs := []string{}
for _, need := range job.Needs {
if !allExistingJobIDs.Contains(need) {
unknownJobIDs = append(unknownJobIDs, need)
}
}
return unknownJobIDs, len(unknownJobIDs) == 0
}

View file

@ -22,6 +22,14 @@ func (jobs ActionJobList) GetRunIDs() []int64 {
})
}
func (jobs ActionJobList) GetJobIDs() container.Set[string] {
jobIDs := container.SetOf[string]()
for _, job := range jobs {
jobIDs.Add(job.JobID)
}
return jobIDs
}
func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error {
runIDs := jobs.GetRunIDs()
runs := make(map[int64]*ActionRun, len(runIDs))

View file

@ -0,0 +1,21 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package actions
import (
"testing"
"forgejo.org/modules/container"
"github.com/stretchr/testify/assert"
)
func TestActionJobList_GetJobIDs(t *testing.T) {
jobs := ActionJobList{
&ActionRunJob{JobID: "job 1"},
&ActionRunJob{JobID: "job 2"},
}
assert.Equal(t, container.SetOf("job 2", "job 1"), jobs.GetJobIDs())
}

View file

@ -8,6 +8,7 @@ import (
"forgejo.org/models/db"
"forgejo.org/models/unittest"
"forgejo.org/modules/container"
"forgejo.org/modules/timeutil"
"code.forgejo.org/forgejo/runner/v12/act/jobparser"
@ -369,3 +370,126 @@ func TestIsRequestedByRunner(t *testing.T) {
assert.False(t, emptyHandleJob.IsRequestedByRunner(&differentHandle))
}
func TestAllNeedsExist(t *testing.T) {
testCases := []struct {
name string
job ActionRunJob
existingJobIDs container.Set[string]
expectedUnknownIDs []string
ok bool
}{
{
name: "no needs",
job: ActionRunJob{Needs: nil},
existingJobIDs: container.Set[string]{},
expectedUnknownIDs: []string{},
ok: true,
},
{
name: "empty needs",
job: ActionRunJob{Needs: []string{}},
existingJobIDs: container.Set[string]{},
expectedUnknownIDs: []string{},
ok: true,
},
{
name: "satisfied needs",
job: ActionRunJob{Needs: []string{"job1", "job2"}},
existingJobIDs: container.SetOf("job2", "job1"),
expectedUnknownIDs: []string{},
ok: true,
},
{
name: "unsatisfied needs",
job: ActionRunJob{Needs: []string{"unknown", "job2"}},
existingJobIDs: container.SetOf("job2", "job1"),
expectedUnknownIDs: []string{"unknown"},
ok: false,
},
{
name: "comparison is case-sensitive",
job: ActionRunJob{Needs: []string{"Job1", "job2"}},
existingJobIDs: container.SetOf("job2", "job1"),
expectedUnknownIDs: []string{"Job1"},
ok: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
unknownIDs, ok := testCase.job.AllNeedsExist(testCase.existingJobIDs)
assert.Equal(t, testCase.ok, ok)
assert.Equal(t, testCase.expectedUnknownIDs, unknownIDs)
})
}
}
func TestActionRunJob_CanBeRerun(t *testing.T) {
testCases := []struct {
name string
job ActionRunJob
canBeRerun bool
}{
{
name: "job with unknown status",
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusUnknown},
canBeRerun: false,
},
{
name: "successful job",
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusSuccess},
canBeRerun: true,
},
{
name: "failed job",
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusFailure},
canBeRerun: true,
},
{
name: "cancelled job",
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusCancelled},
canBeRerun: true,
},
{
name: "skipped job",
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusSkipped},
canBeRerun: true,
},
{
name: "waiting job",
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusWaiting},
canBeRerun: false,
},
{
name: "blocked job",
job: ActionRunJob{Run: &ActionRun{Status: StatusSuccess}, Status: StatusBlocked},
canBeRerun: false,
},
{
name: "ActionRun is nil",
job: ActionRunJob{Run: nil, Status: StatusSuccess},
canBeRerun: false,
},
{
name: "with busy run but completed job",
job: ActionRunJob{Run: &ActionRun{Status: StatusRunning}, Status: StatusSuccess},
canBeRerun: true,
},
{
name: "with run that cannot be run",
job: ActionRunJob{
Run: &ActionRun{Status: StatusRunning, PreExecutionErrorCode: ErrorCodeEventDetectionError},
Status: StatusSuccess,
},
canBeRerun: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.canBeRerun, testCase.job.CanBeRerun())
})
}
}

View file

@ -19,9 +19,6 @@ import (
"github.com/stretchr/testify/require"
)
func TestGetRunBefore(t *testing.T) {
}
func TestSetConcurrencyGroup(t *testing.T) {
run := ActionRun{}
run.SetConcurrencyGroup("abc123")
@ -96,6 +93,86 @@ func TestIsManualRun(t *testing.T) {
assert.False(t, pushRun.IsDispatchedRun())
}
func TestActionRun_IsRunnable(t *testing.T) {
testCases := []struct {
name string
run ActionRun
isRunnable bool
}{
{
name: "valid run",
run: ActionRun{},
isRunnable: true,
},
{
name: "with pre-execution error",
run: ActionRun{PreExecutionErrorCode: ErrorCodeIncompleteRunsOnMissingOutput},
isRunnable: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.isRunnable, testCase.run.IsRunnable())
})
}
}
func TestActionRun_CanBeRerun(t *testing.T) {
testCases := []struct {
name string
run ActionRun
canBeRerun bool
}{
{
name: "run with unknown status",
run: ActionRun{Status: StatusUnknown},
canBeRerun: false,
},
{
name: "successful run",
run: ActionRun{Status: StatusSuccess},
canBeRerun: true,
},
{
name: "failed run",
run: ActionRun{Status: StatusFailure},
canBeRerun: true,
},
{
name: "cancelled run",
run: ActionRun{Status: StatusCancelled},
canBeRerun: true,
},
{
name: "skipped run",
run: ActionRun{Status: StatusSkipped},
canBeRerun: true,
},
{
name: "waiting run",
run: ActionRun{Status: StatusWaiting},
canBeRerun: false,
},
{
name: "blocked run",
run: ActionRun{Status: StatusBlocked},
canBeRerun: false,
},
{
name: "with pre-execution error",
run: ActionRun{PreExecutionErrorCode: ErrorCodeIncompleteRunsOnMissingOutput, Status: StatusSuccess},
canBeRerun: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.canBeRerun, testCase.run.CanBeRerun())
})
}
}
func TestRepoNumOpenActions(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
err := cache.Init()
@ -606,3 +683,12 @@ jobs:
assert.Zero(t, insertedJobs[1].TaskID)
assert.Equal(t, StatusWaiting, insertedJobs[1].Status)
}
func TestActionRunLoadAttributes(t *testing.T) {
run := &ActionRun{
RepoID: 10,
TriggerUserID: 1000,
}
require.NoError(t, run.LoadAttributes(t.Context()))
assert.Equal(t, "ghost", run.TriggerUser.LowerName)
}

View file

@ -5,7 +5,6 @@ package actions
import (
"context"
"time"
"forgejo.org/models/db"
repo_model "forgejo.org/models/repo"
@ -21,7 +20,7 @@ import (
type ActionSchedule struct {
ID int64
Title string
Specs []string
Specs []*ActionScheduleSpec `xorm:"-"`
RepoID int64 `xorm:"index"`
Repo *repo_model.Repository `xorm:"-"`
OwnerID int64 `xorm:"index"`
@ -73,25 +72,12 @@ func CreateScheduleTask(ctx context.Context, rows []*ActionSchedule) error {
return err
}
// Loop through each schedule spec and create a new spec row
now := time.Now()
for _, spec := range row.Specs {
specRow := &ActionScheduleSpec{
RepoID: row.RepoID,
ScheduleID: row.ID,
Spec: spec,
}
// Parse the spec and check for errors
schedule, err := specRow.Parse()
if err != nil {
continue // skip to the next spec if there's an error
}
specRow.Next = timeutil.TimeStamp(schedule.Next(now).Unix())
spec.ScheduleID = row.ID
spec.RepoID = row.RepoID
// Insert the new schedule spec row
if err = db.Insert(ctx, specRow); err != nil {
if err = db.Insert(ctx, spec); err != nil {
return err
}
}

View file

@ -10,6 +10,7 @@ import (
"forgejo.org/models/db"
repo_model "forgejo.org/models/repo"
"forgejo.org/modules/optional"
"forgejo.org/modules/timeutil"
"github.com/robfig/cron/v3"
@ -27,13 +28,28 @@ type ActionScheduleSpec struct {
// started or this entry's schedule is unsatisfiable
Next timeutil.TimeStamp `xorm:"index"`
// Prev is the last time this job was run, or the zero time if never.
Prev timeutil.TimeStamp
Spec string
Prev timeutil.TimeStamp
Spec string
TimeZone optional.Option[string]
Created timeutil.TimeStamp `xorm:"created"`
Updated timeutil.TimeStamp `xorm:"updated"`
}
func NewActionScheduleSpec(cron string, tz optional.Option[string], referenceTime time.Time) (*ActionScheduleSpec, error) {
spec := &ActionScheduleSpec{
Spec: cron,
TimeZone: tz,
}
cronSchedule, err := spec.Parse()
if err != nil {
return nil, err
}
spec.Next = timeutil.TimeStamp(cronSchedule.Next(referenceTime).Unix())
return spec, nil
}
// Parse parses the spec and returns a cron.Schedule
// Unlike the default cron parser, Parse uses UTC timezone as the default if none is specified.
func (s *ActionScheduleSpec) Parse() (cron.Schedule, error) {
@ -43,19 +59,29 @@ func (s *ActionScheduleSpec) Parse() (cron.Schedule, error) {
return nil, err
}
// If the spec has specified a timezone, use it
if strings.HasPrefix(s.Spec, "TZ=") || strings.HasPrefix(s.Spec, "CRON_TZ=") {
return schedule, nil
}
specSchedule, ok := schedule.(*cron.SpecSchedule)
// If it's not a spec schedule, like "@every 5m", timezone is not relevant
if !ok {
return schedule, nil
}
// Set the timezone to UTC
specSchedule.Location = time.UTC
// If `timezone` is not defined in the workflow, but the spec includes a timezone, use it.
if !s.TimeZone.Has() && (strings.HasPrefix(s.Spec, "TZ=") || strings.HasPrefix(s.Spec, "CRON_TZ=")) {
return schedule, nil
}
var location *time.Location
if present, tz := s.TimeZone.Get(); present {
location, err = time.LoadLocation(tz)
if err != nil {
return nil, err
}
} else {
// UTC is the default time zone.
location = time.UTC
}
specSchedule.Location = location
return specSchedule, nil
}

View file

@ -7,6 +7,8 @@ import (
"testing"
"time"
"forgejo.org/modules/optional"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -21,50 +23,105 @@ func TestActionScheduleSpec_Parse(t *testing.T) {
}()
time.Local = tz
now, err := time.Parse(time.RFC3339, "2024-07-31T15:47:55+08:00")
require.NoError(t, err)
tests := []struct {
name string
spec string
want string
wantErr assert.ErrorAssertionFunc
name string
refTime time.Time
spec string
timeZone string
want string
wantErr assert.ErrorAssertionFunc
}{
{
name: "regular",
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
spec: "0 10 * * *",
want: "2024-07-31T10:00:00Z",
wantErr: assert.NoError,
},
{
name: "invalid",
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
spec: "0 10 * *",
want: "",
wantErr: assert.Error,
},
{
name: "with timezone",
name: "with TZ in cron schedule",
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
spec: "TZ=America/New_York 0 10 * * *",
want: "2024-07-31T14:00:00Z",
wantErr: assert.NoError,
},
{
name: "timezone irrelevant",
name: "with CRON_TZ in cron schedule",
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
spec: "CRON_TZ=America/New_York 0 10 * * *",
want: "2024-07-31T14:00:00Z",
wantErr: assert.NoError,
},
{
name: "with separate time zone",
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
spec: "0 10 * * *",
timeZone: "America/New_York",
want: "2024-07-31T14:00:00Z",
wantErr: assert.NoError,
},
{
name: "separate time zone takes precedence over inlined time zone",
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
spec: "CRON_TZ=Europe/Berlin 0 10 * * *",
timeZone: "America/New_York",
want: "2024-07-31T14:00:00Z",
wantErr: assert.NoError,
},
{
name: "time zone irrelevant",
refTime: time.Date(2024, 7, 31, 15, 47, 55, 0, time.Local),
spec: "@every 5m",
want: "2024-07-31T07:52:55Z",
wantErr: assert.NoError,
},
{
// The various cron implementations handle the DST jump forwards differently. The most popular approaches
// are (a) scheduling all jobs at 3 o'clock that were supposed to run between 2 and 3 o'clock, or (b)
// skipping the execution on that day because any time between 2 and 3 o'clock never happened. Forgejo uses
// option B because the code it inherited already did that and was exposed to users.
name: "skips execution during DST jump forwards",
refTime: time.Date(2025, 3, 30, 1, 5, 0, 0, time.UTC),
spec: "10 2 * * *", // The clock jumps at 2 o'clock to 3 o'clock.
timeZone: "Europe/Berlin",
want: "2025-03-31T00:10:00Z",
wantErr: assert.NoError,
},
{
name: "executes a first time before DST jump backwards",
refTime: time.Date(2025, 10, 26, 0, 5, 0, 0, time.UTC),
spec: "10 2 * * *", // The clock jumps at 3 o'clock to 2 o'clock.
timeZone: "Europe/Berlin",
want: "2025-10-26T00:10:00Z",
wantErr: assert.NoError,
},
{
name: "executes a second time after DST jump backwards",
refTime: time.Date(2025, 10, 26, 1, 5, 0, 0, time.UTC),
spec: "10 2 * * *", // The clock jumps at 3 o'clock to 2 o'clock.
timeZone: "Europe/Berlin",
want: "2025-10-26T01:10:00Z",
wantErr: assert.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &ActionScheduleSpec{
Spec: tt.spec,
Spec: tt.spec,
TimeZone: optional.FromNonDefault(tt.timeZone),
}
got, err := s.Parse()
tt.wantErr(t, err)
if err == nil {
assert.Equal(t, tt.want, got.Next(now).UTC().Format(time.RFC3339))
assert.Equal(t, tt.want, got.Next(tt.refTime).UTC().Format(time.RFC3339))
}
})
}

View file

@ -0,0 +1,102 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package actions
import (
"testing"
"time"
"forgejo.org/models/db"
"forgejo.org/models/repo"
"forgejo.org/models/unittest"
"forgejo.org/models/user"
"forgejo.org/modules/optional"
"forgejo.org/modules/timeutil"
"forgejo.org/modules/webhook"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestScheduleCreateScheduleTask(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
user2 := unittest.AssertExistsAndLoadBean(t, &user.User{ID: 2})
repo62 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 62, Name: "test_workflows", OwnerID: user2.ID})
content := `
on:
push:
schedule:
- cron: "2 13 * * *"
- cron: "03 13 * * *"
timezone: Europe/Paris
jobs:
test:
runs-on: debian
steps:
- run: |
echo "OK"
`
referenceTime := time.Date(2026, 3, 27, 17, 41, 21, 0, time.UTC)
specWithoutTZ, err := NewActionScheduleSpec("2 13 * * *", optional.None[string](), referenceTime)
require.NoError(t, err)
specWithTZ, err := NewActionScheduleSpec("3 13 * * *", optional.Some("Europe/Paris"), referenceTime)
require.NoError(t, err)
schedule := &ActionSchedule{
Title: ".forgejo/workflows/test.yaml",
Specs: []*ActionScheduleSpec{specWithoutTZ, specWithTZ},
RepoID: repo62.ID,
OwnerID: user2.ID,
WorkflowID: "test.yaml",
WorkflowDirectory: ".forgejo/workflows",
TriggerUserID: -2,
Ref: "main",
CommitSHA: "6af834a5bc97c1a337eb3a21d26903c5cdceca0c",
Event: webhook.HookEventPush,
EventPayload: "{\"action\":\"schedule\"}",
Content: []byte(content),
}
err = CreateScheduleTask(t.Context(), []*ActionSchedule{schedule})
require.NoError(t, err)
schedules, err := db.Find[ActionSchedule](t.Context(), FindScheduleOptions{OwnerID: user2.ID, RepoID: repo62.ID})
require.NoError(t, err)
require.Len(t, schedules, 1)
assert.NotZero(t, schedules[0].ID)
assert.Equal(t, ".forgejo/workflows/test.yaml", schedules[0].Title)
assert.Equal(t, "test.yaml", schedules[0].WorkflowID)
assert.Equal(t, ".forgejo/workflows", schedules[0].WorkflowDirectory)
assert.Equal(t, int64(-2), schedules[0].TriggerUserID)
assert.Equal(t, "main", schedules[0].Ref)
assert.Equal(t, "6af834a5bc97c1a337eb3a21d26903c5cdceca0c", schedules[0].CommitSHA)
assert.Equal(t, webhook.HookEventPush, schedules[0].Event)
assert.JSONEq(t, "{\"action\":\"schedule\"}", schedules[0].EventPayload)
assert.Equal(t, []byte(content), schedules[0].Content)
specs, total, err := FindSpecs(t.Context(), FindSpecOptions{RepoID: repo62.ID})
require.NoError(t, err)
assert.Equal(t, int64(2), total)
assert.NotZero(t, specs[0].ID)
assert.Equal(t, schedules[0].ID, specs[0].ScheduleID)
assert.Equal(t, timeutil.TimeStamp(1774699380), specs[0].Next)
assert.Equal(t, "3 13 * * *", specs[0].Spec)
assert.Equal(t, optional.Some("Europe/Paris"), specs[0].TimeZone)
assert.Zero(t, specs[0].Prev)
assert.NotZero(t, specs[1].ID)
assert.Equal(t, schedules[0].ID, specs[1].ScheduleID)
assert.Equal(t, timeutil.TimeStamp(1774702920), specs[1].Next)
assert.Equal(t, "2 13 * * *", specs[1].Spec)
assert.Equal(t, optional.None[string](), specs[1].TimeZone)
assert.Zero(t, specs[1].Prev)
}

View file

@ -4,6 +4,8 @@
package actions
import (
"slices"
"forgejo.org/modules/translation"
runnerv1 "code.forgejo.org/forgejo/actions-proto/runner/v1"
@ -107,12 +109,7 @@ func (s Status) IsBlocked() bool {
// In returns whether s is one of the given statuses
func (s Status) In(statuses ...Status) bool {
for _, v := range statuses {
if s == v {
return true
}
}
return false
return slices.Contains(statuses, s)
}
func (s Status) AsResult() runnerv1.Result {

View file

@ -451,9 +451,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner, requestKey,
}
// Placeholder tasks are created when the status/content of an [ActionRunJob] is resolved by Forgejo without dispatch to
// a runner, specifically in the case of a workflow call's outer job. It is the responsibility of the caller to
// increment the job's Attempt field before invoking this method, and to update that field in the database, so that
// reruns can function for placeholder tasks and provide updated outputs.
// a runner, specifically in the case of a workflow call's outer job.
func CreatePlaceholderTask(ctx context.Context, job *ActionRunJob, outputs map[string]string) (*ActionTask, error) {
actionTask := &ActionTask{
JobID: job.ID,

View file

@ -132,12 +132,7 @@ func (at ActionType) String() string {
}
func (at ActionType) InActions(actions ...string) bool {
for _, action := range actions {
if action == at.String() {
return true
}
}
return false
return slices.Contains(actions, at.String())
}
// Action represents user operation type and other information to
@ -817,3 +812,36 @@ func FixActionCreatedUnixString(ctx context.Context) (int64, error) {
}
return 0, nil
}
func (a *Action) IsActionPrivate(ctx context.Context) (bool, error) {
if a.IsPrivate {
return true, nil
}
a.loadRepo(ctx)
if a.Repo == nil {
return true, repo_model.ErrRepoNotExist{}
}
repo := a.Repo
err := repo.LoadOwner(ctx)
if err != nil {
return true, err
}
if repo.IsPrivate || repo.Owner.KeepActivityPrivate || repo.Owner.Visibility != structs.VisibleTypePublic {
return true, nil
}
a.LoadActUser(ctx)
if a.ActUser == nil {
return true, user_model.ErrUserNotExist{}
}
user := a.ActUser
if user.KeepActivityPrivate || user.Visibility != structs.VisibleTypePublic {
return true, nil
}
return false, nil
}

View file

@ -371,3 +371,28 @@ func TestGetIssueInfos(t *testing.T) {
assert.Equal(t, test.field3, issueInfos[2])
}
}
func TestIsPrivate(t *testing.T) {
defer unittest.OverrideFixtures("models/activities/fixtures/TestIsPrivate")()
require.NoError(t, unittest.PrepareTestDatabase())
tt := []struct {
activityID int64
private bool
}{
{1, true}, // private repo
{3, false}, // public activities, public repo
{11, true}, // private activities
}
for _, test := range tt {
ctx := t.Context()
action, err := activities_model.GetActivityByID(ctx, test.activityID)
require.NoError(t, err)
private, err := action.IsActionPrivate(ctx)
require.NoError(t, err)
assert.Equal(t, test.private, private, "action ID: %d", test.activityID)
}
}

View file

@ -0,0 +1,14 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package activities
import "fmt"
type ErrActivityPrivate struct {
id int64
}
func (err ErrActivityPrivate) Error() string {
return fmt.Sprintf("Activity with id %d is private", err.id)
}

View file

@ -0,0 +1,7 @@
- id: 11
user_id: 44
op_type: 1 # create repo
act_user_id: 44 # private user activities
repo_id: 60 # public
is_private: false
created_unix: 1680454039

View file

@ -0,0 +1,36 @@
- id: 44
lower_name: user44
name: user44
full_name: user44
email: user44@example.com
keep_email_private: false
email_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
login_source: 0
login_name: user44
type: 0
salt: ZogKvWdyEx
max_repo_creation: -1
is_active: true
is_admin: false
is_restricted: false
allow_git_hook: false
allow_import_local: false
allow_create_organization: true
prohibit_login: false
avatar: ""
avatar_email: user44@example.com
use_custom_avatar: true
num_followers: 0
num_following: 0
num_stars: 0
num_repos: 0
num_teams: 0
num_members: 0
visibility: 0
repo_admin_change_team_access: false
theme: ""
keep_activity_private: true
created_unix: 1672578380

View file

@ -210,34 +210,9 @@ func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.Repository
}
repoIDs := nl.getPendingRepoIDs()
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
left := len(repoIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
rows, err := db.GetEngine(ctx).
In("id", repoIDs[:limit]).
Rows(new(repo_model.Repository))
if err != nil {
return nil, nil, err
}
for rows.Next() {
var repo repo_model.Repository
err = rows.Scan(&repo)
if err != nil {
rows.Close()
return nil, nil, err
}
repos[repo.ID] = &repo
}
_ = rows.Close()
left -= limit
repoIDs = repoIDs[limit:]
repos, err := db.GetByIDs(ctx, "id", repoIDs, &repo_model.Repository{})
if err != nil {
return nil, nil, err
}
failed := []int{}
@ -284,34 +259,9 @@ func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) {
}
issueIDs := nl.getPendingIssueIDs()
issues := make(map[int64]*issues_model.Issue, len(issueIDs))
left := len(issueIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
rows, err := db.GetEngine(ctx).
In("id", issueIDs[:limit]).
Rows(new(issues_model.Issue))
if err != nil {
return nil, err
}
for rows.Next() {
var issue issues_model.Issue
err = rows.Scan(&issue)
if err != nil {
rows.Close()
return nil, err
}
issues[issue.ID] = &issue
}
_ = rows.Close()
left -= limit
issueIDs = issueIDs[limit:]
issues, err := db.GetByIDs(ctx, "id", issueIDs, &issues_model.Issue{})
if err != nil {
return nil, err
}
failures := []int{}
@ -379,34 +329,9 @@ func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) {
}
userIDs := nl.getUserIDs()
users := make(map[int64]*user_model.User, len(userIDs))
left := len(userIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
rows, err := db.GetEngine(ctx).
In("id", userIDs[:limit]).
Rows(new(user_model.User))
if err != nil {
return nil, err
}
for rows.Next() {
var user user_model.User
err = rows.Scan(&user)
if err != nil {
rows.Close()
return nil, err
}
users[user.ID] = &user
}
_ = rows.Close()
left -= limit
userIDs = userIDs[limit:]
users, err := db.GetByIDs(ctx, "id", userIDs, &user_model.User{})
if err != nil {
return nil, err
}
failures := []int{}
@ -430,34 +355,9 @@ func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) {
}
commentIDs := nl.getPendingCommentIDs()
comments := make(map[int64]*issues_model.Comment, len(commentIDs))
left := len(commentIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
rows, err := db.GetEngine(ctx).
In("id", commentIDs[:limit]).
Rows(new(issues_model.Comment))
if err != nil {
return nil, err
}
for rows.Next() {
var comment issues_model.Comment
err = rows.Scan(&comment)
if err != nil {
rows.Close()
return nil, err
}
comments[comment.ID] = &comment
}
_ = rows.Close()
left -= limit
commentIDs = commentIDs[limit:]
comments, err := db.GetByIDs(ctx, "id", commentIDs, &issues_model.Comment{})
if err != nil {
return nil, err
}
failures := []int{}

View file

@ -138,10 +138,7 @@ func GetActivityStatsTopAuthors(ctx context.Context, repo *repo_model.Repository
return v[i].Commits > v[j].Commits
})
cnt := count
if cnt > len(v) {
cnt = len(v)
}
cnt := min(count, len(v))
return v[:cnt], nil
}

View file

@ -5,6 +5,7 @@ package auth
import (
"fmt"
"slices"
"strings"
"forgejo.org/models/perm"
@ -204,12 +205,7 @@ func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTok
// ContainsCategory checks if a list of categories contains a specific category
func ContainsCategory(categories []AccessTokenScopeCategory, category AccessTokenScopeCategory) bool {
for _, c := range categories {
if c == category {
return true
}
}
return false
return slices.Contains(categories, category)
}
// GetScopeLevelFromAccessMode converts permission access mode to scope level

View file

@ -6,6 +6,7 @@ package auth
import (
"context"
"crypto/sha256"
"crypto/subtle"
"encoding/base32"
"encoding/base64"
"errors"
@ -151,9 +152,9 @@ func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-12#section-3.1
contains := func(s string) bool {
s = strings.TrimSuffix(strings.ToLower(s), "/")
s = strings.TrimSuffix(util.ToUpperASCII(s), "/")
for _, u := range app.RedirectURIs {
if strings.TrimSuffix(strings.ToLower(u), "/") == s {
if strings.TrimSuffix(util.ToUpperASCII(u), "/") == s {
return true
}
}
@ -408,26 +409,41 @@ func (code *OAuth2AuthorizationCode) GenerateRedirectURI(state string) (*url.URL
return redirect, err
}
// Invalidate deletes the auth code from the database to invalidate this code
// Invalidate deletes the auth code from the database to invalidate this code.
// It returns an error if the code was already invalidated (i.e., no rows were deleted),
// which prevents authorization code replay attacks.
func (code *OAuth2AuthorizationCode) Invalidate(ctx context.Context) error {
_, err := db.GetEngine(ctx).ID(code.ID).NoAutoCondition().Delete(code)
return err
affected, err := db.GetEngine(ctx).ID(code.ID).NoAutoCondition().Delete(code)
if err != nil {
return err
}
if affected == 0 {
return fmt.Errorf("authorization code already used or does not exist")
}
return nil
}
// ValidateCodeChallenge validates the given verifier against the saved code challenge. This is part of the PKCE implementation.
// ValidateCodeChallenge validates the given verifier against the saved code challenge. This is part of the PKCE
// implementation. If a code challenge was set during authorization, a valid verifier MUST be provided.
func (code *OAuth2AuthorizationCode) ValidateCodeChallenge(verifier string) bool {
// If no PKCE was used during authorization, no verifier is needed.
if code.CodeChallengeMethod == "" && code.CodeChallenge == "" {
return true
}
// A challenge was set but no verifier provided: reject outright, no comparison or hashing is required.
if verifier == "" {
return false
}
switch code.CodeChallengeMethod {
case "S256":
// base64url(SHA256(verifier)) see https://tools.ietf.org/html/rfc7636#section-4.6
h := sha256.Sum256([]byte(verifier))
hashedVerifier := base64.RawURLEncoding.EncodeToString(h[:])
return hashedVerifier == code.CodeChallenge
return subtle.ConstantTimeCompare([]byte(hashedVerifier), []byte(code.CodeChallenge)) == 1
case "plain":
return verifier == code.CodeChallenge
case "":
return true
return subtle.ConstantTimeCompare([]byte(verifier), []byte(code.CodeChallenge)) == 1
default:
// unsupported method -> return false
// unsupported or empty method with a non-empty challenge -> reject
return false
}
}
@ -490,22 +506,25 @@ func (grant *OAuth2Grant) GenerateNewAuthorizationCode(ctx context.Context, redi
}
// IncreaseCounter increases the counter and updates the grant
// IncreaseCounter atomically increments the counter only if it still matches
// the value loaded into this grant. Returns an error if the counter was already
// changed by a concurrent request (refresh token replay).
func (grant *OAuth2Grant) IncreaseCounter(ctx context.Context) error {
_, err := db.GetEngine(ctx).ID(grant.ID).Incr("counter").Update(new(OAuth2Grant))
affected, err := db.GetEngine(ctx).Where("id = ? AND counter = ?", grant.ID, grant.Counter).
Incr("counter").Update(new(OAuth2Grant))
if err != nil {
return err
}
updatedGrant, err := GetOAuth2GrantByID(ctx, grant.ID)
if err != nil {
return err
if affected == 0 {
return fmt.Errorf("grant counter changed unexpectedly (possible replay)")
}
grant.Counter = updatedGrant.Counter
grant.Counter++
return nil
}
// ScopeContains returns true if the grant scope contains the specified scope
func (grant *OAuth2Grant) ScopeContains(scope string) bool {
for _, currentScope := range strings.Split(grant.Scope, " ") {
for currentScope := range strings.SplitSeq(grant.Scope, " ") {
if scope == currentScope {
return true
}

View file

@ -75,6 +75,13 @@ func TestOAuth2Application_ContainsRedirect_Slash(t *testing.T) {
assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other"))
}
func TestOAuth2Application_ContainsRedirect_Normalization(t *testing.T) {
app := &auth_model.OAuth2Application{RedirectURIs: []string{"https://website.com"}}
assert.True(t, app.ContainsRedirectURI("https://website.com"))
assert.True(t, app.ContainsRedirectURI("https://webSITE.com")) // ascii uppercase I
assert.False(t, app.ContainsRedirectURI("https://websİte.com")) // U+0130 as I, Latin Capital Letter I with Dot Above
}
func TestOAuth2Application_ValidateClientSecret(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
@ -147,9 +154,18 @@ func TestGetOAuth2GrantByID(t *testing.T) {
func TestOAuth2Grant_IncreaseCounter(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 1})
// First increment succeeds
require.NoError(t, grant.IncreaseCounter(db.DefaultContext))
assert.Equal(t, int64(2), grant.Counter)
unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 2})
// Simulate a stale grant (counter still 1): must fail (concurrent replay)
grant.Counter = 1
require.Error(t, grant.IncreaseCounter(db.DefaultContext), "stale counter must be rejected")
// Counter in DB should be unchanged
unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 2})
}
func TestOAuth2Grant_ScopeContains(t *testing.T) {
@ -232,12 +248,33 @@ func TestOAuth2AuthorizationCode_ValidateCodeChallenge(t *testing.T) {
}
assert.False(t, code.ValidateCodeChallenge("foiwgjioriogeiogjerger"))
// test no code challenge
// test no PKCE at all (no challenge set, no verifier needed)
code = &auth_model.OAuth2AuthorizationCode{
CodeChallengeMethod: "",
CodeChallenge: "",
}
assert.True(t, code.ValidateCodeChallenge(""))
// test PKCE required: challenge was set but verifier is empty
code = &auth_model.OAuth2AuthorizationCode{
CodeChallengeMethod: "S256",
CodeChallenge: "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg",
}
assert.False(t, code.ValidateCodeChallenge(""), "PKCE required: S256 challenge set but empty verifier must be rejected")
code = &auth_model.OAuth2AuthorizationCode{
CodeChallengeMethod: "plain",
CodeChallenge: "test123",
}
assert.False(t, code.ValidateCodeChallenge(""), "PKCE required: plain challenge set but empty verifier must be rejected")
// test challenge stored but method empty (malformed: should reject)
code = &auth_model.OAuth2AuthorizationCode{
CodeChallengeMethod: "",
CodeChallenge: "foierjiogerogerg",
}
assert.True(t, code.ValidateCodeChallenge(""))
assert.False(t, code.ValidateCodeChallenge(""), "challenge present with empty method must be rejected")
assert.False(t, code.ValidateCodeChallenge("foierjiogerogerg"), "challenge present with empty method must be rejected even with matching verifier")
}
func TestOAuth2AuthorizationCode_GenerateRedirectURI(t *testing.T) {
@ -262,6 +299,20 @@ func TestOAuth2AuthorizationCode_Invalidate(t *testing.T) {
unittest.AssertNotExistsBean(t, &auth_model.OAuth2AuthorizationCode{Code: "authcode"})
}
func TestOAuth2AuthorizationCode_Invalidate_DoubleUse(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
code := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2AuthorizationCode{Code: "authcode"})
// First invalidation should succeed
require.NoError(t, code.Invalidate(db.DefaultContext))
unittest.AssertNotExistsBean(t, &auth_model.OAuth2AuthorizationCode{Code: "authcode"})
// Second invalidation of the same code must fail (replay prevention)
err := code.Invalidate(db.DefaultContext)
require.Error(t, err)
assert.Contains(t, err.Error(), "authorization code already used")
}
func TestOAuth2AuthorizationCode_TableName(t *testing.T) {
assert.Equal(t, "oauth2_authorization_code", new(auth_model.OAuth2AuthorizationCode).TableName())
}

View file

@ -6,6 +6,9 @@ package db
import (
"context"
"database/sql"
"errors"
"fmt"
"slices"
"xorm.io/builder"
"xorm.io/xorm"
@ -268,6 +271,91 @@ func GetByID[T any](ctx context.Context, id int64) (object *T, exist bool, err e
return &bean, true, nil
}
// Retrieves multiple objects with database queries similar to an xorm `.In(idField, idList)`. idField must be a unique
// field on the database table, as a map[id]obj is returned and the usage of a non-unique field would result in objects
// being overwritten in the map.
//
// The length of the IN list is constrained to DefaultMaxInSize for each database query, resulting in multiple database
// queries if the length of the idList exceeds that setting; this constraint prevents exceeding bind parameter
// limitations or query length limitations in the database engine.
func GetByIDs[Bean any, Id comparable](ctx context.Context, idField string, idList []Id, bean *Bean) (map[Id]*Bean, error) {
retval := make(map[Id]*Bean, len(idList))
if len(idList) == 0 {
return retval, nil
}
table, err := TableInfo(bean)
if err != nil {
return nil, fmt.Errorf("unable to fetch table info for bean %v: %w", bean, err)
}
var structFieldName string
for _, c := range table.Columns() {
if c.Name == idField {
structFieldName = c.FieldName
break
}
}
if structFieldName == "" {
return nil, fmt.Errorf("unable to identify struct field for id field %s", idField)
}
for idChunk := range slices.Chunk(idList, DefaultMaxInSize) {
beans := make([]*Bean, 0, len(idChunk))
if err := GetEngine(ctx).In(idField, idChunk).Find(&beans); err != nil {
return nil, err
}
for _, bean := range beans {
retval[extractFieldValue(bean, structFieldName).(Id)] = bean
}
}
return retval, nil
}
// Retrieves multiple objects with database queries similar to an xorm `.In(field, valueList)`. Similar to GetByIDs,
// except that a map[Id][]*Bean is returned as the field value is not assumed to be a unique value -- if there are
// multiple rows in the table for each value, all of them are returned.
//
// The length of the IN list is constrained to DefaultMaxInSize for each database query, resulting in multiple database
// queries if the length of the idList exceeds that setting; this constraint prevents exceeding bind parameter
// limitations or query length limitations in the database engine.
func GetByFieldIn[Bean any, Id comparable](ctx context.Context, field string, valueList []Id, bean *Bean) (map[Id][]*Bean, error) {
retval := make(map[Id][]*Bean, len(valueList))
if len(valueList) == 0 {
return retval, nil
}
table, err := TableInfo(bean)
if err != nil {
return nil, fmt.Errorf("unable to fetch table info for bean %v: %w", bean, err)
}
var structFieldName string
for _, c := range table.Columns() {
if c.Name == field {
structFieldName = c.FieldName
break
}
}
if structFieldName == "" {
return nil, fmt.Errorf("unable to identify struct field for field %s", field)
}
for idChunk := range slices.Chunk(valueList, DefaultMaxInSize) {
beans := make([]*Bean, 0, len(idChunk))
if err := GetEngine(ctx).In(field, idChunk).Find(&beans); err != nil {
return nil, err
}
for _, bean := range beans {
fieldValue := extractFieldValue(bean, structFieldName).(Id)
retval[fieldValue] = append(retval[fieldValue], bean)
}
}
return retval, nil
}
func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) {
if !cond.IsValid() {
panic("cond is invalid in db.Exist(ctx, cond). This should not be possible.")
@ -416,3 +504,68 @@ func inTransaction(ctx context.Context) (*xorm.Session, bool) {
return nil, false
}
}
type RetryConfig struct {
ErrorIs []error
AttemptCount int
}
var ErrNestedRetryTxFailure = errors.New("(nested)")
type nestedRetryTxState int
var nestedRetryTx nestedRetryTxState
// Execute the given function in a transaction. RetryConfig will retry the function on an error, if it matches the
// ErrorIs parameter, up to the total of AttemptCount number of tries. RetryTx cannot be invoked when already within a
// transaction and will return an error immediately.
//
// ErrNestedRetryTxFailure is an error type that will occur when RetryTx is nested within each other, and indicates that
// an inner RetryTx encountered an error that matched its error list.
func RetryTx(ctx context.Context, config RetryConfig, f func(ctx context.Context) error) error {
matchError := func(err error) bool {
for _, possibleError := range config.ErrorIs {
if errors.Is(err, possibleError) {
return true
}
}
return false
}
// Accept `ErrNestedRetryTxFailure` as error to retry on, means that a nested
// RetryTx indicated to retry the whole transaction.
config.ErrorIs = append(config.ErrorIs, ErrNestedRetryTxFailure)
withinRetryTx, present := ctx.Value(nestedRetryTx).(bool)
if present && withinRetryTx {
// If a caller already started `RetryTx`, then we assume we don't have to actually perform retries here -- we
// can attempt the requested function once, and if an error is returned that matches the configured error list,
// we'll return that error + ErrNestedRetryTxFailure wrapping.
err := f(ctx)
if err == nil {
return nil
} else if matchError(err) {
return fmt.Errorf("nested RetryTx; internal Tx failed with error that won't be retried: %w %w", err, ErrNestedRetryTxFailure)
}
return err
} else if InTransaction(ctx) {
return errors.New("unsupported operation: attempted to use RetryTx while already within a transaction")
} else if config.AttemptCount == 0 {
return errors.New("unsupported operation: attempted to use RetryTx with 0 attempts")
}
innerCtx := context.WithValue(ctx, nestedRetryTx, true)
var lastError error
for range config.AttemptCount {
err := WithTx(innerCtx, f)
if err == nil {
return nil
} else if !matchError(err) {
return err
}
lastError = err
}
return fmt.Errorf("retry tx failed after %d attempts; last error: %w", config.AttemptCount, lastError)
}

View file

@ -220,3 +220,103 @@ func TestAfterTx(t *testing.T) {
})
}
}
func TestRetryTx(t *testing.T) {
t.Run("success", func(t *testing.T) {
err := db.RetryTx(t.Context(), db.RetryConfig{AttemptCount: 1}, func(ctx context.Context) error {
assert.True(t, db.InTransaction(ctx))
return nil
})
require.NoError(t, err)
})
t.Run("fail constantly", func(t *testing.T) {
attemptCount := 0
testError := errors.New("hello")
err := db.RetryTx(t.Context(), db.RetryConfig{
AttemptCount: 2,
ErrorIs: []error{testError},
}, func(ctx context.Context) error {
attemptCount++
return testError
})
require.ErrorIs(t, err, testError)
require.ErrorContains(t, err, "2 attempts")
assert.Equal(t, 2, attemptCount)
})
t.Run("fail w/ non retriable error", func(t *testing.T) {
attemptCount := 0
testError := errors.New("hello")
err := db.RetryTx(t.Context(), db.RetryConfig{
AttemptCount: 2,
ErrorIs: []error{},
}, func(ctx context.Context) error {
attemptCount++
return testError
})
require.ErrorIs(t, err, testError)
assert.Equal(t, 1, attemptCount)
})
t.Run("succeed on retry", func(t *testing.T) {
attemptCount := 0
testError := errors.New("hello")
err := db.RetryTx(t.Context(), db.RetryConfig{
AttemptCount: 2,
ErrorIs: []error{testError},
}, func(ctx context.Context) error {
attemptCount++
if attemptCount == 1 {
return testError
}
return nil
})
require.NoError(t, err)
assert.Equal(t, 2, attemptCount)
})
t.Run("nested", func(t *testing.T) {
attemptCount := 0
testError := errors.New("hello")
err := db.RetryTx(t.Context(), db.RetryConfig{
AttemptCount: 2,
}, func(ctx context.Context) error {
attemptCount++
return db.RetryTx(ctx, db.RetryConfig{
AttemptCount: 2,
ErrorIs: []error{testError},
}, func(ctx context.Context) error {
if attemptCount == 2 {
return nil
}
return testError
})
})
require.NoError(t, err)
assert.Equal(t, 2, attemptCount)
})
t.Run("inner RetryTx decides on error", func(t *testing.T) {
attemptCount := 0
testError := errors.New("hello")
err := db.RetryTx(t.Context(), db.RetryConfig{
AttemptCount: 2,
ErrorIs: []error{},
}, func(ctx context.Context) error {
attemptCount++
return db.RetryTx(ctx, db.RetryConfig{
AttemptCount: 2,
}, func(ctx context.Context) error {
if attemptCount == 2 {
return nil
}
return testError
})
})
require.ErrorIs(t, err, testError)
assert.Equal(t, 1, attemptCount)
})
}

View file

@ -80,7 +80,7 @@ func Iterate[Bean any](ctx context.Context, cond builder.Cond, f func(ctx contex
func extractFieldValue(bean any, fieldName string) any {
v := reflect.ValueOf(bean)
if v.Kind() == reflect.Ptr {
if v.Kind() == reflect.Pointer {
v = v.Elem()
}
field := v.FieldByName(fieldName)

View file

@ -14,7 +14,7 @@ import (
const (
// DefaultMaxInSize represents default variables number on IN () in SQL
DefaultMaxInSize = 50
DefaultMaxInSize = 500
defaultFindSliceSize = 10
)

View file

@ -6,6 +6,7 @@ package db
import (
"fmt"
"regexp"
"slices"
"strings"
"unicode/utf8"
@ -114,10 +115,8 @@ func IsUsableName(names, patterns []string, name string) error {
return ErrNameEmpty
}
for i := range names {
if name == names[i] {
return ErrNameReserved{name}
}
if slices.Contains(names, name) {
return ErrNameReserved{name}
}
for _, pat := range patterns {

View file

@ -46,10 +46,7 @@ func (f *file) readAt(fileMeta *DbfsMeta, offset int64, p []byte) (n int, err er
blobPos := int(offset % f.blockSize)
blobOffset := offset - int64(blobPos)
blobRemaining := int(f.blockSize) - blobPos
needRead := len(p)
if needRead > blobRemaining {
needRead = blobRemaining
}
needRead := min(len(p), blobRemaining)
if blobOffset+int64(blobPos)+int64(needRead) > fileMeta.FileSize {
needRead = int(fileMeta.FileSize - blobOffset - int64(blobPos))
}
@ -66,14 +63,8 @@ func (f *file) readAt(fileMeta *DbfsMeta, offset int64, p []byte) (n int, err er
blobData = nil
}
canCopy := len(blobData) - blobPos
if canCopy <= 0 {
canCopy = 0
}
realRead := needRead
if realRead > canCopy {
realRead = canCopy
}
canCopy := max(len(blobData)-blobPos, 0)
realRead := min(needRead, canCopy)
if realRead > 0 {
copy(p[:realRead], fileData.BlobData[blobPos:blobPos+realRead])
}
@ -113,10 +104,7 @@ func (f *file) Write(p []byte) (n int, err error) {
blobPos := int(f.offset % f.blockSize)
blobOffset := f.offset - int64(blobPos)
blobRemaining := int(f.blockSize) - blobPos
needWrite := len(p)
if needWrite > blobRemaining {
needWrite = blobRemaining
}
needWrite := min(len(p), blobRemaining)
buf := make([]byte, f.blockSize)
readBytes, err := f.readAt(fileMeta, blobOffset, buf)
if err != nil && !errors.Is(err, io.EOF) {

View file

@ -81,4 +81,4 @@
act_user_id: 40
repo_id: 60 # public
is_private: false
created_unix: 1577404800 # end of heatmap
created_unix: 1577404800 # end of heatmap

View file

@ -9,3 +9,36 @@
created_unix: 1559593109
updated_unix: 1565224552
login_source_id: 0
-
id: 2
owner_id: 5
name: user5
fingerprint: ""
content: "user5"
mode: 2
type: 3
created_unix: 1775805112
updated_unix: 1775805112
login_source_id: 0
-
id: 3
owner_id: 9
name: user9@localhost
fingerprint: "SHA256:K3RfDvtQ/aYVzh6RfXGFxlffLLTRgksf9UQwTlwSM8M"
content: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDN7KuFUnlztx/UM6PUTyiBAq5SeIqr+qSVFC6JzLQAh user9@localhost"
mode: 2
type: 1
created_unix: 1775805112
updated_unix: 1775805112
login_source_id: 0
-
id: 4
owner_id: 9
name: user9
fingerprint: ""
content: "user9"
mode: 2
type: 3
created_unix: 1775805112
updated_unix: 1775805112
login_source_id: 0

View file

@ -6,7 +6,6 @@ package forgejo_migrations
import (
"context"
actions_model "forgejo.org/models/actions"
"forgejo.org/models/db"
"forgejo.org/modules/log"
"forgejo.org/modules/timeutil"
@ -59,6 +58,18 @@ type v14ActionsApprovalAndTrustTrusted struct {
}
func v14ActionsApprovalAndTrustPopulateTableActionUser(x *xorm.Engine) error {
type ActionUser struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"INDEX UNIQUE(action_user_index) REFERENCES(user, id)"`
RepoID int64 `xorm:"INDEX UNIQUE(action_user_index) REFERENCES(repository, id)"`
TrustedWithPullRequests bool
LastAccess timeutil.TimeStamp `xorm:"INDEX"`
}
insertActionUser := func(ctx context.Context, user *ActionUser) error {
user.LastAccess = timeutil.TimeStampNow()
return db.Insert(ctx, user)
}
//
// Users approved once were trusted before and are trusted now.
//
@ -87,7 +98,7 @@ func v14ActionsApprovalAndTrustPopulateTableActionUser(x *xorm.Engine) error {
if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error {
for _, trusted := range trustedList {
log.Debug("v14a_actions-approval-and-trust: repository %d trusts user %d", trusted.RepoID, trusted.UserID)
if err := actions_model.InsertActionUser(ctx, &actions_model.ActionUser{
if err := insertActionUser(ctx, &ActionUser{
RepoID: trusted.RepoID,
UserID: trusted.UserID,
TrustedWithPullRequests: true,

View file

@ -7,11 +7,8 @@ import (
"testing"
"time"
actions_model "forgejo.org/models/actions"
"forgejo.org/models/db"
migration_tests "forgejo.org/models/gitea_migrations/test"
repo_model "forgejo.org/models/repo"
user_model "forgejo.org/models/user"
"forgejo.org/modules/timeutil"
webhook_module "forgejo.org/modules/webhook"
@ -20,6 +17,9 @@ import (
)
func Test_v14ActionsApprovalAndTrustPopulateTableActionUser(t *testing.T) {
type ConcurrencyMode int
type Status int
type ActionUser struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"INDEX UNIQUE(action_user_index) REFERENCES(user, id)"`
@ -32,21 +32,18 @@ func Test_v14ActionsApprovalAndTrustPopulateTableActionUser(t *testing.T) {
type ActionRun struct {
ID int64
Title string
RepoID int64 `xorm:"index unique(repo_index) index(concurrency)"`
Repo *repo_model.Repository `xorm:"-"`
OwnerID int64 `xorm:"index"`
WorkflowID string `xorm:"index"` // the name of workflow file
Index int64 `xorm:"index unique(repo_index)"` // a unique number for each run of a repository
TriggerUserID int64 `xorm:"index"`
TriggerUser *user_model.User `xorm:"-"`
RepoID int64 `xorm:"index unique(repo_index) index(concurrency)"`
OwnerID int64 `xorm:"index"`
WorkflowID string `xorm:"index"` // the name of workflow file
Index int64 `xorm:"index unique(repo_index)"` // a unique number for each run of a repository
TriggerUserID int64 `xorm:"index"`
ScheduleID int64
Ref string `xorm:"index"` // the commit/tag/… that caused the run
IsRefDeleted bool `xorm:"-"`
CommitSHA string
Event webhook_module.HookEventType // the webhook event that causes the workflow to run
EventPayload string `xorm:"LONGTEXT"`
TriggerEvent string // the trigger event defined in the `on` configuration of the triggered workflow
Status actions_model.Status `xorm:"index"`
Status Status `xorm:"index"`
Version int `xorm:"version default 0"` // Status could be updated concomitantly, so an optimistic lock is needed
// Started and Stopped is used for recording last run time, if rerun happened, they will be reset to 0
Started timeutil.TimeStamp
@ -65,7 +62,7 @@ func Test_v14ActionsApprovalAndTrustPopulateTableActionUser(t *testing.T) {
ApprovedBy int64 `xorm:"index"`
ConcurrencyGroup string `xorm:"'concurrency_group' index(concurrency)"`
ConcurrencyType actions_model.ConcurrencyMode
ConcurrencyType ConcurrencyMode
PreExecutionError string `xorm:"LONGTEXT"` // used to report errors that blocked execution of a workflow
}
@ -83,10 +80,10 @@ func Test_v14ActionsApprovalAndTrustPopulateTableActionUser(t *testing.T) {
require.NoError(t, v14ActionsApprovalAndTrustPopulateTableActionUser(x))
var users []*actions_model.ActionUser
var users []*ActionUser
require.NoError(t, db.GetEngine(t.Context()).Select("`repo_id`, `user_id`").OrderBy("`id`").Find(&users))
// See models/gitea_migrations/fixtures/Test_v14ActionsApprovalAndTrustPopulateTableActionUser/action_run.yml
assert.Equal(t, []*actions_model.ActionUser{
assert.Equal(t, []*ActionUser{
{
UserID: 3,
RepoID: 15,

View file

@ -10,15 +10,14 @@ package forgejo_migrations
import (
"context"
"database/sql"
"fmt"
"strings"
"forgejo.org/models/db"
"forgejo.org/models/forgefed"
user_model "forgejo.org/models/user"
"forgejo.org/modules/log"
"forgejo.org/modules/timeutil"
"forgejo.org/modules/validation"
user_service "forgejo.org/services/user"
"xorm.io/xorm"
)
@ -31,6 +30,42 @@ func init() {
}
func changeActivityPubUsernameFormat(x *xorm.Engine) error {
type FederationHost struct {
ID int64 `xorm:"pk autoincr"`
HostFqdn string `xorm:"host_fqdn UNIQUE(federation_host) INDEX VARCHAR(255) NOT NULL"`
HostPort uint16 `xorm:" UNIQUE(federation_host) INDEX NOT NULL DEFAULT 443"`
HostSchema string `xorm:"NOT NULL DEFAULT 'https'"`
Created timeutil.TimeStamp `xorm:"created"`
Updated timeutil.TimeStamp `xorm:"updated"`
}
type FederatedUser struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL INDEX user_id"`
ExternalID string `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
FederationHostID int64 `xorm:"UNIQUE(federation_user_mapping) NOT NULL"`
KeyID sql.NullString `xorm:"key_id UNIQUE"`
PublicKey sql.Null[sql.RawBytes] `xorm:"BLOB"`
InboxPath string
NormalizedOriginalURL string // This field is just to keep original information. Pls. do not use for search or as ID!
}
type User struct {
ID int64 `xorm:"pk autoincr"`
LowerName string `xorm:"UNIQUE NOT NULL"`
Name string `xorm:"UNIQUE NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
deleteFederatedUser := func(ctx context.Context, userID int64) error {
_, err := db.GetEngine(ctx).Delete(&FederatedUser{UserID: userID})
return err
}
userLogString := func(u *User) string {
if u == nil {
return "<User nil>"
}
return fmt.Sprintf("<User %d:%s>", u.ID, u.Name)
}
// Normally, the db.WithTx statement ensures that the database transaction (aka. all changes made
// by this migration) will only be committed if the SQL operations inside of the iteration
// (db.Iterate) don't return an error.
@ -45,9 +80,9 @@ func changeActivityPubUsernameFormat(x *xorm.Engine) error {
// migrations at a later point and has been kept as-is.
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
// The transaction is committed only if modifying all federated users is possible.
return db.Iterate(ctx, nil, func(ctx context.Context, federatedUser *user_model.FederatedUser) error {
return db.Iterate(ctx, nil, func(ctx context.Context, federatedUser *FederatedUser) error {
// localUser represents the "local" representation of an ActivityPub (federated) user
localUser := &user_model.User{}
localUser := &User{}
has, err := db.GetEngine(ctx).ID(federatedUser.UserID).Get(localUser)
if err != nil {
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while getting local user (ID: %d), ignoring...: %e", federatedUser.UserID, err)
@ -56,7 +91,7 @@ func changeActivityPubUsernameFormat(x *xorm.Engine) error {
if !has {
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: User missing for federated user: %v", federatedUser)
err := user_model.DeleteFederatedUser(ctx, federatedUser.UserID)
err := deleteFederatedUser(ctx, federatedUser.UserID)
if err != nil {
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while deleting federated user (%s), ignoring...: %e", federatedUser, err)
return nil
@ -68,24 +103,13 @@ func changeActivityPubUsernameFormat(x *xorm.Engine) error {
} else {
// Copied from models/forgefed/federationhost_repository.go (forgefed.GetFederationHost),
// minus some validation code for FederationHost which we do not otherwise manipulate here.
federationHost := new(forgefed.FederationHost)
federationHost := new(FederationHost)
has, err := db.GetEngine(ctx).ID(federatedUser.FederationHostID).Get(federationHost)
if err != nil {
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while looking up federation host info (for %v), ignoring...: %e", federatedUser, err)
return nil
} else if !has {
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Federation host for federated user missing, deleting: %v", federatedUser)
err := user_model.DeleteFederatedUser(ctx, federatedUser.UserID)
if err != nil {
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while deleting federated user (%v), ignoring...: %e", federatedUser, err)
return nil
}
err = user_service.DeleteUser(ctx, localUser, true)
if err != nil {
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while deleting user (%s), ignoring...: %v", localUser.LogString(), err)
}
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Federation host for federated user %s is missing", federatedUser)
return nil
}
@ -117,10 +141,10 @@ func changeActivityPubUsernameFormat(x *xorm.Engine) error {
// Implicitly assumes that there won't be a lower name unique constraint violation.
// Potentially a bit paranoid, but why not?
userThatShouldntExist := &user_model.User{}
userThatShouldntExist := &User{}
lowernameTaken, err := db.GetEngine(ctx).Where("lower_name = ?", strings.ToLower(newUsername)).Table("user").Get(userThatShouldntExist)
if err != nil {
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred, skipping migration of %s: %e", localUser.LogString(), err)
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred, skipping migration of %s: %e", userLogString(localUser), err)
return nil
}
@ -128,23 +152,23 @@ func changeActivityPubUsernameFormat(x *xorm.Engine) error {
log.Warn(
"Migration[v14a_ap-change-fedi-handle-structure]: New username %s for %s already taken by %s, deleting the former...",
newUsername,
localUser.LogString(),
userThatShouldntExist.LogString(),
userLogString(localUser),
userLogString(userThatShouldntExist),
)
err := user_model.DeleteFederatedUser(ctx, localUser.ID)
err := deleteFederatedUser(ctx, localUser.ID)
if err != nil {
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while deleting federated user (%s), ignoring...: %e", localUser.LogString(), err)
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred while deleting federated user (%s), ignoring...: %e", userLogString(localUser), err)
}
return nil
}
// Safe to assume that the following operations should just work now.
log.Info("Migration[v14a_ap-change-fedi-handle-structure]: Updating username of %s to %s", localUser.LogString(), newUsername)
if _, err := db.GetEngine(ctx).ID(localUser.ID).Cols("lower_name", "name").Update(&user_model.User{
log.Info("Migration[v14a_ap-change-fedi-handle-structure]: Updating username of %s to %s", userLogString(localUser), newUsername)
if _, err := db.GetEngine(ctx).ID(localUser.ID).Cols("lower_name", "name").Update(&User{
LowerName: strings.ToLower(newUsername),
Name: newUsername,
}); err != nil {
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred when updating federated user's username (%s), ignoring...: %e", localUser.LogString(), err)
log.Warn("Migration[v14a_ap-change-fedi-handle-structure]: Database error occurred when updating federated user's username (%s), ignoring...: %e", userLogString(localUser), err)
return nil
}
}

View file

@ -8,7 +8,6 @@ import (
"encoding/base64"
"fmt"
admin_model "forgejo.org/models/admin"
"forgejo.org/models/db"
"forgejo.org/modules/json"
"forgejo.org/modules/keying"
@ -17,6 +16,7 @@ import (
"forgejo.org/modules/secret"
"forgejo.org/modules/setting"
"forgejo.org/modules/structs"
"forgejo.org/modules/timeutil"
"xorm.io/builder"
"xorm.io/xorm"
@ -30,6 +30,19 @@ func init() {
}
func migrateTaskSecrets(x *xorm.Engine) error {
type Task struct {
ID int64
DoerID int64 `xorm:"index"`
OwnerID int64 `xorm:"index"`
RepoID int64 `xorm:"index"`
PayloadContent string `xorm:"TEXT"`
Created timeutil.TimeStamp `xorm:"created"`
}
taskUpdateCols := func(ctx context.Context, task *Task, cols ...string) error {
_, err := db.GetEngine(ctx).ID(task.ID).Cols(cols...).Update(task)
return err
}
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
sess := db.GetEngine(ctx)
@ -39,7 +52,7 @@ func migrateTaskSecrets(x *xorm.Engine) error {
messages := make([]string, 0, 100)
ids := make([]int64, 0, 100)
err := db.Iterate(ctx, builder.Eq{"type": structs.TaskTypeMigrateRepo}, func(ctx context.Context, bean *admin_model.Task) error {
err := db.Iterate(ctx, builder.Eq{"type": structs.TaskTypeMigrateRepo}, func(ctx context.Context, bean *Task) error {
var opts migration.MigrateOptions
err := json.Unmarshal([]byte(bean.PayloadContent), &opts)
if err != nil {
@ -96,7 +109,7 @@ func migrateTaskSecrets(x *xorm.Engine) error {
}
bean.PayloadContent = string(bs)
return bean.UpdateCols(ctx, "payload_content")
return taskUpdateCols(ctx, bean, "payload_content")
})
if err == nil {
@ -106,7 +119,7 @@ func migrateTaskSecrets(x *xorm.Engine) error {
log.Error("v14a_migrate_task_secrets: %s", message)
}
_, err = sess.In("id", ids).NoAutoCondition().NoAutoTime().Delete(&admin_model.Task{})
_, err = sess.In("id", ids).NoAutoCondition().NoAutoTime().Delete(&Task{})
}
}
return err

View file

@ -8,11 +8,11 @@ import (
"fmt"
"forgejo.org/models/db"
webhook_model "forgejo.org/models/webhook"
"forgejo.org/modules/keying"
"forgejo.org/modules/log"
"forgejo.org/modules/secret"
"forgejo.org/modules/setting"
"forgejo.org/modules/timeutil"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
@ -26,6 +26,16 @@ func init() {
}
func migrateWebhookSecrets(x *xorm.Engine) error {
type Webhook struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
OwnerID int64 `xorm:"INDEX"`
HeaderAuthorizationEncrypted []byte `xorm:"BLOB"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
sess := db.GetEngine(ctx)
@ -59,7 +69,7 @@ func migrateWebhookSecrets(x *xorm.Engine) error {
messages := make([]string, 0, 100)
ids := make([]int64, 0, 100)
err := db.Iterate(ctx, nil, func(ctx context.Context, bean *webhook_model.Webhook) error {
err := db.Iterate(ctx, nil, func(ctx context.Context, bean *Webhook) error {
if len(bean.HeaderAuthorizationEncrypted) == 0 {
return nil
}
@ -83,7 +93,7 @@ func migrateWebhookSecrets(x *xorm.Engine) error {
log.Error("migration[v14a_migrate_webhook_authorization]: %s", message)
}
_, err = sess.In("id", ids).NoAutoCondition().NoAutoTime().Delete(&webhook_model.Webhook{})
_, err = sess.In("id", ids).NoAutoCondition().NoAutoTime().Delete(&Webhook{})
}
}
return err

View file

@ -7,7 +7,6 @@ import (
"testing"
migration_tests "forgejo.org/models/gitea_migrations/test"
webhook_model "forgejo.org/models/webhook"
"forgejo.org/modules/keying"
"forgejo.org/modules/timeutil"
webhook_module "forgejo.org/modules/webhook"
@ -17,6 +16,7 @@ import (
)
func Test_MigrateWebhookSecrets(t *testing.T) {
type HookContentType int
type Webhook struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
@ -24,7 +24,7 @@ func Test_MigrateWebhookSecrets(t *testing.T) {
IsSystemWebhook bool
URL string `xorm:"url TEXT"`
HTTPMethod string `xorm:"http_method"`
ContentType webhook_model.HookContentType
ContentType HookContentType
Secret string `xorm:"TEXT"`
Events string `xorm:"TEXT"`
IsActive bool `xorm:"INDEX"`
@ -45,7 +45,7 @@ func Test_MigrateWebhookSecrets(t *testing.T) {
IsSystemWebhook bool
URL string `xorm:"url TEXT"`
HTTPMethod string `xorm:"http_method"`
ContentType webhook_model.HookContentType
ContentType HookContentType
Secret string `xorm:"TEXT"`
Events string `xorm:"TEXT"`
IsActive bool `xorm:"INDEX"`

View file

@ -4,7 +4,6 @@
package forgejo_migrations
import (
activities_model "forgejo.org/models/activities"
"forgejo.org/modules/setting"
"xorm.io/xorm"
@ -18,9 +17,10 @@ func init() {
}
func reworkNotification(x *xorm.Engine) error {
type NotificationStatus uint8
type Notification struct {
UserID int64 `xorm:"NOT NULL INDEX(s)"`
Status activities_model.NotificationStatus `xorm:"SMALLINT NOT NULL INDEX(s)"`
UserID int64 `xorm:"NOT NULL INDEX(s)"`
Status NotificationStatus `xorm:"SMALLINT NOT NULL INDEX(s)"`
}
if err := dropIndexIfExists(x, "notification", "IDX_notification_user_id"); err != nil {

View file

@ -5,10 +5,11 @@ package forgejo_migrations
import (
"context"
"fmt"
"forgejo.org/models/db"
user_model "forgejo.org/models/user"
"forgejo.org/modules/log"
"forgejo.org/modules/timeutil"
"xorm.io/builder"
"xorm.io/xorm"
@ -22,13 +23,45 @@ func init() {
}
func setProhibitLoginActivityPubUser(x *xorm.Engine) error {
type UserType int
const (
UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
UserTypeOrganization // 1
UserTypeUserReserved // 2
UserTypeOrganizationReserved // 3
UserTypeBot // 4
UserTypeRemoteUser // 5
UserTypeActivityPubUser // 6
)
type User struct {
ID int64 `xorm:"pk autoincr"`
Name string `xorm:"UNIQUE NOT NULL"`
Passwd string `xorm:"NOT NULL"`
PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"`
Type UserType
Salt string `xorm:"VARCHAR(32)"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
ProhibitLogin bool `xorm:"NOT NULL DEFAULT false"`
}
type FederatedUser struct {
UserID int64 `xorm:"NOT NULL INDEX user_id"`
}
userLogString := func(u *User) string {
if u == nil {
return "<User nil>"
}
return fmt.Sprintf("<User %d:%s>", u.ID, u.Name)
}
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
return db.Iterate(ctx, builder.Eq{"type": 5}, func(ctx context.Context, user *user_model.User) error {
log.Info("Checking if user %s is created from ActivityPub", user.LogString())
return db.Iterate(ctx, builder.Eq{"type": 5}, func(ctx context.Context, user *User) error {
log.Info("Checking if user %s is created from ActivityPub", userLogString(user))
// Users created from f3 also have the RemoteUser user type. All
// FederatedUser should reference exactly one User.
has, err := db.GetEngine(ctx).Table("federated_user").Get(&user_model.FederatedUser{UserID: user.ID})
has, err := db.GetEngine(ctx).Table("federated_user").Get(&FederatedUser{UserID: user.ID})
if err != nil {
return err
}
@ -37,9 +70,9 @@ func setProhibitLoginActivityPubUser(x *xorm.Engine) error {
return nil
}
log.Info("Updating user %s", user.LogString())
_, err = db.GetEngine(ctx).Table("user").ID(user.ID).Cols("type", "prohibit_login", "passwd", "salt", "passwd_hash_algo").Update(&user_model.User{
Type: user_model.UserTypeActivityPubUser,
log.Info("Updating user %s", userLogString(user))
_, err = db.GetEngine(ctx).Table("user").ID(user.ID).Cols("type", "prohibit_login", "passwd", "salt", "passwd_hash_algo").Update(&User{
Type: UserTypeActivityPubUser,
ProhibitLogin: true,
Passwd: "",
Salt: "",

View file

@ -0,0 +1,30 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package forgejo_migrations
import (
"xorm.io/xorm"
)
func init() {
registerMigration(&Migration{
Description: "replace remote_address with encrypted_remote_address in table mirror",
Upgrade: addMirrorRemoteAddressAuth,
})
}
func addMirrorRemoteAddressAuth(x *xorm.Engine) error {
type Mirror struct {
EncryptedRemoteAddress []byte `xorm:"BLOB NULL"`
}
if _, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(Mirror)); err != nil {
return err
}
// No data migration is necessary or desired. `remote_address` contains sanitized URLs which don't have
// credentials, so they can't be migrated to `encrypted_remote_address`. Instead, as this data is accessed,
// `DecryptOrRecoverRemoteAddress` will recover the fully credentialed contents of the remote address from the git
// repo's `origin` remote address.
_, err := x.Exec("ALTER TABLE `mirror` DROP COLUMN `remote_address`")
return err
}

View file

@ -0,0 +1,31 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package forgejo_migrations
import (
"forgejo.org/modules/optional"
"xorm.io/xorm"
)
func init() {
registerMigration(&Migration{
Description: "add time zone support to action_schedule_spec",
Upgrade: addActionScheduleSpecTimeZone,
})
}
func addActionScheduleSpecTimeZone(x *xorm.Engine) error {
type ActionScheduleSpec struct {
TimeZone optional.Option[string]
}
_, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(ActionScheduleSpec))
if err != nil {
return err
}
_, err = x.Exec("ALTER TABLE action_schedule DROP COLUMN `specs`")
return err
}

View file

@ -213,7 +213,7 @@ func (protectBranch *ProtectedBranch) GetUnprotectedFilePatterns() []glob.Glob {
func getFilePatterns(filePatterns string) []glob.Glob {
extarr := make([]glob.Glob, 0, 10)
for _, expr := range strings.Split(strings.ToLower(filePatterns), ";") {
for expr := range strings.SplitSeq(strings.ToLower(filePatterns), ";") {
expr = strings.TrimSpace(expr)
if expr != "" {
if g, err := glob.Compile(expr, '.', '/'); err != nil {

View file

@ -265,7 +265,7 @@ func deleteDB() error {
func removeAllWithRetry(dir string) error {
var err error
for i := 0; i < 20; i++ {
for range 20 {
err = os.RemoveAll(dir)
if err == nil {
break

View file

@ -5,6 +5,7 @@ package v1_11
import (
"fmt"
"slices"
"xorm.io/xorm"
)
@ -345,10 +346,8 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
}
return AccessModeWrite <= perm.UnitsMode[UnitTypeCode], nil
}
for _, id := range protectedBranch.ApprovalsWhitelistUserIDs {
if id == reviewer.ID {
return true, nil
}
if slices.Contains(protectedBranch.ApprovalsWhitelistUserIDs, reviewer.ID) {
return true, nil
}
// isUserInTeams

View file

@ -146,7 +146,7 @@ func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error)
return "", fmt.Errorf("io.ReadAll: %w", err)
}
newAvatar := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", userID, md5.Sum(data)))))
newAvatar := fmt.Sprintf("%x", md5.Sum(fmt.Appendf(nil, "%d-%x", userID, md5.Sum(data))))
if newAvatar == oldAvatar {
return newAvatar, nil
}

View file

@ -329,7 +329,7 @@ func ConvertScopedAccessTokens(x *xorm.Engine) error {
for _, token := range tokens {
var scopes []string
allNewScopesMap := make(map[AccessTokenScope]bool)
for _, oldScope := range strings.Split(token.Scope, ",") {
for oldScope := range strings.SplitSeq(token.Scope, ",") {
if newScopes, exists := accessTokenScopeMap[OldAccessTokenScope(oldScope)]; exists {
for _, newScope := range newScopes {
allNewScopesMap[newScope] = true

View file

@ -9,6 +9,7 @@ import (
"context"
"fmt"
"html/template"
"slices"
"strconv"
"unicode/utf8"
@ -198,12 +199,7 @@ func (t CommentType) HasMailReplySupport() bool {
}
func (t CommentType) CountedAsConversation() bool {
for _, ct := range ConversationCountedCommentType() {
if t == ct {
return true
}
}
return false
return slices.Contains(ConversationCountedCommentType(), t)
}
// ConversationCountedCommentType returns the comment types that are counted as a conversation
@ -619,7 +615,7 @@ func (c *Comment) UpdateAttachments(ctx context.Context, uuids []string) error {
if err != nil {
return fmt.Errorf("FindRepoAttachmentsByUUID[uuids=%q,repoID=%d]: %w", uuids, c.Issue.RepoID, err)
}
for i := 0; i < len(attachments); i++ {
for i := range attachments {
attachments[i].IssueID = c.IssueID
attachments[i].CommentID = c.ID
if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil {
@ -667,21 +663,6 @@ func (c *Comment) LoadAssigneeUserAndTeam(ctx context.Context) error {
return nil
}
// LoadResolveDoer if comment.Type is CommentTypeCode and ResolveDoerID not zero, then load resolveDoer
func (c *Comment) LoadResolveDoer(ctx context.Context) (err error) {
if c.ResolveDoerID == 0 || c.Type != CommentTypeCode {
return nil
}
c.ResolveDoer, err = user_model.GetUserByID(ctx, c.ResolveDoerID)
if err != nil {
if user_model.IsErrUserNotExist(err) {
c.ResolveDoer = user_model.NewGhostUser()
err = nil
}
}
return err
}
// IsResolved check if an code comment is resolved
func (c *Comment) IsResolved() bool {
return c.ResolveDoerID != 0 && c.Type == CommentTypeCode

View file

@ -133,7 +133,7 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
return nil, err
}
n := 0
readyComments := make(CommentList, 0, len(comments))
for _, comment := range comments {
if re, ok := reviews[comment.ReviewID]; ok && re != nil {
// If the review is pending only the author can see the comments (except if the review is set)
@ -143,17 +143,18 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
}
comment.Review = re
}
comments[n] = comment
n++
readyComments = append(readyComments, comment)
}
if err := comment.LoadResolveDoer(ctx); err != nil {
return nil, err
}
if err := readyComments.LoadResolveDoers(ctx); err != nil {
return nil, err
}
if err := comment.LoadReactions(ctx, issue.Repo); err != nil {
return nil, err
}
if err := readyComments.LoadReactions(ctx, issue.Repo); err != nil {
return nil, err
}
for _, comment := range readyComments {
var err error
if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
@ -165,7 +166,8 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
return nil, err
}
}
return comments[:n], nil
return readyComments, nil
}
// FetchCodeConversation fetches the code conversation of a given comment (same review, treePath and line number)

View file

@ -5,6 +5,7 @@ package issues
import (
"context"
"errors"
"forgejo.org/models/db"
repo_model "forgejo.org/models/repo"
@ -51,32 +52,9 @@ func (comments CommentList) loadLabels(ctx context.Context) error {
}
labelIDs := comments.getLabelIDs()
commentLabels := make(map[int64]*Label, len(labelIDs))
left := len(labelIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
rows, err := db.GetEngine(ctx).
In("id", labelIDs[:limit]).
Rows(new(Label))
if err != nil {
return err
}
for rows.Next() {
var label Label
err = rows.Scan(&label)
if err != nil {
_ = rows.Close()
return err
}
commentLabels[label.ID] = &label
}
_ = rows.Close()
left -= limit
labelIDs = labelIDs[limit:]
commentLabels, err := db.GetByIDs(ctx, "id", labelIDs, &Label{})
if err != nil {
return err
}
for _, comment := range comments {
@ -101,21 +79,9 @@ func (comments CommentList) loadMilestones(ctx context.Context) error {
return nil
}
milestones := make(map[int64]*Milestone, len(milestoneIDs))
left := len(milestoneIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
err := db.GetEngine(ctx).
In("id", milestoneIDs[:limit]).
Find(&milestones)
if err != nil {
return err
}
left -= limit
milestoneIDs = milestoneIDs[limit:]
milestones, err := db.GetByIDs(ctx, "id", milestoneIDs, &Milestone{})
if err != nil {
return err
}
for _, comment := range comments {
@ -140,21 +106,9 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error {
return nil
}
milestones := make(map[int64]*Milestone, len(milestoneIDs))
left := len(milestoneIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
err := db.GetEngine(ctx).
In("id", milestoneIDs[:limit]).
Find(&milestones)
if err != nil {
return err
}
left -= limit
milestoneIDs = milestoneIDs[limit:]
milestones, err := db.GetByIDs(ctx, "id", milestoneIDs, &Milestone{})
if err != nil {
return err
}
for _, comment := range comments {
@ -175,34 +129,9 @@ func (comments CommentList) loadAssignees(ctx context.Context) error {
}
assigneeIDs := comments.getAssigneeIDs()
assignees := make(map[int64]*user_model.User, len(assigneeIDs))
left := len(assigneeIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
rows, err := db.GetEngine(ctx).
In("id", assigneeIDs[:limit]).
Rows(new(user_model.User))
if err != nil {
return err
}
for rows.Next() {
var user user_model.User
err = rows.Scan(&user)
if err != nil {
rows.Close()
return err
}
assignees[user.ID] = &user
}
_ = rows.Close()
left -= limit
assigneeIDs = assigneeIDs[limit:]
assignees, err := db.GetByIDs(ctx, "id", assigneeIDs, &user_model.User{})
if err != nil {
return err
}
for _, comment := range comments {
@ -243,34 +172,9 @@ func (comments CommentList) LoadIssues(ctx context.Context) error {
}
issueIDs := comments.getIssueIDs()
issues := make(map[int64]*Issue, len(issueIDs))
left := len(issueIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
rows, err := db.GetEngine(ctx).
In("id", issueIDs[:limit]).
Rows(new(Issue))
if err != nil {
return err
}
for rows.Next() {
var issue Issue
err = rows.Scan(&issue)
if err != nil {
rows.Close()
return err
}
issues[issue.ID] = &issue
}
_ = rows.Close()
left -= limit
issueIDs = issueIDs[limit:]
issues, err := db.GetByIDs(ctx, "id", issueIDs, &Issue{})
if err != nil {
return err
}
for _, comment := range comments {
@ -295,36 +199,10 @@ func (comments CommentList) loadDependentIssues(ctx context.Context) error {
return nil
}
e := db.GetEngine(ctx)
issueIDs := comments.getDependentIssueIDs()
issues := make(map[int64]*Issue, len(issueIDs))
left := len(issueIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
rows, err := e.
In("id", issueIDs[:limit]).
Rows(new(Issue))
if err != nil {
return err
}
for rows.Next() {
var issue Issue
err = rows.Scan(&issue)
if err != nil {
_ = rows.Close()
return err
}
issues[issue.ID] = &issue
}
_ = rows.Close()
left -= limit
issueIDs = issueIDs[limit:]
issues, err := db.GetByIDs(ctx, "id", issueIDs, &Issue{})
if err != nil {
return err
}
for _, comment := range comments {
@ -375,34 +253,10 @@ func (comments CommentList) LoadAttachments(ctx context.Context) (err error) {
return nil
}
attachments := make(map[int64][]*repo_model.Attachment, len(comments))
commentsIDs := comments.getAttachmentCommentIDs()
left := len(commentsIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
rows, err := db.GetEngine(ctx).
In("comment_id", commentsIDs[:limit]).
Rows(new(repo_model.Attachment))
if err != nil {
return err
}
for rows.Next() {
var attachment repo_model.Attachment
err = rows.Scan(&attachment)
if err != nil {
_ = rows.Close()
return err
}
attachments[attachment.CommentID] = append(attachments[attachment.CommentID], &attachment)
}
_ = rows.Close()
left -= limit
commentsIDs = commentsIDs[limit:]
attachments, err := db.GetByFieldIn(ctx, "comment_id", commentsIDs, &repo_model.Attachment{})
if err != nil {
return err
}
for _, comment := range comments {
@ -411,6 +265,84 @@ func (comments CommentList) LoadAttachments(ctx context.Context) (err error) {
return nil
}
func (comments CommentList) LoadResolveDoers(ctx context.Context) (err error) {
relevant := func(c *Comment) bool {
return c.ResolveDoerID != 0 && c.Type == CommentTypeCode
}
userIDs := make(container.Set[int64])
for _, comment := range comments {
if relevant(comment) {
userIDs.Add(comment.ResolveDoerID)
}
}
if len(userIDs) == 0 {
return nil
}
userMap := make(map[int64]*user_model.User)
users, err := user_model.GetUsersByIDs(ctx, userIDs.Slice())
if err != nil {
return err
}
for _, user := range users {
userMap[user.ID] = user
}
for _, comment := range comments {
if !relevant(comment) {
continue
}
resolveDoer, ok := userMap[comment.ResolveDoerID]
if !ok {
comment.ResolveDoer = user_model.NewGhostUser()
} else {
comment.ResolveDoer = resolveDoer
}
}
return nil
}
func (comments CommentList) LoadReactions(ctx context.Context, repo *repo_model.Repository) (err error) {
loadIssueID := int64(0)
loadCommentIDs := make([]int64, 0, len(comments))
for _, comment := range comments {
if loadIssueID == 0 {
loadIssueID = comment.IssueID
} else if loadIssueID != comment.IssueID {
return errors.New("unable to load reactions from comments on different issues than each other")
}
if comment.Reactions == nil {
loadCommentIDs = append(loadCommentIDs, comment.ID)
}
}
if loadIssueID == 0 {
return nil
}
reactions, err := getReactionsForComments(ctx, loadIssueID, loadCommentIDs)
if err != nil {
return err
}
allReactions := make(ReactionList, 0, len(reactions))
for _, comment := range comments {
if comment.Reactions == nil {
comment.Reactions = reactions[comment.ID]
allReactions = append(allReactions, comment.Reactions...)
}
}
if _, err := allReactions.LoadUsers(ctx, repo); err != nil {
return err
}
return nil
}
func (comments CommentList) getReviewIDs() []int64 {
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
return comment.ReviewID, comment.ReviewID > 0

View file

@ -84,3 +84,111 @@ func TestCommentListLoadUser(t *testing.T) {
})
}
}
func TestCommentListLoadResolveDoers(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
issue := unittest.AssertExistsAndLoadBean(t, &Issue{})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
empty := CommentList{}
require.NoError(t, empty.LoadResolveDoers(t.Context()))
comment1, err := CreateComment(db.DefaultContext, &CreateCommentOptions{
Type: CommentTypeCode,
Doer: doer,
Repo: repo,
Issue: issue,
Content: "Hello",
})
require.NoError(t, err)
require.NoError(t, MarkConversation(t.Context(), comment1, doer, true))
comment1 = unittest.AssertExistsAndLoadBean(t, &Comment{ID: comment1.ID}) // reload after change
comment1List := CommentList{comment1}
require.NoError(t, comment1List.LoadResolveDoers(t.Context()))
require.NotNil(t, comment1.ResolveDoer)
assert.Equal(t, doer.ID, comment1.ResolveDoer.ID)
comment2, err := CreateComment(db.DefaultContext, &CreateCommentOptions{
Type: CommentTypeCode,
Doer: doer,
Repo: repo,
Issue: issue,
Content: "Hello again",
})
require.NoError(t, err)
require.NoError(t, MarkConversation(t.Context(), comment2, user_model.NewGhostUser(), true))
// Reload for fresh objects
comment1 = unittest.AssertExistsAndLoadBean(t, &Comment{ID: comment1.ID})
comment2 = unittest.AssertExistsAndLoadBean(t, &Comment{ID: comment2.ID})
comment2List := CommentList{comment1, comment2}
require.NoError(t, comment2List.LoadResolveDoers(t.Context()))
require.NotNil(t, comment1.ResolveDoer)
assert.Equal(t, doer.ID, comment1.ResolveDoer.ID)
require.NotNil(t, comment2.ResolveDoer)
assert.EqualValues(t, -1, comment2.ResolveDoer.ID)
}
func TestCommentListLoadReactions(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
issue := unittest.AssertExistsAndLoadBean(t, &Issue{})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
empty := CommentList{}
require.NoError(t, empty.LoadReactions(t.Context(), repo))
comment1, err := CreateComment(db.DefaultContext, &CreateCommentOptions{
Type: CommentTypeCode,
Doer: doer,
Repo: repo,
Issue: issue,
Content: "Hello",
})
require.NoError(t, err)
_, err = CreateReaction(t.Context(), &ReactionOptions{
Type: "eyes",
DoerID: doer.ID,
IssueID: issue.ID,
CommentID: comment1.ID,
})
require.NoError(t, err)
comment1 = unittest.AssertExistsAndLoadBean(t, &Comment{ID: comment1.ID}) // reload after change
comment1List := CommentList{comment1}
require.NoError(t, comment1List.LoadReactions(t.Context(), repo))
require.Len(t, comment1.Reactions, 1)
assert.Equal(t, "eyes", comment1.Reactions[0].Type)
assert.NotNil(t, comment1.Reactions[0].User)
comment2, err := CreateComment(db.DefaultContext, &CreateCommentOptions{
Type: CommentTypeCode,
Doer: doer,
Repo: repo,
Issue: issue,
Content: "Hello again",
})
require.NoError(t, err)
_, err = CreateReaction(t.Context(), &ReactionOptions{
Type: "rocket",
DoerID: doer.ID,
IssueID: issue.ID,
CommentID: comment2.ID,
})
require.NoError(t, err)
// Reload for fresh objects
comment1 = unittest.AssertExistsAndLoadBean(t, &Comment{ID: comment1.ID})
comment2 = unittest.AssertExistsAndLoadBean(t, &Comment{ID: comment2.ID})
comment2List := CommentList{comment1, comment2}
require.NoError(t, comment2List.LoadReactions(t.Context(), repo))
require.Len(t, comment1.Reactions, 1)
require.Len(t, comment2.Reactions, 1)
assert.Equal(t, "rocket", comment2.Reactions[0].Type)
assert.NotNil(t, comment2.Reactions[0].User)
}

View file

@ -52,12 +52,29 @@ func TestFetchCodeConversations(t *testing.T) {
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
_, err := issues_model.CreateReaction(t.Context(), &issues_model.ReactionOptions{
Type: "eyes",
DoerID: 2,
IssueID: issue.ID,
CommentID: 4,
})
require.NoError(t, err)
require.NoError(t, issues_model.MarkConversation(t.Context(),
unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 4}),
user, true))
res, err := issues_model.FetchCodeConversations(db.DefaultContext, issue, user, false)
require.NoError(t, err)
assert.Contains(t, res, "README.md")
assert.Contains(t, res["README.md"], int64(4))
assert.Len(t, res["README.md"][4], 1)
assert.Equal(t, int64(4), res["README.md"][4][0][0].ID)
require.Contains(t, res, "README.md")
require.Contains(t, res["README.md"], int64(4))
require.Len(t, res["README.md"][4], 1)
require.Len(t, res["README.md"][4][0], 1)
comment := res["README.md"][4][0][0]
assert.Equal(t, int64(4), comment.ID)
assert.NotNil(t, comment.ResolveDoer)
require.Len(t, comment.Reactions, 1)
r := comment.Reactions[0]
assert.NotNil(t, r.User)
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
res, err = issues_model.FetchCodeConversations(db.DefaultContext, issue, user2, false)

View file

@ -6,6 +6,7 @@ package issues
import (
"context"
"fmt"
"slices"
"forgejo.org/models/db"
project_model "forgejo.org/models/project"
@ -40,21 +41,9 @@ func (issues IssueList) LoadRepositories(ctx context.Context) (repo_model.Reposi
}
repoIDs := issues.getRepoIDs()
repoMaps := make(map[int64]*repo_model.Repository, len(repoIDs))
left := len(repoIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
err := db.GetEngine(ctx).
In("id", repoIDs[:limit]).
Find(&repoMaps)
if err != nil {
return nil, fmt.Errorf("find repository: %w", err)
}
left -= limit
repoIDs = repoIDs[limit:]
repoMaps, err := db.GetByIDs(ctx, "id", repoIDs, &repo_model.Repository{})
if err != nil {
return nil, fmt.Errorf("find repository: %w", err)
}
for _, issue := range issues {
@ -96,21 +85,9 @@ func (issues IssueList) LoadPosters(ctx context.Context) error {
}
func getPostersByIDs(ctx context.Context, posterIDs []int64) (map[int64]*user_model.User, error) {
posterMaps := make(map[int64]*user_model.User, len(posterIDs))
left := len(posterIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
err := db.GetEngine(ctx).
In("id", posterIDs[:limit]).
Find(&posterMaps)
if err != nil {
return nil, err
}
left -= limit
posterIDs = posterIDs[limit:]
posterMaps, err := db.GetByIDs(ctx, "id", posterIDs, &user_model.User{})
if err != nil {
return nil, err
}
return posterMaps, nil
}
@ -135,21 +112,15 @@ func (issues IssueList) LoadLabels(ctx context.Context) error {
issueLabels := make(map[int64][]*Label, len(issues)*3)
issueIDs := issues.getIssueIDs()
left := len(issueIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
for issueIDChunk := range slices.Chunk(issueIDs, db.DefaultMaxInSize) {
rows, err := db.GetEngine(ctx).Table("label").
Join("LEFT", "issue_label", "issue_label.label_id = label.id").
In("issue_label.issue_id", issueIDs[:limit]).
In("issue_label.issue_id", issueIDChunk).
Asc("label.name").
Rows(new(LabelIssue))
if err != nil {
return err
}
for rows.Next() {
var labelIssue LabelIssue
err = rows.Scan(&labelIssue)
@ -166,8 +137,6 @@ func (issues IssueList) LoadLabels(ctx context.Context) error {
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.LoadLabels: Close: %w", err1)
}
left -= limit
issueIDs = issueIDs[limit:]
}
for _, issue := range issues {
@ -189,21 +158,9 @@ func (issues IssueList) LoadMilestones(ctx context.Context) error {
return nil
}
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
left := len(milestoneIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
err := db.GetEngine(ctx).
In("id", milestoneIDs[:limit]).
Find(&milestoneMaps)
if err != nil {
return err
}
left -= limit
milestoneIDs = milestoneIDs[limit:]
milestoneMaps, err := db.GetByIDs(ctx, "id", milestoneIDs, &Milestone{})
if err != nil {
return err
}
for _, issue := range issues {
@ -216,25 +173,19 @@ func (issues IssueList) LoadMilestones(ctx context.Context) error {
func (issues IssueList) LoadProjects(ctx context.Context) error {
issueIDs := issues.getIssueIDs()
projectMaps := make(map[int64]*project_model.Project, len(issues))
left := len(issueIDs)
type projectWithIssueID struct {
*project_model.Project `xorm:"extends"`
IssueID int64
}
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
projects := make([]*projectWithIssueID, 0, limit)
for issueIDChunk := range slices.Chunk(issueIDs, db.DefaultMaxInSize) {
projects := make([]*projectWithIssueID, 0, len(issueIDChunk))
err := db.GetEngine(ctx).
Table("project").
Select("project.*, project_issue.issue_id").
Join("INNER", "project_issue", "project.id = project_issue.project_id").
In("project_issue.issue_id", issueIDs[:limit]).
In("project_issue.issue_id", issueIDChunk).
Find(&projects)
if err != nil {
return err
@ -242,8 +193,6 @@ func (issues IssueList) LoadProjects(ctx context.Context) error {
for _, project := range projects {
projectMaps[project.IssueID] = project.Project
}
left -= limit
issueIDs = issueIDs[limit:]
}
for _, issue := range issues {
@ -264,15 +213,10 @@ func (issues IssueList) LoadAssignees(ctx context.Context) error {
assignees := make(map[int64][]*user_model.User, len(issues))
issueIDs := issues.getIssueIDs()
left := len(issueIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
for issueIDChunk := range slices.Chunk(issueIDs, db.DefaultMaxInSize) {
rows, err := db.GetEngine(ctx).Table("issue_assignees").
Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id").
In("`issue_assignees`.issue_id", issueIDs[:limit]).OrderBy(user_model.GetOrderByName()).
In("`issue_assignees`.issue_id", issueIDChunk).OrderBy(user_model.GetOrderByName()).
Rows(new(AssigneeIssue))
if err != nil {
return err
@ -293,8 +237,6 @@ func (issues IssueList) LoadAssignees(ctx context.Context) error {
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadAssignees: Close: %w", err1)
}
left -= limit
issueIDs = issueIDs[limit:]
}
for _, issue := range issues {
@ -324,36 +266,9 @@ func (issues IssueList) LoadPullRequests(ctx context.Context) error {
return nil
}
pullRequestMaps := make(map[int64]*PullRequest, len(issuesIDs))
left := len(issuesIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
rows, err := db.GetEngine(ctx).
In("issue_id", issuesIDs[:limit]).
Rows(new(PullRequest))
if err != nil {
return err
}
for rows.Next() {
var pr PullRequest
err = rows.Scan(&pr)
if err != nil {
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadPullRequests: Close: %w", err1)
}
return err
}
pullRequestMaps[pr.IssueID] = &pr
}
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadPullRequests: Close: %w", err1)
}
left -= limit
issuesIDs = issuesIDs[limit:]
pullRequestMaps, err := db.GetByIDs(ctx, "issue_id", issuesIDs, &PullRequest{})
if err != nil {
return err
}
for _, issue := range issues {
@ -371,37 +286,10 @@ func (issues IssueList) LoadAttachments(ctx context.Context) (err error) {
return nil
}
attachments := make(map[int64][]*repo_model.Attachment, len(issues))
issuesIDs := issues.getIssueIDs()
left := len(issuesIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
rows, err := db.GetEngine(ctx).
In("issue_id", issuesIDs[:limit]).
Rows(new(repo_model.Attachment))
if err != nil {
return err
}
for rows.Next() {
var attachment repo_model.Attachment
err = rows.Scan(&attachment)
if err != nil {
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadAttachments: Close: %w", err1)
}
return err
}
attachments[attachment.IssueID] = append(attachments[attachment.IssueID], &attachment)
}
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadAttachments: Close: %w", err1)
}
left -= limit
issuesIDs = issuesIDs[limit:]
attachments, err := db.GetByFieldIn(ctx, "issue_id", issuesIDs, &repo_model.Attachment{})
if err != nil {
return err
}
for _, issue := range issues {
@ -418,15 +306,10 @@ func (issues IssueList) loadComments(ctx context.Context, cond builder.Cond) (er
comments := make(map[int64][]*Comment, len(issues))
issuesIDs := issues.getIssueIDs()
left := len(issuesIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
for issueIDChunk := range slices.Chunk(issuesIDs, db.DefaultMaxInSize) {
rows, err := db.GetEngine(ctx).Table("comment").
Join("INNER", "issue", "issue.id = comment.issue_id").
In("issue.id", issuesIDs[:limit]).
In("issue.id", issueIDChunk).
Where(cond).
Rows(new(Comment))
if err != nil {
@ -447,8 +330,6 @@ func (issues IssueList) loadComments(ctx context.Context, cond builder.Cond) (er
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadComments: Close: %w", err1)
}
left -= limit
issuesIDs = issuesIDs[limit:]
}
for _, issue := range issues {
@ -484,18 +365,12 @@ func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) {
}
}
left := len(ids)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
for idChunk := range slices.Chunk(ids, db.DefaultMaxInSize) {
// select issue_id, sum(time) from tracked_time where issue_id in (<issue ids in current page>) group by issue_id
rows, err := db.GetEngine(ctx).Table("tracked_time").
Where("deleted = ?", false).
Select("issue_id, sum(time) as time").
In("issue_id", ids[:limit]).
In("issue_id", idChunk).
GroupBy("issue_id").
Rows(new(totalTimesByIssue))
if err != nil {
@ -516,8 +391,6 @@ func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) {
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %w", err1)
}
left -= limit
ids = ids[limit:]
}
for _, issue := range issues {

View file

@ -94,10 +94,7 @@ func GetIssueStats(ctx context.Context, opts *IssuesOptions) (*IssueStats, error
// ids in a temporary table and join from them.
accum := &IssueStats{}
for i := 0; i < len(opts.IssueIDs); {
chunk := i + MaxQueryParameters
if chunk > len(opts.IssueIDs) {
chunk = len(opts.IssueIDs)
}
chunk := min(i+MaxQueryParameters, len(opts.IssueIDs))
stats, err := getIssueStatsChunk(ctx, opts, opts.IssueIDs[i:chunk])
if err != nil {
return nil, err

View file

@ -5,6 +5,7 @@ package issues_test
import (
"fmt"
"slices"
"sort"
"sync"
"testing"
@ -311,7 +312,7 @@ func TestIssue_ResolveMentions(t *testing.T) {
for i, user := range resolved {
ids[i] = user.ID
}
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
slices.Sort(ids)
assert.Equal(t, expected, ids)
}
@ -338,7 +339,7 @@ func TestResourceIndex(t *testing.T) {
require.NoError(t, err)
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
for i := range 100 {
wg.Add(1)
t.Run(fmt.Sprintf("issue %d", i+1), func(t *testing.T) {
t.Parallel()
@ -369,7 +370,7 @@ func TestCorrectIssueStats(t *testing.T) {
issueAmount := issues_model.MaxQueryParameters + 10
var wg sync.WaitGroup
for i := 0; i < issueAmount; i++ {
for i := range issueAmount {
wg.Add(1)
go func(i int) {
testInsertIssue(t, fmt.Sprintf("Issue %d", i+1), "Bugs are nasty", 0)

View file

@ -244,7 +244,7 @@ func UpdateIssueAttachments(ctx context.Context, issue *Issue, uuids []string) (
if err != nil {
return fmt.Errorf("FindRepoAttachmentsByUUID[uuids=%q,repoID=%d]: %w", uuids, issue.RepoID, err)
}
for i := 0; i < len(attachments); i++ {
for i := range attachments {
attachments[i].IssueID = issue.ID
if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil {
return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err)

View file

@ -63,7 +63,7 @@ func GetUnmergedPullRequestsByHeadInfoMax(ctx context.Context, repoID, olderThan
}
// GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) {
func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) (PullRequestList, error) {
prs := make([]*PullRequest, 0, 2)
sess := db.GetEngine(ctx).
Join("INNER", "issue", "issue.id = pull_request.issue_id").
@ -82,18 +82,44 @@ func CanMaintainerWriteToBranch(ctx context.Context, p access_model.Permission,
}
prs, err := GetUnmergedPullRequestsByHeadInfo(ctx, p.Units[0].RepoID, branch)
// All these error cases return `false` to defer to the safer choice of not allowing write access on an error.
if err != nil {
log.Error("GetUnmergedPullRequestsByHeadInfo failed: %s", err)
return false
} else if issues, err := prs.LoadIssues(ctx); err != nil {
log.Error("LoadIssues failed: %s", err)
return false
} else if err := issues.LoadPosters(ctx); err != nil {
log.Error("LoadPosters failed: %s", err)
return false
} else if err := prs.LoadHeadRepos(ctx); err != nil {
log.Error("LoadHeadRepos failed: %s", err)
return false
}
for _, pr := range prs {
if pr.AllowMaintainerEdit {
// PR Poster must have write access to the head, so that when they turned on "AllowMaintainerEdit" they
// delegated that write access to the maintainers of the PR base. If they don't currently have write
// access, they can't delegate that access.
poster := pr.Issue.Poster
posterHeadPerm, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, poster)
if err != nil {
log.Error("GetUserRepoPermission failed: %s", err)
continue
}
if !posterHeadPerm.CanWrite(unit.TypeCode) {
continue
}
err = pr.LoadBaseRepo(ctx)
if err != nil {
log.Error("LoadBaseRepo failed: %s", err)
continue
}
prPerm, err := access_model.GetUserRepoPermission(ctx, pr.BaseRepo, user)
if err != nil {
log.Error("GetUserRepoPermission failed: %s", err)
continue
}
if prPerm.CanWrite(unit.TypeCode) {
@ -240,6 +266,25 @@ func (prs PullRequestList) LoadIssues(ctx context.Context) (IssueList, error) {
return issueList, nil
}
func (prs PullRequestList) LoadHeadRepos(ctx context.Context) error {
repoIDs := []int64{}
for _, pr := range prs {
repoIDs = append(repoIDs, pr.HeadRepoID)
}
repos, err := db.GetByIDs(ctx, "id", repoIDs, &repo_model.Repository{})
if err != nil {
return err
}
for _, pr := range prs {
repo, ok := repos[pr.HeadRepoID]
if !ok {
return fmt.Errorf("unable to find repo %d", pr.HeadRepoID)
}
pr.HeadRepo = repo
}
return nil
}
// GetIssueIDs returns all issue ids
func (prs PullRequestList) GetIssueIDs() []int64 {
return container.FilterSlice(prs, func(pr *PullRequest) (int64, bool) {

View file

@ -7,6 +7,7 @@ import (
"bytes"
"context"
"fmt"
"slices"
"forgejo.org/models/db"
repo_model "forgejo.org/models/repo"
@ -176,6 +177,34 @@ func FindReactions(ctx context.Context, opts FindReactionsOptions) (ReactionList
return reactions, count, err
}
func getReactionsForComments(ctx context.Context, issueID int64, commentIDs []int64) (map[int64]ReactionList, error) {
reactions := make(map[int64]ReactionList, len(commentIDs))
for commentIDChunk := range slices.Chunk(commentIDs, db.DefaultMaxInSize) {
rows, err := db.GetEngine(ctx).
Where(builder.Eq{"issue_id": issueID}).
In("reaction.`type`", setting.UI.Reactions).
In("comment_id", commentIDChunk).
Rows(&Reaction{})
if err != nil {
return nil, err
}
for rows.Next() {
var reaction Reaction
err = rows.Scan(&reaction)
if err != nil {
_ = rows.Close()
return nil, err
}
reactions[reaction.CommentID] = append(reactions[reaction.CommentID], &reaction)
}
_ = rows.Close()
}
return reactions, nil
}
func createReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, error) {
reaction := &Reaction{
Type: opts.Type,

View file

@ -457,6 +457,11 @@ func SubmitReview(ctx context.Context, doer *user_model.User, issue *Issue, revi
if official, err = IsOfficialReviewer(ctx, issue, doer); err != nil {
return nil, nil, err
}
// delete previous review requests from the same user
reviewCond := builder.Eq{"reviewer_id": doer.ID, "issue_id": issue.ID}
if _, err := sess.Where(reviewCond.And(builder.Eq{"type": ReviewTypeRequest})).Delete(new(Review)); err != nil {
return nil, nil, err
}
}
review.Official = official
@ -511,10 +516,14 @@ func SubmitReview(ctx context.Context, doer *user_model.User, issue *Issue, revi
// GetReviewByIssueIDAndUserID get the latest review of reviewer for a pull request
func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*Review, error) {
return GetReviewByIssueIDUserIDAndTypes(ctx, issueID, userID, []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest})
}
func GetReviewByIssueIDUserIDAndTypes(ctx context.Context, issueID, userID int64, types []ReviewType) (*Review, error) {
review := new(Review)
has, err := db.GetEngine(ctx).Where(
builder.In("type", ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
builder.In("type", types).
And(builder.Eq{"issue_id": issueID, "reviewer_id": userID, "original_author_id": 0})).
Desc("id").
Get(review)
@ -707,12 +716,12 @@ func RemoveReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user
}
defer committer.Close()
review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID)
review, err := GetReviewByIssueIDUserIDAndTypes(ctx, issue.ID, reviewer.ID, []ReviewType{ReviewTypeRequest})
if err != nil && !IsErrReviewNotExist(err) {
return nil, err
}
if review == nil || review.Type != ReviewTypeRequest {
if review == nil {
return nil, nil
}

View file

@ -20,7 +20,7 @@ type ReviewList []*Review
// LoadReviewers loads reviewers
func (reviews ReviewList) LoadReviewers(ctx context.Context) error {
reviewerIDs := make([]int64, len(reviews))
for i := 0; i < len(reviews); i++ {
for i := range reviews {
reviewerIDs[i] = reviews[i].ReviewerID
}
reviewers, err := user_model.GetPossibleUserByIDs(ctx, reviewerIDs)

View file

@ -321,6 +321,82 @@ func TestAddReviewRequest(t *testing.T) {
assert.True(t, issues_model.IsErrReviewRequestOnClosedPR(err))
}
func TestSubmitPendingReviewDeletesReviewRequest(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
require.NoError(t, pull.LoadIssue(db.DefaultContext))
issue := pull.Issue
require.NoError(t, issue.LoadRepo(db.DefaultContext))
reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
reviewRequest, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
Issue: issue,
Reviewer: reviewer,
Type: issues_model.ReviewTypeRequest,
})
require.NoError(t, err)
// creating a pending review should NOT remove review requests
reviewPending, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
Issue: issue,
Reviewer: reviewer,
Type: issues_model.ReviewTypePending,
})
require.NoError(t, err)
unittest.AssertExistsIf(t, true, &issues_model.Review{ID: reviewRequest.ID})
// submitting a pending review to finish it SHOULD remove review requests
_, _, err = issues_model.SubmitReview(
db.DefaultContext,
reviewer,
issue,
issues_model.ReviewTypeReject,
"test content",
reviewPending.CommitID,
false,
[]string{},
)
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &issues_model.Review{ID: reviewRequest.ID})
}
// this test is for handling a state correctly that should never exist, but is representable and was
// achievable thanks to #12243
func TestReviewRequestDeletesReviewRequestsBeforeRejectedReviews(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
sess := db.GetEngine(db.DefaultContext)
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
require.NoError(t, pull.LoadIssue(db.DefaultContext))
issue := pull.Issue
require.NoError(t, issue.LoadRepo(db.DefaultContext))
reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
// this one will end up being a ReviewTypeRequest. We are initially creating it as
// ReviewTypeReject to avoid it being deleted on making the actual rejected review
reviewRequest, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
Issue: issue,
Reviewer: reviewer,
Type: issues_model.ReviewTypeReject,
})
require.NoError(t, err)
// this review is an actual rejected review that somehow managed to be saved without deleting
// reviewRequest. This is a state that is representable and is/was achievable thanks to #12243
_, err = issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
Issue: issue,
Reviewer: reviewer,
Type: issues_model.ReviewTypeReject,
})
require.NoError(t, err)
reviewRequest.Type = issues_model.ReviewTypeRequest
_, err = sess.ID(reviewRequest.ID).Cols("type").Update(reviewRequest)
require.NoError(t, err)
_, err = issues_model.RemoveReviewRequest(db.DefaultContext, issue, reviewer, doer)
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &issues_model.Review{ID: reviewRequest.ID})
}
func TestAddTeamReviewRequest(t *testing.T) {
defer unittest.OverrideFixtures("models/fixtures/TestAddTeamReviewRequest")()
require.NoError(t, unittest.PrepareTestDatabase())

View file

@ -350,10 +350,7 @@ func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed
// we get the statistics in smaller chunks and get accumulates
var accum int64
for i := 0; i < len(opts.IssueIDs); {
chunk := i + MaxQueryParameters
if chunk > len(opts.IssueIDs) {
chunk = len(opts.IssueIDs)
}
chunk := min(i+MaxQueryParameters, len(opts.IssueIDs))
time, err := getIssueTotalTrackedTimeChunk(ctx, opts, isClosed, opts.IssueIDs[i:chunk])
if err != nil {
return 0, err

View file

@ -161,10 +161,16 @@ func (t *Team) LoadRepositories(ctx context.Context) (err error) {
return err
}
// LoadMembers returns paginated members in team of organization.
// LoadMembers loads the members of the team in t.Members.
func (t *Team) LoadMembers(ctx context.Context) (err error) {
return t.LoadPaginatedMembers(ctx, db.ListOptionsAll)
}
// LoadPaginatedMembers loads paginated members of the team in t.Members.
func (t *Team) LoadPaginatedMembers(ctx context.Context, listOptions db.ListOptions) (err error) {
t.Members, err = GetTeamMembers(ctx, &SearchMembersOptions{
TeamID: t.ID,
ListOptions: listOptions,
TeamID: t.ID,
})
return err
}

View file

@ -5,11 +5,13 @@ package packages
import (
"context"
"errors"
"strconv"
"strings"
"forgejo.org/models/db"
"forgejo.org/modules/optional"
"forgejo.org/modules/setting"
"forgejo.org/modules/timeutil"
"forgejo.org/modules/util"
@ -155,6 +157,25 @@ func HasVersionFileReferences(ctx context.Context, versionID int64) (bool, error
})
}
func (pv *PackageVersion) LockForUpdate(ctx context.Context) error {
if !db.InTransaction(ctx) {
return errors.New("invalid state for PackageVersion.LockForUpdate: database is not in a transaction")
} else if setting.Database.Type.IsSQLite3() {
// SQLite both doesn't support "SELECT ... FOR UPDATE", and it's irrelevant for SQLite as the entire database is
// locked for write when a write transaction is open.
return nil
}
pvfu := PackageVersion{}
has, err := db.GetEngine(ctx).ID(pv.ID).ForUpdate().Get(&pvfu)
if err != nil {
return err
} else if !has {
return ErrPackageNotExist
}
return nil
}
// SearchValue describes a value to search
// If ExactMatch is true, the field must match the value otherwise a LIKE search is performed.
type SearchValue struct {

View file

@ -6,6 +6,7 @@ package access
import (
"context"
"fmt"
"strings"
actions_model "forgejo.org/models/actions"
"forgejo.org/models/db"
@ -115,7 +116,8 @@ func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool {
}
func (p *Permission) LogString() string {
format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [ "
var format strings.Builder
format.WriteString("<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [ ")
args := []any{p.AccessMode.String(), len(p.Units), len(p.UnitsMode)}
for i, unit := range p.Units {
@ -127,15 +129,15 @@ func (p *Permission) LogString() string {
config = err.Error()
}
}
format += "\nUnits[%d]: ID: %d RepoID: %d Type: %s Config: %s"
format.WriteString("\nUnits[%d]: ID: %d RepoID: %d Type: %s Config: %s")
args = append(args, i, unit.ID, unit.RepoID, unit.Type.LogString(), config)
}
for key, value := range p.UnitsMode {
format += "\nUnitMode[%-v]: %-v"
format.WriteString("\nUnitMode[%-v]: %-v")
args = append(args, key.LogString(), value.LogString())
}
format += " ]>"
return fmt.Sprintf(format, args...)
format.WriteString(" ]>")
return fmt.Sprintf(format.String(), args...)
}
func GetActionRepoPermission(ctx context.Context, repo *repo_model.Repository, task *actions_model.ActionTask) (Permission, error) {

View file

@ -164,7 +164,7 @@ func Test_NewColumn(t *testing.T) {
require.NoError(t, err)
assert.Len(t, columns, 3)
for i := 0; i < maxProjectColumns-3; i++ {
for i := range maxProjectColumns - 3 {
err := NewColumn(db.DefaultContext, &Column{
Title: fmt.Sprintf("column-%d", i+4),
ProjectID: project1.ID,

View file

@ -6,6 +6,7 @@ package pull
import (
"context"
"fmt"
"maps"
"forgejo.org/models/db"
"forgejo.org/modules/log"
@ -100,9 +101,7 @@ func mergeFiles(oldFiles, newFiles map[string]ViewedState) map[string]ViewedStat
return oldFiles
}
for file, viewed := range newFiles {
oldFiles[file] = viewed
}
maps.Copy(oldFiles, newFiles)
return oldFiles
}

View file

@ -14,10 +14,8 @@ import (
asymkey_model "forgejo.org/models/asymkey"
"forgejo.org/models/db"
issues_model "forgejo.org/models/issues"
access_model "forgejo.org/models/perm/access"
repo_model "forgejo.org/models/repo"
"forgejo.org/models/unit"
user_model "forgejo.org/models/user"
"forgejo.org/modules/log"
"forgejo.org/services/stats"
@ -277,7 +275,7 @@ func DoctorUserStarNum(ctx context.Context) (err error) {
}
// DeleteDeployKey delete deploy keys
func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error {
func DeleteDeployKey(ctx context.Context, id, repoID int64) error {
key, err := asymkey_model.GetDeployKeyByID(ctx, id)
if err != nil {
if asymkey_model.IsErrDeployKeyNotExist(err) {
@ -286,21 +284,10 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error
return fmt.Errorf("GetDeployKeyByID: %w", err)
}
// Check if user has access to delete this key.
if !doer.IsAdmin {
repo, err := repo_model.GetRepositoryByID(ctx, key.RepoID)
if err != nil {
return fmt.Errorf("GetRepositoryByID: %w", err)
}
has, err := access_model.IsUserRepoAdmin(ctx, repo, doer)
if err != nil {
return fmt.Errorf("GetUserRepoPermission: %w", err)
} else if !has {
return asymkey_model.ErrKeyAccessDenied{
UserID: doer.ID,
KeyID: key.ID,
Note: "deploy",
}
if key.RepoID != repoID {
return asymkey_model.ErrKeyAccessDenied{
KeyID: key.ID,
Note: "deploy",
}
}

View file

@ -6,10 +6,14 @@ package repo
import (
"context"
"errors"
"net/url"
"time"
"forgejo.org/models/db"
"forgejo.org/modules/keying"
"forgejo.org/modules/log"
"forgejo.org/modules/optional"
"forgejo.org/modules/timeutil"
"forgejo.org/modules/util"
)
@ -31,7 +35,9 @@ type Mirror struct {
LFS bool `xorm:"lfs_enabled NOT NULL DEFAULT false"`
LFSEndpoint string `xorm:"lfs_endpoint TEXT"`
RemoteAddress string `xorm:"VARCHAR(2048)"`
// Encrypted remote address w/ credentials; can be NULL if a mirror has not performed a sync since this field was
// introduced, in which case the remote address exists only in the repo's configured git remote on disk.
EncryptedRemoteAddress []byte `xorm:"BLOB NULL"`
}
func init() {
@ -73,6 +79,71 @@ func (m *Mirror) ScheduleNextUpdate() {
}
}
// InsertMirror inserts a mirror to database. RemoteAddress must be provided so that it can be encrypted and stored
// during the insert process.
func (m *Mirror) InsertWithAddress(ctx context.Context, addr string) error {
return db.WithTx(ctx, func(ctx context.Context) error {
if _, err := db.GetEngine(ctx).Insert(m); err != nil {
return err
}
return m.UpdateRemoteAddress(ctx, addr)
})
}
// Stores a credential-free version of the address in `RemoteAddress`, encrypts the original into `RemoteAddressAuth`,
// and stores both in the database. The ID of the mirror must be known, so this must be done after the mirror is
// inserted.
func (m *Mirror) UpdateRemoteAddress(ctx context.Context, addr string) error {
if m.ID == 0 {
return errors.New("must persist mirror to database before using UpdateRemoteAddress")
}
m.EncryptedRemoteAddress = keying.PullMirror.Encrypt(
[]byte(addr),
keying.ColumnAndID("remote_address_auth", m.ID),
)
_, err := db.GetEngine(ctx).ID(m.ID).Cols("encrypted_remote_address").Update(m)
return err
}
// Retrieves the encrypted remote address and decrypts it. Note that this field is expected to be absent for mirrors
// created before the introduction of EncryptedRemoteAddress, in which case credentials are not known to Forgejo
// directly (but may be on-disk in the repository's config file) and None will be returned.
func (m *Mirror) DecryptRemoteAddress() (optional.Option[string], error) {
if m.EncryptedRemoteAddress == nil {
return optional.None[string](), nil
}
contents, err := keying.PullMirror.Decrypt(m.EncryptedRemoteAddress, keying.ColumnAndID("remote_address_auth", m.ID))
if err != nil {
return optional.None[string](), err
}
return optional.Some(string(contents)), nil
}
// Retrieves the remote address but sanitizes it of sensitive credentials. May be absent for mirrors created before the
// introduction of EncryptedRemoteAddress.
func (m *Mirror) SanitizedRemoteAddress() (optional.Option[string], error) {
maybeAddr, err := m.DecryptRemoteAddress()
if err != nil {
return optional.None[string](), err
} else if has, addr := maybeAddr.Get(); has {
parsedURL, err := url.Parse(addr)
if err != nil {
return optional.None[string](), err
}
// Remove the password if present. Retain the username for consistency with `AddAuthCredentialHelperForRemote`
// which retains the username for the `git clone` command line, which ends up as the remote URL in the mirror's
// git config.
if parsedURL.User != nil {
parsedURL.User = url.User(parsedURL.User.Username())
}
return optional.Some(parsedURL.String()), nil
}
return optional.None[string](), nil
}
// GetMirrorByRepoID returns mirror information of a repository.
func GetMirrorByRepoID(ctx context.Context, repoID int64) (*Mirror, error) {
m := &Mirror{RepoID: repoID}
@ -115,9 +186,3 @@ func MirrorsIterate(ctx context.Context, limit int, f func(idx int, bean any) er
}
return sess.Iterate(new(Mirror), f)
}
// InsertMirror inserts a mirror to database
func InsertMirror(ctx context.Context, mirror *Mirror) error {
_, err := db.GetEngine(ctx).Insert(mirror)
return err
}

View file

@ -608,6 +608,7 @@ func InsertReleases(ctx context.Context, rels ...*Release) error {
if len(rel.Attachments) > 0 {
for i := range rel.Attachments {
rel.Attachments[i].ReleaseID = rel.ID
rel.Attachments[i].RepoID = rel.RepoID
}
if _, err := sess.NoAutoTime().Insert(rel.Attachments); err != nil {

View file

@ -20,11 +20,14 @@ func TestMigrate_InsertReleases(t *testing.T) {
UUID: "a0eebc91-9c0c-4ef7-bb6e-6bb9bd380a12",
}
r := &Release{
RepoID: 1001,
Attachments: []*Attachment{a},
}
err := InsertReleases(db.DefaultContext, r)
require.NoError(t, err)
assert.EqualValues(t, 1001, unittest.AssertExistsAndLoadBean(t, &Attachment{UUID: "a0eebc91-9c0c-4ef7-bb6e-6bb9bd380a12"}).RepoID)
}
func TestReleaseLoadRepo(t *testing.T) {

View file

@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"html/template"
"maps"
"net"
"net/url"
"path/filepath"
@ -543,9 +544,7 @@ func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string {
func (repo *Repository) ComposeDocumentMetas(ctx context.Context) map[string]string {
if len(repo.DocumentRenderingMetas) == 0 {
metas := map[string]string{}
for k, v := range repo.ComposeMetas(ctx) {
metas[k] = v
}
maps.Copy(metas, repo.ComposeMetas(ctx))
metas["mode"] = "document"
repo.DocumentRenderingMetas = metas
}
@ -786,8 +785,8 @@ func GetRepositoryByName(ctx context.Context, ownerID int64, name string) (*Repo
// getRepositoryURLPathSegments returns segments (owner, reponame) extracted from a url
func getRepositoryURLPathSegments(repoURL string) []string {
if strings.HasPrefix(repoURL, setting.AppURL) {
return strings.Split(strings.TrimPrefix(repoURL, setting.AppURL), "/")
if after, ok := strings.CutPrefix(repoURL, setting.AppURL); ok {
return strings.Split(after, "/")
}
sshURLVariants := [4]string{
@ -798,8 +797,8 @@ func getRepositoryURLPathSegments(repoURL string) []string {
}
for _, sshURL := range sshURLVariants {
if strings.HasPrefix(repoURL, sshURL) {
return strings.Split(strings.TrimPrefix(repoURL, sshURL), "/")
if after, ok := strings.CutPrefix(repoURL, sshURL); ok {
return strings.Split(after, "/")
}
}

View file

@ -361,7 +361,7 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
}
// Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate
if opts.OwnerID > 0 {
if opts.OwnerID != 0 {
accessCond := builder.NewCond()
if !opts.Collaborate.ValueOrZeroValue() {
accessCond = builder.Eq{"owner_id": opts.OwnerID}
@ -401,7 +401,7 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
if opts.Keyword != "" {
// separate keyword
subQueryCond := builder.NewCond()
for _, v := range strings.Split(opts.Keyword, ",") {
for v := range strings.SplitSeq(opts.Keyword, ",") {
if opts.TopicOnly {
subQueryCond = subQueryCond.Or(builder.Eq{"topic.name": strings.ToLower(v)})
} else {
@ -416,7 +416,7 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
keywordCond := builder.In("id", subQuery)
if !opts.TopicOnly {
likes := builder.NewCond()
for _, v := range strings.Split(opts.Keyword, ",") {
for v := range strings.SplitSeq(opts.Keyword, ",") {
likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)})
// If the string looks like "org/repo", match against that pattern too

View file

@ -237,10 +237,8 @@ func (cfg *ActionsConfig) IsWorkflowDisabled(file string) bool {
}
func (cfg *ActionsConfig) DisableWorkflow(file string) {
for _, workflow := range cfg.DisabledWorkflows {
if file == workflow {
return
}
if slices.Contains(cfg.DisabledWorkflows, file) {
return
}
cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file)

View file

@ -117,7 +117,7 @@ func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) {
defer committer.Close()
ids := make([]int64, len(uploads))
for i := 0; i < len(uploads); i++ {
for i := range uploads {
ids[i] = uploads[i].ID
}
if err = db.DeleteByIDs[Upload](ctx, ids...); err != nil {

View file

@ -20,7 +20,7 @@ import (
var (
namePattern = regexp.MustCompile("(?i)^[A-Z_][A-Z0-9_]*$")
forbiddenPrefixPattern = regexp.MustCompile("(?i)^FORGEJO_|GITEA_|GITHUB_")
forbiddenPrefixPattern = regexp.MustCompile("(?i)^(FORGEJO_|GITEA_|GITHUB_|[0-9])")
ErrInvalidName = util.NewInvalidArgumentErrorf("invalid secret name")
)

View file

@ -257,12 +257,18 @@ func TestSecretValidateName(t *testing.T) {
valid bool
}{
{"FORGEJO_", false},
{"PRE_FORGEJO_", true},
{"PRE_FORGEJO_SUF", true},
{"FORGEJO_123", false},
{"FORGEJO_ABC", false},
{"GITEA_", false},
{"PRE_GITEA_", true},
{"PRE_GITEA_SUF", true},
{"GITEA_123", false},
{"GITEA_ABC", false},
{"GITHUB_", false},
{"PRE_GITHUB_", true},
{"PRE_GITHUB_SUF", true},
{"GITHUB_123", false},
{"GITHUB_ABC", false},
{"123_TEST", false},

View file

@ -248,22 +248,12 @@ func LoadUnitConfig() error {
// UnitGlobalDisabled checks if unit type is global disabled
func (u Type) UnitGlobalDisabled() bool {
for _, ud := range DisabledRepoUnitsGet() {
if u == ud {
return true
}
}
return false
return slices.Contains(DisabledRepoUnitsGet(), u)
}
// CanBeDefault checks if the unit type can be a default repo unit
func (u *Type) CanBeDefault() bool {
for _, nadU := range NotAllowedDefaultRepoUnits {
if *u == nadU {
return false
}
}
return true
return !slices.Contains(NotAllowedDefaultRepoUnits, *u)
}
// Unit is a section of one repository

View file

@ -151,8 +151,8 @@ func (l *loader) buildFixtureFile(fixturePath string) (*fixtureFile, error) {
switch v := value.(type) {
case string:
// Try to decode hex.
if strings.HasPrefix(v, "0x") {
value, err = hex.DecodeString(strings.TrimPrefix(v, "0x"))
if after, ok := strings.CutPrefix(v, "0x"); ok {
value, err = hex.DecodeString(after)
if err != nil {
return nil, err
}

View file

@ -102,13 +102,13 @@ func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveM
// parse back the fixture file into a series of HTTP headers followed by response body
lines := strings.Split(stringFixture, "\n")
for idx, line := range lines {
colonIndex := strings.Index(line, ": ")
if colonIndex != -1 {
before, after, ok := strings.Cut(line, ": ")
if ok {
// Because we modified the body with ReplaceAll() above, we need to
// remove Content-Length. w.Write() should add it back.
header := line[0:colonIndex]
header := before
if !strings.EqualFold(header, "Content-Length") {
w.Header().Set(line[0:colonIndex], line[colonIndex+2:])
w.Header().Set(before, after)
}
} else {
// we reached the end of the headers (empty line), so what follows is the body

View file

@ -9,7 +9,7 @@ import (
)
func fieldByName(v reflect.Value, field string) reflect.Value {
if v.Kind() == reflect.Ptr {
if v.Kind() == reflect.Pointer {
v = v.Elem()
}
f := v.FieldByName(field)

View file

@ -108,7 +108,7 @@ func (u *User) IsUploadAvatarChanged(data []byte) bool {
if !u.UseCustomAvatar || len(u.Avatar) == 0 {
return true
}
avatarID := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data)))))
avatarID := fmt.Sprintf("%x", md5.Sum(fmt.Appendf(nil, "%d-%x", u.ID, md5.Sum(data))))
return u.Avatar != avatarID
}

View file

@ -5,6 +5,7 @@ package user_test
import (
"fmt"
"slices"
"testing"
"forgejo.org/models/db"
@ -77,12 +78,7 @@ func TestListEmails(t *testing.T) {
assert.Greater(t, count, int64(5))
contains := func(match func(s *user_model.SearchEmailResult) bool) bool {
for _, v := range emails {
if match(v) {
return true
}
}
return false
return slices.ContainsFunc(emails, match)
}
assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 18 }))

View file

@ -87,7 +87,7 @@ func newUserData(user *User) UserData {
// (e.g. FieldName -> field_name) corresponding to UserData struct fields.
var userDataColumnNames = sync.OnceValue(func() []string {
mapper := new(names.GonicMapper)
udType := reflect.TypeOf(UserData{})
udType := reflect.TypeFor[UserData]()
columnNames := make([]string, 0, udType.NumField())
for i := 0; i < udType.NumField(); i++ {
columnNames = append(columnNames, mapper.Obj2Table(udType.Field(i).Name))

View file

@ -1243,8 +1243,8 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) {
}
// Finally, if email address is the protected email address:
if strings.HasSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) {
username := strings.TrimSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress))
if before, ok := strings.CutSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)); ok {
username := before
user := &User{}
has, err := db.GetEngine(ctx).Where("lower_name=?", username).Get(user)
if err != nil {

View file

@ -273,9 +273,9 @@ func TestHashPasswordDeterministic(t *testing.T) {
b := make([]byte, 16)
u := &user_model.User{}
algos := hash.RecommendedHashAlgorithms
for j := 0; j < len(algos); j++ {
for j := range algos {
u.PasswdHashAlgo = algos[j]
for i := 0; i < 50; i++ {
for range 50 {
// generate a random password
rand.Read(b)
pass := string(b)

View file

@ -429,7 +429,7 @@ func CreateWebhooks(ctx context.Context, ws []*Webhook) error {
if len(ws) == 0 {
return nil
}
for i := 0; i < len(ws); i++ {
for i := range ws {
ws[i].Type = strings.TrimSpace(ws[i].Type)
}
return db.Insert(ctx, ws)

View file

@ -7,6 +7,7 @@ import (
"bytes"
"fmt"
"io"
"slices"
"strings"
actions_model "forgejo.org/models/actions"
@ -609,11 +610,8 @@ func matchPullRequestReviewEvent(prPayload *api.PullRequestPayload, evt *jobpars
matched := false
for _, val := range vals {
for _, action := range actions {
if glob.MustCompile(val, '/').Match(action) {
matched = true
break
}
if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) {
matched = true
}
if matched {
break
@ -658,11 +656,8 @@ func matchPullRequestReviewCommentEvent(prPayload *api.PullRequestPayload, evt *
matched := false
for _, val := range vals {
for _, action := range actions {
if glob.MustCompile(val, '/').Match(action) {
matched = true
break
}
if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) {
matched = true
}
if matched {
break

Some files were not shown because too many files have changed in this diff Show more