Compare commits

...

43 commits

Author SHA1 Message Date
David Rotermund
24bbe20487 Merge branch 'forgejo' into delete_with_subpath
Some checks failed
Integration tests for the release process / release-simulation (push) Has been cancelled
2025-06-23 17:23:49 +02:00
Danko Aleksejevs
39e6785da0 fix(ui): erroneous list continuation on Cmd+Enter on macOS (#8153) (#8170)
The line continuation code in the Markdown editor ignored Enter presses if Ctrl, Alt or Shift were being held. This now also accounts for Cmd on macOS (which browsers represent as metaKey).

### Tests

- Use Safari (on macOS)
- create a new issue in a repository
- start writing a list (with - one[enter]- two)
- now press Cmd+Enter
- verify that while the form is being submitted, no new line got visually added

***

The visual evidence of this bug is a race condition (form is being edited while also being submitted) and I don't think a reliable cross-browser E2E test is possible.

Bugged and fixed behavior verified manually on an actual Macbook in current Safari. Specifically,

* Before the fix: pressing Cmd+Enter submits the form *and* inserts a newline + list prefix in the markdown editor.  Reporter had the changed content submitted, I only saw it submit the original (but the change was visible during submission).
* After the fix: Cmd+Enter only submits the form. Enter with no modifiers inserts newlines/prefixes as before.

### 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

- [ ] I do not want this change to show in the release notes.
- [x] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8170
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: Danko Aleksejevs <danko@very.lv>
Co-committed-by: Danko Aleksejevs <danko@very.lv>
2025-06-23 15:21:15 +02:00
Earl Warren
debd74e1b6 fix: add an index to the ActionRun.stopped column (#8252)
The models/actions/run.go:GetRunBefore function sorts ActionRun rows to get the most recently stopped. Since the ActionRun rows do not expire, the cost will keep increasing over time.

The index is meant to ensure the execution time of the associated query does not grow linearly with the number of rows in the ActionRun table.

Ref https://codeberg.org/forgejo/forgejo/pulls/7491/files#issuecomment-5495441

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8252
Reviewed-by: Christopher Besch <mail@chris-besch.com>
Co-authored-by: Earl Warren <contact@earl-warren.org>
Co-committed-by: Earl Warren <contact@earl-warren.org>
2025-06-23 08:00:18 +02:00
Earl Warren
cf4d0e6c34 bug: unify RepoActionRun and ActionRun structs (#8250)
Two pull requests were merged at the same time

- https://codeberg.org/forgejo/forgejo/pulls/7699
- https://codeberg.org/forgejo/forgejo/pulls/7508

And added conflicting structs ActionRun modules/structs.  That broke
the forgejo development branch and a quick fix was made to resolve
the name conflict.

- https://codeberg.org/forgejo/forgejo/pulls/8066

However that creates an undesirable duplication of two structures that
serve the same purpose but are different.

- Remove RepoActionRun and replace it with ActionRun
- convert.ToActionRun has one more argument, the doer, because it
  is determined differently in the context of webhooks or API

### Tests

- No need because the two pull requests involved already have good coverage.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8250
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Reviewed-by: klausfyhn <klausfyhn@noreply.codeberg.org>
Reviewed-by: Christopher Besch <mail@chris-besch.com>
Co-authored-by: Earl Warren <contact@earl-warren.org>
Co-committed-by: Earl Warren <contact@earl-warren.org>
2025-06-23 07:54:32 +02:00
Renovate Bot
b58cebc2d9 Update renovate to v41.1.4 (forgejo) (#8256)
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2025-06-23 07:40:04 +02:00
John Veness
d8ad592d4e Fix sentence structure mentioning cooldown period (#8197)
The text should be two sentences, or at the very least separated by a semicolon rather than a comma.

## 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

- 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 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] I do not want this change to show in the release notes.
- [ ] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8197
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: John Veness <john-codeberg@jveness.co.uk>
Co-committed-by: John Veness <john-codeberg@jveness.co.uk>
2025-06-22 08:51:01 +02:00
Renovate Bot
b6dd1dd799 Update renovate to v41 (forgejo) (major) (#8253)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8253
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2025-06-21 18:42:42 +02:00
Earl Warren
d7329f5dd4 fix(ui): issue comment anchor on time stamp (#8214)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8214
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2025-06-21 14:51:50 +02:00
Alex Smith
690532efb8 add model viewer for .glb (GLTF) model in file view (#8111)
## Motivation

The GLTF (`.gltf`, `.glb`) 3D model format is very popular for game development and visual productions.

For an indie game studio, it would be convenient for a team to view textured 3D models directly from the Forgejo interface (otherwise they need to be downloaded and opened). [Perforce](https://www.perforce.com/products/helix-dam), [Diversion](https://www.diversion.dev/), and GitHub all have this capability to differing extents.

Some discussion on 3D file support here: https://codeberg.org/forgejo/forgejo/issues/5188

## Changes

Adds a model viewer similar to [GitHub STL viewer](https://github.com/assimp/assimp/blob/master/test/models/STL/Spider_ascii.stl) for `.glb` model files, and lays some groundwork to support future files. Uses the [model-viewer](https://modelviewer.dev/) library by Google and three.js. The model viewer is interactive and can be rotated and scaled.

![Screen Recording 2025-06-08 at 15.27.15](/attachments/84c63dea-a0ce-45f9-b48b-c80867636639)

## How to Test

1) Create a new repository or use an existing one.
2) Upload a `.glb` file such as `tests/testdata/data/viewer/Unicode❤♻Test.glb` (CC0 1.0 Universal)
3) View the file in the repository.
    - Similar to image files, the 3D model should be rendered in a viewer.
    - Use mouse clicks to turn and zoom.

## Licenses

Libraries used for this change include three.js and @google/model-viewer, which are MIT and Apache-2.0 licenses respectively. Both of these are compatible with Forgejo's GPL3.0 license.

## Future Plans

1) `.gltf` was not attempted because it is a multiple file format, referencing other files in the same directory. Still need to experiment with this to see if it can work. `.glb` is a single file containing a `.gltf` and all of its other file/texture dependencies so was easier to implement.
2) The PR diff still shows the model as an unviewable bin file, but clicking the "View File" button takes you to a view screen where this model viewer is used. It would be nice to view the before and after of the model in two side-by-side model viewers, akin to reviewing a change in an image.
3) Also inserted stubs for adding contexts for GLTF, STL, OBJ, and 3MF. These ultimately don't do anything yet as only `.glb` files can be detected by the type sniffer of all of these.

## 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

- I added test coverage for checking GLB file content using the first few bytes.
  - [x] in their respective `typesniffer_test.go` for unit 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

- [ ] I do not want this change to show in the release notes.
- [ ] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

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

## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- User Interface features
  - [PR](https://codeberg.org/forgejo/forgejo/pulls/8111): <!--number 8111 --><!--line 0 --><!--description YWRkIG1vZGVsIHZpZXdlciBmb3IgYC5nbGJgIChHTFRGKSBtb2RlbCBpbiBmaWxlIHZpZXc=-->add model viewer for `.glb` (GLTF) model in file view<!--description-->
<!--end release-notes-assistant-->

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8111
Reviewed-by: oliverpool <oliverpool@noreply.codeberg.org>
Co-authored-by: Alex Smith <amsmith.pro@pm.me>
Co-committed-by: Alex Smith <amsmith.pro@pm.me>
2025-06-21 14:42:35 +02:00
Earl Warren
b2c4fc9f94 bug: Forgejo Actions email notifications are opt-in (#8242)
* Add the `notify-email` column / NotifyEmail to ActionRun and set it:
  * services/actions/workflows.go `Dispatch`
  * services/actions/schedule_tasks.go `CreateScheduleTask`
  * services/actions/notifier_helper.go `handleWorkflows`
* Only send an email if the workflow has `enable-email-notifications: true` by having `MailActionRun` return immediately if `NotifyEmail` is false.
* Ignore or silently fail on `enable-email-notifications: true` parsing errors. Reporting such errors  belongs in workflow validation, not when it is evaluated for the notifications.
* Add unit and integration tests.

Refs: https://codeberg.org/forgejo/forgejo/issues/8187

## 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

- 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.

### Documentation

- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] I do not want this change to show in the release notes.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8242
Reviewed-by: Christopher Besch <mail@chris-besch.com>
Co-authored-by: Earl Warren <contact@earl-warren.org>
Co-committed-by: Earl Warren <contact@earl-warren.org>
2025-06-21 13:11:01 +02:00
Earl Warren
9e6f722f94 fix: only send Forgejo Actions notifications to one user (#8227)
- If the run was attributed to a system user (which is the case for scheduled runs for instance), ignore it and fallback to the mail of the owner. System users may have email addresses, but they are not to be used.
- If the owner is a system user or an organization with no email associated with it, do nothing.
- If a user with an email exists, check if they did not disable notifications and send the email.

Refs: https://codeberg.org/forgejo/forgejo/issues/8187
Refs: https://codeberg.org/forgejo/forgejo/issues/8233

## 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

- I added test coverage for Go changes...
  - [x] in their respective `*_test.go` for unit tests.

### Documentation

- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] I do not want this change to show in the release notes.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8227
Reviewed-by: Christopher Besch <mail@chris-besch.com>
Co-authored-by: Earl Warren <contact@earl-warren.org>
Co-committed-by: Earl Warren <contact@earl-warren.org>
2025-06-21 12:15:38 +02:00
Michael Jerger
25d596d387 Federated user activity following: Isolated model changes (#8078)
This PR is part of https://codeberg.org/forgejo/forgejo/pulls/4767

This should not have an outside impact but bring all model changes needed & bring migrations.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8078
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Michael Jerger <michael.jerger@meissa-gmbh.de>
Co-committed-by: Michael Jerger <michael.jerger@meissa-gmbh.de>
2025-06-21 12:02:58 +02:00
Otto Richter
1c0e9d8015 chore(ci): downgrade playwright temporarily and allow running all e2e tests (#8245)
In https://codeberg.org/forgejo/forgejo/pulls/7906#issuecomment-5511884, I noticed that the e2e tests were failing without obvious reasons. I was able to reproduce locally with Mobile Chrome, but the error doesn't make sense to me.

So I tried to downgrade playwright to the previous version, and it works fine. I actually suspect a bug in playwright, but I currently lack the capacity to reach out to upstream with a reproducer (I also found conversation with the playwright team a little difficult, unless you have absolutely convincing arguments that the flakiness you observe is really their fault, which is very hard to prove).

Tests pass with this version of playwright. In order to detect such cases earlier, I added a way to run all playwright tests (which I thought I had added with the changed files patch, but apparently forgot).

All tests are triggered by an explicit label (but only after firing a next event, because the testing workflows don't listen to label changes) and when the PR title contains "playwright", which should cover at least renovate dependency updates of playwright and the axe framework (both contain playwright).

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8245
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: Otto Richter <git@otto.splvs.net>
Co-committed-by: Otto Richter <git@otto.splvs.net>
2025-06-21 11:50:39 +02:00
Renovate Bot
1efb4f1aaf Update module github.com/go-chi/chi/v5 to v5.2.2 (forgejo) (#8248)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) | require | patch | `v5.2.1` -> `v5.2.2` |

---

### Release Notes

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

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

[Compare Source](https://github.com/go-chi/chi/compare/v5.2.1...v5.2.2)

#### What's Changed

- Use strings.Cut in a few places by [@&#8203;JRaspass](https://github.com/JRaspass) in https://github.com/go-chi/chi/pull/971
- Fix non-constant format strings in t.Fatalf by [@&#8203;JRaspass](https://github.com/JRaspass) in https://github.com/go-chi/chi/pull/972
- Apply fieldalignment fixes to optimize struct memory layout by [@&#8203;pixel365](https://github.com/pixel365) in https://github.com/go-chi/chi/pull/974
- go 1.24 by [@&#8203;pkieltyka](https://github.com/pkieltyka) in https://github.com/go-chi/chi/pull/977
- chore: delint ioutil usage by [@&#8203;costela](https://github.com/costela) in https://github.com/go-chi/chi/pull/962
- Fixed typo in Router interface definition by [@&#8203;mithileshgupta12](https://github.com/mithileshgupta12) in https://github.com/go-chi/chi/pull/958
- Add support for TinyGo by [@&#8203;efraimbart](https://github.com/efraimbart) in https://github.com/go-chi/chi/pull/978
- Exclude middleware/profiler.go in TinyGo, as there's no net/http/pprof pkg by [@&#8203;cxjava](https://github.com/cxjava) in https://github.com/go-chi/chi/pull/982
- Make use of strings.Cut by [@&#8203;scop](https://github.com/scop) in https://github.com/go-chi/chi/pull/1005
- Change install command format to code block by [@&#8203;sglkc](https://github.com/sglkc) in https://github.com/go-chi/chi/pull/1001
- Correct documentation by [@&#8203;mrdomino](https://github.com/mrdomino) in https://github.com/go-chi/chi/pull/992

#### Security fix

- Fixes [GHSA-vrw8-fxc6-2r93](https://github.com/go-chi/chi/security/advisories/GHSA-vrw8-fxc6-2r93) - "Host Header Injection Leads to Open Redirect in RedirectSlashes" [commit](1be7ad938c)
  - a lower-severity Open Redirect that can't be exploited in browser or email client, as it requires manipulation of a Host header
  - reported by Anuraag Baishya, [@&#8203;anuraagbaishya](https://github.com/anuraagbaishya). Thank you!

#### New Contributors

- [@&#8203;pixel365](https://github.com/pixel365) made their first contribution in https://github.com/go-chi/chi/pull/974
- [@&#8203;mithileshgupta12](https://github.com/mithileshgupta12) made their first contribution in https://github.com/go-chi/chi/pull/958
- [@&#8203;efraimbart](https://github.com/efraimbart) made their first contribution in https://github.com/go-chi/chi/pull/978
- [@&#8203;cxjava](https://github.com/cxjava) made their first contribution in https://github.com/go-chi/chi/pull/982
- [@&#8203;sglkc](https://github.com/sglkc) made their first contribution in https://github.com/go-chi/chi/pull/1001
- [@&#8203;mrdomino](https://github.com/mrdomino) made their first contribution in https://github.com/go-chi/chi/pull/992

**Full Changelog**: https://github.com/go-chi/chi/compare/v5.2.1...v5.2.2

</details>

---

### Configuration

📅 **Schedule**: Branch creation - Between 12:00 AM and 03:59 AM ( * 0-3 * * * ) (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:eyJjcmVhdGVkSW5WZXIiOiI0MC41Ny4xIiwidXBkYXRlZEluVmVyIjoiNDAuNTcuMSIsInRhcmdldEJyYW5jaCI6ImZvcmdlam8iLCJsYWJlbHMiOlsiZGVwZW5kZW5jeS11cGdyYWRlIiwidGVzdC9ub3QtbmVlZGVkIl19-->

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8248
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2025-06-21 10:57:17 +02:00
Earl Warren
c8e54e11d7 feat: git/blob use NewTruncatedReader for profile and codeowners (#8243)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8243
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2025-06-20 21:41:30 +02:00
oliverpool
f708bacfff blob: use NewTruncatedReader for CodeOwners parsing
tested in tests/integration/pull_review_test.go:TestPullView_CodeOwner
2025-06-20 20:43:10 +02:00
oliverpool
dd79f0ce2b blob: use NewTruncatedReader for markdown 2025-06-20 20:43:10 +02:00
oliverpool
c78f56e7cb git/blob: add truncated tests 2025-06-20 20:43:10 +02:00
Robert Wolff
ef27d55468 chore(ui): add integration tests for issue comment badges and avatars 2025-06-19 19:10:25 +02:00
oliverpool
31ad7c9353 blob: GetBlobContent: reduce allocations (#8223)
See #8222 for context.

## git.Blob.NewTruncatedReader

This introduce a new `NewTruncatedReader` method to return a blob-reader which silently truncates when the limit is reached (io.EOF will be returned).
Since the actual size is also returned `GetBlobContent` can pre-allocate a `[]byte` of the full-size (min of the asked size and the actual size) and call `io.ReadFull(rc, buf)` (instead of `util.ReadWithLimit(dataRc, int(limit))` which is convoluted and not used anywhere else).

### Tests

- I added test coverage for Go changes...
  - [x] in their respective `*_test.go` for unit tests.

### Documentation

- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] I do not want this change to show in the release notes.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8223
Reviewed-by: Lucas <sclu1034@noreply.codeberg.org>
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: oliverpool <git@olivier.pfad.fr>
Co-committed-by: oliverpool <git@olivier.pfad.fr>
2025-06-19 18:36:12 +02:00
oliverpool
913eaffb8a fix: collaborator can edit wiki with write access (#8234)
fixes: #8119, replaces #8135

Bug likely introduced in 5eeccecafc

### Tests

- I added test coverage for Go changes in the `tests/integration` directory if it involves interactions with a live Forgejo server.

### Documentation

- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] I do not want this change to show in the release notes.
- [x] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8234
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: oliverpool <git@olivier.pfad.fr>
Co-committed-by: oliverpool <git@olivier.pfad.fr>
2025-06-19 18:35:09 +02:00
oliverpool
285f66b782 fix: prevent 500 message on invalid username (#8236)
Bug introduced by #7998

### Tests

- go to your settings page https://v12.next.forgejo.org/user/settings
- try to change to an invalid username (like `.well--invalid`)
- verify that no 500 error is shown, only a flash message

I tried to add a test in `tests/integration/user_test.go`, but failed to catch the error message:

```
		resp := session.MakeRequest(t, req, http.StatusOK)
		txt := resp.Body.String()
		t.Log(txt) // no template error??
		t.FailNow()
```

### Release notes

- [x] I do not want this change to show in the release notes.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8236
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: oliverpool <git@olivier.pfad.fr>
Co-committed-by: oliverpool <git@olivier.pfad.fr>
2025-06-19 18:31:08 +02:00
Earl Warren
968ba1bf84 i18n: update of translations from Codeberg Translate (#8178)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8178
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
2025-06-19 07:59:28 +02:00
Codeberg Translate
42ea73d46f
i18n: update of translations from Codeberg Translate
Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: Benedikt Straub <benedikt-straub@web.de>
Co-authored-by: Codeberg Translate <translate@codeberg.org>
Co-authored-by: Dirk <dirk@noreply.codeberg.org>
Co-authored-by: Edgarsons <edgarsons@noreply.codeberg.org>
Co-authored-by: Fjuro <git@alius.cz>
Co-authored-by: Juno Takano <jutty@noreply.codeberg.org>
Co-authored-by: Laurent FAVOLE <lfavole@noreply.codeberg.org>
Co-authored-by: Lzebulon <lzebulon@noreply.codeberg.org>
Co-authored-by: Outbreak2096 <outbreak2096@noreply.codeberg.org>
Co-authored-by: SomeTr <sometr@noreply.codeberg.org>
Co-authored-by: Tin <hntin@noreply.codeberg.org>
Co-authored-by: Vyxie <kitakita@disroot.org>
Co-authored-by: earl-warren <earl-warren@noreply.codeberg.org>
Co-authored-by: hntin <hntin@noreply.codeberg.org>
Co-authored-by: janAkali <janakali@noreply.codeberg.org>
Co-authored-by: justbispo <justbispo@noreply.codeberg.org>
Co-authored-by: tacaly <frederick@tacaly.com>
Co-authored-by: volkan <volkan@noreply.codeberg.org>
Co-authored-by: xtex <xtexchooser@duck.com>
Co-authored-by: yeager <yeager@noreply.codeberg.org>
Co-authored-by: yurtpage <yurtpage@noreply.codeberg.org>
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/cs/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/da/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/de/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/fr/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/lv/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/nds/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/pt_BR/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/pt_PT/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/ru/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/sv/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/uk/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/zh_Hans/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/cs/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/da/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/de/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/fil/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/fr/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/lv/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/nds/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/ru/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/sv/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/tr/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/uk/
Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/vi/
Translation: Forgejo/forgejo
Translation: Forgejo/forgejo-next
2025-06-19 05:14:47 +00:00
Gusted
e7eca7f36c chore: migrate to @stylistic/eslint-plugin (#8216)
- The JS variant is deprecated, move to the unified package.
`[@stylistic/eslint-plugin-js] This package is deprecated in favor of
the unified @stylistic/eslint-plugin, please consider migrating to the
main package` is logged when running `make lint-frontend`.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8216
Reviewed-by: floss4good <floss4good@noreply.codeberg.org>
Reviewed-by: Otto <otto@codeberg.org>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-committed-by: Gusted <postmaster@gusted.xyz>
2025-06-19 07:14:42 +02:00
Gusted
5fa37539de chore: sort mailer messages in test assertion (#8226)
- Ref https://codeberg.org/forgejo/forgejo/issues/8221#issuecomment-5461218
- Databases might return users in a different order, sort them before doing assertions on them.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8226
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-committed-by: Gusted <postmaster@gusted.xyz>
2025-06-18 21:12:08 +02:00
Robert Wolff
1e114a1225 chore(ui): add integration tests for issue comments 2025-06-18 18:09:34 +02:00
Robert Wolff
1879ce8efe fix(ui): issue comment anchor on time stamp 2025-06-18 17:28:19 +02:00
Danko Aleksejevs
ae00a1d61b fix: Remove 1ms delay before inserting list prefix, fix race condition in tests (#8207)
Fixed the race condition that made the existing E2E tests fail. There was a 1ms delay between inserting a newline and the line prefix to facilitate creation of two "undo" entries (so "ctrl+z" basically undoes the list continuation, but not the newline). Thus scripted text changes may have happened out of order.

This only ever reliably worked in Firefox and seems to still work there even without a timeout.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8207
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Danko Aleksejevs <danko@very.lv>
Co-committed-by: Danko Aleksejevs <danko@very.lv>
2025-06-18 12:58:31 +02:00
Renovate Bot
321561d315 Update data.forgejo.org/oci/alpine Docker tag to v3.22 (forgejo) (#8218)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8218
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2025-06-18 12:47:18 +02:00
Earl Warren
b1e75421c1 chore(release-notes): Forgejo v11.0.2 (#8224)
Co-authored-by: forgejo-release-manager <contact-forgejo-release-manager@forgejo.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8224
Reviewed-by: Lucas <sclu1034@noreply.codeberg.org>
2025-06-18 11:31:23 +02:00
Earl Warren
e934d0a3f3 fix(tests): TestInitInstructions must use forEachObjectFormat (#8220)
Otherwise it [fails with older git versions](https://codeberg.org/forgejo-integration/forgejo/actions/runs/10341/jobs/1#jobstep-5-2706).

```
--- FAIL: TestInitInstructions (0.12s)
    testlogger.go:411: 2025/06/18 00:32:37 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /workspace/***/forgejo/tests/gitea-lfs-meta
    testlogger.go:411: 2025/06/18 00:32:37 ...eb/routing/logger.go:102:func1() [I] router: completed GET /user/login for test-mock:12345, 200 OK in 4.7ms @ auth/auth.go:145(auth.SignIn)
    testlogger.go:411: 2025/06/18 00:32:37 ...eb/routing/logger.go:102:func1() [I] router: completed POST /user/login for test-mock:12345, 303 See Other in 3.8ms @ auth/auth.go:179(auth.SignInPost)
    repo_test.go:1456:
        	Error Trace:	/workspace/***/forgejo/tests/test_utils.go:383
        	            				/workspace/***/forgejo/tests/integration/repo_test.go:1456
        	Error:      	Received unexpected error:
        	            	initRepository: git.InitRepository: invalid object format: sha256
        	Test:       	TestInitInstructions
```

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8220
Reviewed-by: Antonin Delpeuch <wetneb@noreply.codeberg.org>
Reviewed-by: oliverpool <oliverpool@noreply.codeberg.org>
Co-authored-by: Earl Warren <contact@earl-warren.org>
Co-committed-by: Earl Warren <contact@earl-warren.org>
2025-06-18 10:56:30 +02:00
Renovate Bot
34987a2be7 Update module code.forgejo.org/forgejo/act to v1.28.0 (forgejo) (#8219)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [code.forgejo.org/forgejo/act](https://code.forgejo.org/forgejo/act) | replace | minor | `v1.26.0` -> `v1.28.0` |

---

### Release Notes

<details>
<summary>forgejo/act (code.forgejo.org/forgejo/act)</summary>

### [`v1.28.0`](https://code.forgejo.org/forgejo/act/compare/v1.27.0...v1.28.0)

[Compare Source](https://code.forgejo.org/forgejo/act/compare/v1.27.0...v1.28.0)

### [`v1.27.0`](https://code.forgejo.org/forgejo/act/compare/v1.26.0...v1.27.0)

[Compare Source](https://code.forgejo.org/forgejo/act/compare/v1.26.0...v1.27.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - Between 12:00 AM and 03:59 AM ( * 0-3 * * * ) (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:eyJjcmVhdGVkSW5WZXIiOiI0MC41Ny4xIiwidXBkYXRlZEluVmVyIjoiNDAuNTcuMSIsInRhcmdldEJyYW5jaCI6ImZvcmdlam8iLCJsYWJlbHMiOlsiZGVwZW5kZW5jeS11cGdyYWRlIiwidGVzdC9ub3QtbmVlZGVkIl19-->

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8219
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2025-06-18 08:07:21 +02:00
Renovate Bot
fc69250f0f Update module github.com/minio/minio-go/v7 to v7.0.94 (forgejo) (#8217)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [github.com/minio/minio-go/v7](https://github.com/minio/minio-go) | require | patch | `v7.0.93` -> `v7.0.94` |

---

### Release Notes

<details>
<summary>minio/minio-go (github.com/minio/minio-go/v7)</summary>

### [`v7.0.94`](https://github.com/minio/minio-go/compare/v7.0.93...v7.0.94)

[Compare Source](https://github.com/minio/minio-go/compare/v7.0.93...v7.0.94)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - Between 12:00 AM and 03:59 AM ( * 0-3 * * * ) (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:eyJjcmVhdGVkSW5WZXIiOiI0MC41Ny4xIiwidXBkYXRlZEluVmVyIjoiNDAuNTcuMSIsInRhcmdldEJyYW5jaCI6ImZvcmdlam8iLCJsYWJlbHMiOlsiZGVwZW5kZW5jeS11cGdyYWRlIiwidGVzdC9ub3QtbmVlZGVkIl19-->

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8217
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2025-06-18 06:34:30 +02:00
Renovate Bot
9caa3c6c5f Update dependency eslint-plugin-wc to v3 (forgejo) (#8215)
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2025-06-18 00:32:14 +02:00
Michael Jerger
15bb6b7f92 [gitea] week 2025-22 cherry pick (gitea/main -> forgejo) (#8198)
## Checklist

- [x] go to the last cherry-pick PR (forgejo/forgejo#8040) to figure out how far it went: [gitea@d5bbaee64e](d5bbaee64e)
- [x] cherry-pick and open PR (forgejo/forgejo#8198)
- [ ] have the PR pass the CI
- end-to-end (specially important if there are actions related changes)
  - [ ] add `run-end-to-end` label
  - [ ] check the result
- [ ] write release notes
- [ ] assign reviewers
- [ ] 48h later, last call
- merge 1 hour after the last call

## Legend

-  - No decision about the commit has been made.
- 🍒 - The commit has been cherry picked.
-  - The commit has been skipped.
- 💡 - The commit has been skipped, but should be ported to Forgejo.
- ✍️ - The commit has been skipped, and a port to Forgejo already exists.

## Commits

- 🍒 [`gitea`](17cfae82a5) -> [`forgejo`](6397da88d3) Hide href attribute of a tag if there is no target_url ([gitea#34556](https://github.com/go-gitea/gitea/pull/34556))
- 🍒 [`gitea`](b408bf2f0b) -> [`forgejo`](46bc899d57) Fix: skip paths check on tag push events in workflows ([gitea#34602](https://github.com/go-gitea/gitea/pull/34602))
- 🍒 [`gitea`](9165ea8713) -> [`forgejo`](04332f31bf) Only activity tab needs heatmap data loading ([gitea#34652](https://github.com/go-gitea/gitea/pull/34652))
- 🍒 [`gitea`](3f7dbbdaf1) -> [`forgejo`](2a9019fd04) Small fix in Pull Requests page ([gitea#34612](https://github.com/go-gitea/gitea/pull/34612))
- 🍒 [`gitea`](497b83b75d) -> [`forgejo`](9a83cc7bad) Fix migration pull request title too long ([gitea#34577](https://github.com/go-gitea/gitea/pull/34577))

## TODO

- 💡 [`gitea`](6b8b580218) Refactor container and UI ([gitea#34736](https://github.com/go-gitea/gitea/pull/34736))
  Packages: Fix for container, needs careful merge.
------
- 💡 [`gitea`](bbee652e29) Prevent duplicate form submissions when creating forks ([gitea#34714](https://github.com/go-gitea/gitea/pull/34714))
  Fork: Fix, needs careful merge.
------
- 💡 [`gitea`](d21ce9fa07) Improve the performance when detecting the file editable ([gitea#34653](https://github.com/go-gitea/gitea/pull/34653))
  LFS: Performance improvement - needs careful merge.
------
- 💡 [`gitea`](8fed27bf6a) Fix various problems ([gitea#34708](https://github.com/go-gitea/gitea/pull/34708))
  Various: Fixes, tests missing.
------
- 💡 [`gitea`](c9505a26b9) Improve instance wide ssh commit signing ([gitea#34341](https://github.com/go-gitea/gitea/pull/34341))
  CodeSign: Nice feature - needs careful merge.
------
- 💡 [`gitea`](fbc3796f9e) Fix pull requests API convert panic when head repository is deleted. ([gitea#34685](https://github.com/go-gitea/gitea/pull/34685))
  Pull: Fix, needs careful merge.
------
- 💡 [`gitea`](1610a63bfd) Fix commit message rendering and some UI problems ([gitea#34680](https://github.com/go-gitea/gitea/pull/34680))
  Various Fixes - needs carefull merge.
------
- 💡 [`gitea`](0082cb51fa) Fix last admin check when syncing users ([gitea#34649](https://github.com/go-gitea/gitea/pull/34649))
  oidc: fix "first user is always admin". Needs careful merge.
------
- 💡 [`gitea`](c6b2cbd75d) Fix footnote jump behavior on the issue page. ([gitea#34621](https://github.com/go-gitea/gitea/pull/34621))
  Issues: Fix Markdown rendering. Needs carefull merge
------
- 💡 [`gitea`](7a59f5a825) Ignore "Close" error when uploading container blob ([gitea#34620](https://github.com/go-gitea/gitea/pull/34620))
  No issue, no test.
------
- 💡 [`gitea`](6d0b24064a) Keeping consistent between UI and API about combined commit status state and fix some bugs ([gitea#34562](https://github.com/go-gitea/gitea/pull/34562))
  Next PR in Commit-Status story.
------
- 💡 [`gitea`](f6041441ee) Refactor FindOrgOptions to use enum instead of bool, fix membership visibility ([gitea#34629](https://github.com/go-gitea/gitea/pull/34629))
  Just for a common sense here: How should I consider refactorings?
------
- 💡 [`gitea`](cc942e2a86) Fix GetUsersByEmails ([gitea#34643](https://github.com/go-gitea/gitea/pull/34643))
  User: Seems to fix email validation - but seems not to be finished.
------
- 💡 [`gitea`](7fa5a88831) Add `--color-logo` for text that should match logo color ([gitea#34639](https://github.com/go-gitea/gitea/pull/34639))
  UI: Nice idea - can we adapt this?
------
- 💡 [`gitea`](47d69b7749) Validate hex colors when creating/editing labels ([gitea#34623](https://github.com/go-gitea/gitea/pull/34623))
  Label: Color validation but needs careful merge.
------
- 💡 [`gitea`](108db0b04f) Fix possible pull request broken when leave the page immediately after clicking the update button ([gitea#34509](https://github.com/go-gitea/gitea/pull/34509))
  Nice fix for a bug hard to trace down.
  Needs careful merge & think about whether a test is possible.
------
- 💡 [`gitea`](79cc369892) Fix issue label delete incorrect labels webhook payload ([gitea#34575](https://github.com/go-gitea/gitea/pull/34575))
  Small fix but would expect a test, showing what was fixed.
------
- 💡 [`gitea`](fe57ee3074) fixed incorrect page navigation with up and down arrow on last item of dashboard repos ([gitea#34570](https://github.com/go-gitea/gitea/pull/34570))
  Small & simple - but tests are missing.
------
- 💡 [`gitea`](4e471487fb) Remove unnecessary duplicate code ([gitea#34552](https://github.com/go-gitea/gitea/pull/34552))
  Fix arround "Split GetLatestCommitStatus".
------
- 💡 [`gitea`](c5e78fc7ad) Do not mutate incoming options to SearchRepositoryByName ([gitea#34553](https://github.com/go-gitea/gitea/pull/34553))
  Large refactoring to simplify options handling. But needs careful merge.
------
- 💡 [`gitea`](f48c0135a6) Fix/improve avatar sync from LDAP ([gitea#34573](https://github.com/go-gitea/gitea/pull/34573))
  Nice fix but needs test.
------
- 💡 [`gitea`](e8d8984f7c) Fix some trivial problems ([gitea#34579](https://github.com/go-gitea/gitea/pull/34579))
  Various fixes, tests missing.
------

## Skipped

-  [`gitea`](637070e07b) Fix container range bug ([gitea#34725](https://github.com/go-gitea/gitea/pull/34725))
------
-  [`gitea`](0d3e9956cd) [skip ci] Updated translations via Crowdin
------
-  [`gitea`](28debdbe00) [skip ci] Updated translations via Crowdin
------
-  [`gitea`](dcc9206a59) Raise minimum Node.js version to 20, test on 24 ([gitea#34713](https://github.com/go-gitea/gitea/pull/34713))
------
-  [`gitea`](bc28654b49) [skip ci] Updated translations via Crowdin
------
-  [`gitea`](65986f423f) Refactor embedded assets and drop unnecessary dependencies ([gitea#34692](https://github.com/go-gitea/gitea/pull/34692))
------
-  [`gitea`](18bafcc378) Bump minimum go version to 1.24.4 ([gitea#34699](https://github.com/go-gitea/gitea/pull/34699))
------
-  [`gitea`](8d135ef5cf) Update JS deps ([gitea#34701](https://github.com/go-gitea/gitea/pull/34701))
------
-  [`gitea`](d5893ee260) Fix markdown wrap ([gitea#34697](https://github.com/go-gitea/gitea/pull/34697))

  - gitea UI specific specific
------
-  [`gitea`](06ccb3a1d4) [skip ci] Updated translations via Crowdin
------
-  [`gitea`](94db956e31) frontport changelog ([gitea#34689](https://github.com/go-gitea/gitea/pull/34689))
------
-  [`gitea`](d5afdccde8) [skip ci] Updated translations via Crowdin
------
-  [`gitea`](e9f5105e95) Migrate to urfave v3 ([gitea#34510](https://github.com/go-gitea/gitea/pull/34510))
  already in Forgejo - see https://codeberg.org/forgejo/forgejo/pulls/8035
------
-  [`gitea`](2c341b6803) [skip ci] Updated translations via Crowdin
------
-  [`gitea`](92e7e98c56) Update x/crypto package and make builtin SSH use default parameters ([gitea#34667](https://github.com/go-gitea/gitea/pull/34667))
------
-  [`gitea`](7b39c82587) Fix "oras" OCI client compatibility ([gitea#34666](https://github.com/go-gitea/gitea/pull/34666))
  Already in forgejo - see https://codeberg.org/forgejo/forgejo/issues/8070
------
-  [`gitea`](1fe652cd26) [skip ci] Updated translations via Crowdin
------
-  [`gitea`](a9a705f4db) Fix missed merge commit sha and time when migrating from codecommit ([gitea#34645](https://github.com/go-gitea/gitea/pull/34645))
  Migration: Seems to be an important fix, but no tests.

  As I know @earl-warren worked hard on migration, is this still relevant to us?
------
-  [`gitea`](1e0758a9f1) [skip ci] Updated translations via Crowdin
------
-  [`gitea`](f6f6aedd4f) Update JS deps, regenerate SVGs ([gitea#34640](https://github.com/go-gitea/gitea/pull/34640))
------
-  [`gitea`](aa2b3b2b1f) Misc CSS fixes ([gitea#34638](https://github.com/go-gitea/gitea/pull/34638))

  - gitea UI specific specific
------
-  [`gitea`](b38f2d31fd) add codecommit to supported services in api docs ([gitea#34626](https://github.com/go-gitea/gitea/pull/34626))
------
-  [`gitea`](74a0178c6a) add openssh-keygen to rootless image ([gitea#34625](https://github.com/go-gitea/gitea/pull/34625))
  already in Forgejo - see https://codeberg.org/forgejo/forgejo/issues/6896
------
-  [`gitea`](5b22af4373) bump to alpine 3.22 ([gitea#34613](https://github.com/go-gitea/gitea/pull/34613))
------
-  [`gitea`](9e0e107d23) Fix notification count positioning for variable-width elements ([gitea#34597](https://github.com/go-gitea/gitea/pull/34597))

  - gitea UI specific specific
------
-  [`gitea`](e5781cec75) Fix margin issue in markup paragraph rendering ([gitea#34599](https://github.com/go-gitea/gitea/pull/34599))

  - gitea UI specific specific
------
-  [`gitea`](375dab1111) Make pull request and issue history more compact ([gitea#34588](https://github.com/go-gitea/gitea/pull/34588))

  - gitea UI specific specific
------
-  [`gitea`](2a1585b32e) Refactor some tests ([gitea#34580](https://github.com/go-gitea/gitea/pull/34580))
------

<details>
<summary><h2>Stats</h2></summary>

<br>

Between [`gitea@d5bbaee64e`](d5bbaee64e) and [`gitea@6b8b580218`](6b8b580218), **55** commits have been reviewed. We picked **5**, skipped **28** (of which **3** were already in Forgejo!), and decided to port **22**.

</details>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: NorthRealm <155140859+NorthRealm@users.noreply.github.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-authored-by: endo0911engineer <161911062+endo0911engineer@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8198
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Michael Jerger <michael.jerger@meissa-gmbh.de>
Co-committed-by: Michael Jerger <michael.jerger@meissa-gmbh.de>
2025-06-17 18:28:07 +02:00
Earl Warren
adc273e3a8 fix: do not ignore automerge while a PR is checking for conflicts (#8189)
Automerge can be ignored when the following race happens:

* Conflict check is happening on a repository and
  `pr.Status = issues_model.PullRequestStatusChecking` for all open pull
  requests (this happens every time a pull request is merged).
* While the conflict check is ongoing, an event (Forgejo Actions being
  successful for instance) happens and and `StartPRCheckAndAutoMerge*` is called.
* Because `pr.CanAutoMerge()` is false, the pull request is not
  selected and not added to the automerge queue.
* When the conflict check completes and `pr.CanAutoMerge()` becomes
  true, there no longer is a task in the auto merge queue and the
  auto merge does not happen.

This is fixed by adding a task to the auto merge queue when the conflict check for a pull request completes. This is done when the mutx protecting the conflict check task is released to prevent a deadlock when a synchronous queues are used in the following situation:

* the conflict check task finds the pull request is mergeable
* it schedules the auto merge tasks that finds it must be merged
* merging concludes with scheduling a conflict check task

Avoid an extra loop where a conflict check task queues an auto merge task that will schedule a conflict check task if the pull request can be merged. The auto merge row is removed from the database before merging. It would otherwise be removed after the merge commit is received via the git hook which happens asynchronously and can lead to a race.

StartPRCheckAndAutoMerge is modified to re-use HeadCommitID when available, such as when called after a pull request conflict check.

---

A note on tests: they cover the new behavior, i.e. automerge being triggered by a successful conflict check. This is also on the critical paths for every test that involve creating, merging or updating a pull request.

- `tests/integration/git_test.go`
- `tests/integration/actions_commit_status_test.go`
- `tests/integration/api_helper_for_declarative_test.go`
- `tests/integration/patch_status_test.go`
- `tests/integration/pull_merge_test.go`

The [missing fixture file](https://codeberg.org/forgejo/forgejo/pulls/8189/files#diff-b86fdd79108b3ba3cb2e56ffcfd1be2a7b32f46c) for the auto merge table can be verified to be necessary simply by removing it an observing that the integration tests fail.

The [scheduling of the auto merge task](https://codeberg.org/forgejo/forgejo/pulls/8189/files#diff-9489262e93967f6bb2db41837f37c06f4e70d978) in `testPR` can be verified to be required by moving it in the `testPRProtected` function and observing that the tests hang forever because of the deadlock.

## 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

- 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 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

- [ ] I do not want this change to show in the release notes.
- [x] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

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

## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- Bug fixes
  - [PR](https://codeberg.org/forgejo/forgejo/pulls/8189): <!--number 8189 --><!--line 0 --><!--description ZG8gbm90IGlnbm9yZSBhdXRvbWVyZ2Ugd2hpbGUgYSBQUiBpcyBjaGVja2luZyBmb3IgY29uZmxpY3Rz-->do not ignore automerge while a PR is checking for conflicts<!--description-->
<!--end release-notes-assistant-->

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8189
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Reviewed-by: Lucas <sclu1034@noreply.codeberg.org>
Co-authored-by: Earl Warren <contact@earl-warren.org>
Co-committed-by: Earl Warren <contact@earl-warren.org>
2025-06-17 10:58:07 +02:00
Earl Warren
16dbc0efd3 fix: git_model.CommitStatusesHideActionsURL is obsolete (#8209)
Refs: https://codeberg.org/forgejo/forgejo/pulls/7155
Refs: https://codeberg.org/forgejo/forgejo/pulls/8177
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8209
Reviewed-by: Beowulf <beowulf@beocode.eu>
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Reviewed-by: Lucas <sclu1034@noreply.codeberg.org>
Co-authored-by: Earl Warren <contact@earl-warren.org>
Co-committed-by: Earl Warren <contact@earl-warren.org>
2025-06-17 10:15:48 +02:00
Lucas Schwiderski
3a986d282f Implement single-commit PR review flow (#7155)
This implements the UI controls and information displays necessary to allow reviewing pull requests by stepping through commits individually.

Notable changes:

- Within the PR page, commit links now stay in the PR context by navigating to `{owner}/{repo}/pulls/{id}/commits/{sha}`
- When showing a single commit in the "Files changed" tab, the commit header containing commit message and metadata is displayed
  - I dropped the existing buttons, since they make less sense to me in the PR context
  - The SHA links to the separate, dedicated commit view
- "Previous"/"Next" buttons have been added to that header to allow stepping through commits
- Reviews can be submitted in "single commit" view

Talking points:

- The "Showing only changes from" banner made sense when that view was limited (e.g. review submit was disabled). Now that it's on par with the "all commits" view, and visually distinct due to the commit header, this banner could potentially be dropped.

Closes: #5670 #5126 #5671 #2281 #8084

![image](/attachments/cff441dc-a080-42f8-86ae-9b80490761bf)

## 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

- 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 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

- [ ] I do not want this change to show in the release notes.
- [x] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7155
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: Lucas Schwiderski <lucas@lschwiderski.de>
Co-committed-by: Lucas Schwiderski <lucas@lschwiderski.de>
2025-06-17 09:31:50 +02:00
Gusted
3a8cea52cd chore: remove gopls in Makefile (#8205)
- `lint-go-gopls` runs `gopls check` over Forgejo's codebase. It report errors found by the diagnosis tool of gopls, most of it are errors that can be catched by existing linters. It is not used in the CI, remove it.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8205
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-committed-by: Gusted <postmaster@gusted.xyz>
2025-06-17 08:28:26 +02:00
Gusted
b52264c953 fix: do not check for object_format_name field (#8202)
- Only check for the `object_format_name` field if SHA256 is supported.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8202
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-committed-by: Gusted <postmaster@gusted.xyz>
2025-06-17 07:47:00 +02:00
Renovate Bot
0c55cdf6b6 Update dependency chart.js to v4.5.0 (forgejo) (#8190)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8190
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
Co-committed-by: Renovate Bot <forgejo-renovate-action@forgejo.org>
2025-06-17 03:48:53 +02:00
Michael Jerger
9ea796b9ab [gitea] week 2025-21 cherry pick (gitea/main -> forgejo) (#8040)
## Checklist

- [x] go to the last cherry-pick PR (forgejo/forgejo#7965) to figure out how far it went: [gitea@9d4ebc1f2c](9d4ebc1f2c)
- [x] cherry-pick and open PR (forgejo/forgejo#8040)
- [ ] have the PR pass the CI
- end-to-end (specially important if there are actions related changes)
  - [ ] add `run-end-to-end` label
  - [ ] check the result
- [ ] write release notes
- [ ] assign reviewers
- [ ] 48h later, last call
- merge 1 hour after the last call

## Legend

-  - No decision about the commit has been made.
- 🍒 - The commit has been cherry picked.
-  - The commit has been skipped.
- 💡 - The commit has been skipped, but should be ported to Forgejo.
- ✍️ - The commit has been skipped, and a port to Forgejo already exists.

## Commits

- 🍒 [`gitea`](50d9565088) -> [`forgejo`](c3e6eab732) Add sort option recentclose for issues and pulls ([gitea#34525](https://github.com/go-gitea/gitea/pull/34525))

## TODO

- 💡 [`gitea`](d5bbaee64e) Retain issue sort type when a keyword search is introduced ([gitea#34559](https://github.com/go-gitea/gitea/pull/34559))
  UI: Small bat might be nice. Test needed? Do we've frontend tests covering the search?
------
- 💡 [`gitea`](82ea2387e4) Always use an empty line to separate the commit message and trailer ([gitea#34512](https://github.com/go-gitea/gitea/pull/34512))
  Needs merge
------
- 💡 [`gitea`](74858dc5ae) Fix line-button issue after file selection in file tree ([gitea#34574](https://github.com/go-gitea/gitea/pull/34574))
  Frontend: Makes it sense to pick/port ui logic in *.ts files?
------
- 💡 [`gitea`](7149c9c55d) Fix doctor deleting orphaned issues attachments ([gitea#34142](https://github.com/go-gitea/gitea/pull/34142))
  Doctor: seems useful.
------
- 💡 [`gitea`](0cec4b84e2) Fix actions skipped commit status indicator ([gitea#34507](https://github.com/go-gitea/gitea/pull/34507))
  Actions: Might benefit from additional tests.
------
- 💡 [`gitea`](4cb0c641ce) Add "View workflow file" to Actions list page ([gitea#34538](https://github.com/go-gitea/gitea/pull/34538))
  Actions: Needs tests
------
- 💡 [`gitea`](b0936f4f41) Do not mutate incoming options to RenderUserSearch and SearchUsers ([gitea#34544](https://github.com/go-gitea/gitea/pull/34544))
  Nice refactoring but needs manual merge.
------
- 💡 [`gitea`](498088c053) Add webhook assigning test and fix possible bug ([gitea#34420](https://github.com/go-gitea/gitea/pull/34420))
  Integrationtest has conflicts needs merge.
------
- 💡 [`gitea`](24a51059d7) Fix possible nil description of pull request when migrating from CodeCommit ([gitea#34541](https://github.com/go-gitea/gitea/pull/34541))
  Is this relevant to forgejo? Did not find the place to apply this small change.
------
- 💡 [`gitea`](688da55f54) Split GetLatestCommitStatus as two functions ([gitea#34535](https://github.com/go-gitea/gitea/pull/34535))
  Merge required.
------
- 💡 [`gitea`](ab9691291d) Don't display error log when .git-blame-ignore-revs doesn't exist ([gitea#34457](https://github.com/go-gitea/gitea/pull/34457))
  Unsure wheter this affects forgejo. Tests missing.
------
- 💡 [`gitea`](11ee7ff3bf) fix: return 201 Created for CreateVariable API responses ([gitea#34517](https://github.com/go-gitea/gitea/pull/34517))
  Actions: This is marked as breaking the api. Pls think about whether this breaking change iss needed & how this impact api-version-increase.
  The corresponding clinet change can be found here: https://gitea.com/gitea/go-sdk/pulls/713/files
------
- 💡 [`gitea`](9b295e984a) Actions list ([gitea#34530](https://github.com/go-gitea/gitea/pull/34530))
  Actions: Regression from https://github.com/go-gitea/gitea/pull/34337 Part of https://codeberg.org/forgejo/forgejo/pulls/7909
------

## Skipped

-  [`gitea`](bb6377d080) [skip ci] Updated translations via Crowdin
------
-  [`gitea`](07d802a815) [skip ci] Updated translations via Crowdin
------
-  [`gitea`](c6e2093f42) Clean up "file-view" related styles ([gitea#34558](https://github.com/go-gitea/gitea/pull/34558))

  - gitea ui specific specific
------
-  [`gitea`](9f10885b21) Refactor commit reader ([gitea#34542](https://github.com/go-gitea/gitea/pull/34542))

  - gitea refactor specific
------

<details>
<summary><h2>Stats</h2></summary>

<br>

Between [`gitea@9d4ebc1f2c`](9d4ebc1f2c) and [`gitea@d5bbaee64e`](d5bbaee64e), **18** commits have been reviewed. We picked **1**, skipped **4**, and decided to port **13**.

</details>

Co-authored-by: Markus Amshove <scm@amshove.org>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8040
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Michael Jerger <michael.jerger@meissa-gmbh.de>
Co-committed-by: Michael Jerger <michael.jerger@meissa-gmbh.de>
2025-06-16 20:27:47 +02:00
159 changed files with 3832 additions and 820 deletions

View file

@ -13,6 +13,13 @@ forgejo.org/models
IsErrSHANotFound
IsErrMergeDivergingFastForwardOnly
forgejo.org/models/activities
GetActivityByID
NewFederatedUserActivity
CreateUserActivity
GetFollowingFeeds
FederatedUserActivity.loadActor
forgejo.org/models/auth
WebAuthnCredentials
@ -54,9 +61,17 @@ forgejo.org/models/user
IsErrExternalLoginUserAlreadyExist
IsErrExternalLoginUserNotExist
NewFederatedUser
NewFederatedUserFollower
IsErrUserSettingIsNotExist
GetUserAllSettings
DeleteUserSetting
GetFederatedUser
GetFederatedUserByUserID
UpdateFederatedUser
GetFollowersForUser
AddFollower
RemoveFollower
IsFollowingAp
forgejo.org/modules/activitypub
NewContext

View file

@ -1,4 +1,4 @@
FROM data.forgejo.org/oci/alpine:3.21
FROM data.forgejo.org/oci/alpine:3.22
ARG RELEASE_VERSION=unkown
LABEL maintainer="contact@forgejo.org" \
org.opencontainers.image.version="${RELEASE_VERSION}"

View file

@ -28,7 +28,7 @@ jobs:
runs-on: docker
container:
image: data.forgejo.org/renovate/renovate:40.57.1
image: data.forgejo.org/renovate/renovate:41.1.4
steps:
- name: Load renovate repo cache

View file

@ -115,6 +115,11 @@ jobs:
run: |
su forgejo -c 'make deps-frontend frontend'
- uses: ./.forgejo/workflows-composite/build-backend
- name: Decide to run all tests
id: run-all
if: contains(github.event.pull_request.labels.*.name, 'run-all-playwright-tests') || contains(github.event.pull_request.title, 'playwright')
run: |
echo "all=1" >> "$GITHUB_OUTPUT"
- name: Get changed files
id: changed-files
uses: https://data.forgejo.org/tj-actions/changed-files@v46
@ -127,6 +132,7 @@ jobs:
USE_REPO_TEST_DIR: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
CHANGED_FILES: ${{steps.changed-files.outputs.all_changed_files}}
RUN_ALL: ${{steps.run-all.all}}
- name: Upload test artifacts on failure
if: failure()
uses: https://data.forgejo.org/forgejo/upload-artifact@v4

View file

@ -1,6 +1,6 @@
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.21 AS build-env
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.22 AS build-env
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct}
@ -51,7 +51,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/go/src/forgejo.org/environment-to-ini
RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete
FROM data.forgejo.org/oci/alpine:3.21
FROM data.forgejo.org/oci/alpine:3.22
ARG RELEASE_VERSION
LABEL maintainer="contact@forgejo.org" \
org.opencontainers.image.authors="Forgejo" \

View file

@ -1,6 +1,6 @@
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.21 AS build-env
FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.22 AS build-env
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct}
@ -49,7 +49,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
/go/src/forgejo.org/environment-to-ini
RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete
FROM data.forgejo.org/oci/alpine:3.21
FROM data.forgejo.org/oci/alpine:3.22
ARG RELEASE_VERSION
LABEL maintainer="contact@forgejo.org" \
org.opencontainers.image.authors="Forgejo" \

View file

@ -47,8 +47,7 @@ GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasour
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.34.0 # renovate: datasource=go
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.2 # renovate: datasource=go
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.18.1 # renovate: datasource=go
RENOVATE_NPM_PACKAGE ?= renovate@40.57.1 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
RENOVATE_NPM_PACKAGE ?= renovate@41.1.4 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate
# https://github.com/disposable-email-domains/disposable-email-domains/commits/main/
DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ...
@ -222,7 +221,6 @@ help:
@echo " - lint-go lint go files"
@echo " - lint-go-fix lint go files and fix issues"
@echo " - lint-go-vet lint go files with vet"
@echo " - lint-go-gopls lint go files with gopls"
@echo " - lint-js lint js files"
@echo " - lint-js-fix lint js files and fix issues"
@echo " - lint-css lint css files"
@ -487,11 +485,6 @@ lint-go-vet:
@echo "Running go vet..."
@$(GO) vet ./...
.PHONY: lint-go-gopls
lint-go-gopls:
@echo "Running gopls check..."
@GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES_NO_BINDATA)
.PHONY: lint-editorconfig
lint-editorconfig:
$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .forgejo/workflows
@ -932,7 +925,6 @@ deps-tools:
$(GO) install $(GO_LICENSES_PACKAGE)
$(GO) install $(GOVULNCHECK_PACKAGE)
$(GO) install $(GOMOCK_PACKAGE)
$(GO) install $(GOPLS_PACKAGE)
node_modules: package-lock.json
npm install --no-save

View file

@ -1,5 +1,5 @@
import eslintCommunityEslintPluginEslintComments from '@eslint-community/eslint-plugin-eslint-comments';
import stylisticEslintPluginJs from '@stylistic/eslint-plugin-js';
import stylisticEslintPlugin from '@stylistic/eslint-plugin';
import vitest from '@vitest/eslint-plugin';
import arrayFunc from 'eslint-plugin-array-func';
import eslintPluginImportX from 'eslint-plugin-import-x';
@ -26,7 +26,7 @@ export default tseslint.config(
{
plugins: {
'@eslint-community/eslint-comments': eslintCommunityEslintPluginEslintComments,
'@stylistic/js': stylisticEslintPluginJs,
'@stylistic': stylisticEslintPlugin,
'@vitest': vitest,
'array-func': arrayFunc,
'no-jquery': noJquery,
@ -69,62 +69,62 @@ export default tseslint.config(
'@eslint-community/eslint-comments/no-unused-enable': [2],
'@eslint-community/eslint-comments/no-use': [0],
'@eslint-community/eslint-comments/require-description': [0],
'@stylistic/js/array-bracket-newline': [0],
'@stylistic/js/array-bracket-spacing': [2, 'never'],
'@stylistic/js/array-element-newline': [0],
'@stylistic/js/arrow-parens': [2, 'always'],
'@stylistic/array-bracket-newline': [0],
'@stylistic/array-bracket-spacing': [2, 'never'],
'@stylistic/array-element-newline': [0],
'@stylistic/arrow-parens': [2, 'always'],
'@stylistic/js/arrow-spacing': [2, {
'@stylistic/arrow-spacing': [2, {
before: true,
after: true,
}],
'@stylistic/js/block-spacing': [0],
'@stylistic/block-spacing': [0],
'@stylistic/js/brace-style': [2, '1tbs', {
'@stylistic/brace-style': [2, '1tbs', {
allowSingleLine: true,
}],
'@stylistic/js/comma-dangle': [2, 'always-multiline'],
'@stylistic/comma-dangle': [2, 'always-multiline'],
'@stylistic/js/comma-spacing': [2, {
'@stylistic/comma-spacing': [2, {
before: false,
after: true,
}],
'@stylistic/js/comma-style': [2, 'last'],
'@stylistic/js/computed-property-spacing': [2, 'never'],
'@stylistic/js/dot-location': [2, 'property'],
'@stylistic/js/eol-last': [2],
'@stylistic/js/function-call-spacing': [2, 'never'],
'@stylistic/js/function-call-argument-newline': [0],
'@stylistic/js/function-paren-newline': [0],
'@stylistic/js/generator-star-spacing': [0],
'@stylistic/js/implicit-arrow-linebreak': [0],
'@stylistic/comma-style': [2, 'last'],
'@stylistic/computed-property-spacing': [2, 'never'],
'@stylistic/dot-location': [2, 'property'],
'@stylistic/eol-last': [2],
'@stylistic/function-call-spacing': [2, 'never'],
'@stylistic/function-call-argument-newline': [0],
'@stylistic/function-paren-newline': [0],
'@stylistic/generator-star-spacing': [0],
'@stylistic/implicit-arrow-linebreak': [0],
'@stylistic/js/indent': [2, 2, {
'@stylistic/indent': [2, 2, {
ignoreComments: true,
SwitchCase: 1,
}],
'@stylistic/js/key-spacing': [2],
'@stylistic/js/keyword-spacing': [2],
'@stylistic/js/linebreak-style': [2, 'unix'],
'@stylistic/js/lines-around-comment': [0],
'@stylistic/js/lines-between-class-members': [0],
'@stylistic/js/max-len': [0],
'@stylistic/js/max-statements-per-line': [0],
'@stylistic/js/multiline-ternary': [0],
'@stylistic/js/new-parens': [2],
'@stylistic/js/newline-per-chained-call': [0],
'@stylistic/js/no-confusing-arrow': [0],
'@stylistic/js/no-extra-parens': [0],
'@stylistic/js/no-extra-semi': [2],
'@stylistic/js/no-floating-decimal': [0],
'@stylistic/js/no-mixed-operators': [0],
'@stylistic/js/no-mixed-spaces-and-tabs': [2],
'@stylistic/key-spacing': [2],
'@stylistic/keyword-spacing': [2],
'@stylistic/linebreak-style': [2, 'unix'],
'@stylistic/lines-around-comment': [0],
'@stylistic/lines-between-class-members': [0],
'@stylistic/max-len': [0],
'@stylistic/max-statements-per-line': [0],
'@stylistic/multiline-ternary': [0],
'@stylistic/new-parens': [2],
'@stylistic/newline-per-chained-call': [0],
'@stylistic/no-confusing-arrow': [0],
'@stylistic/no-extra-parens': [0],
'@stylistic/no-extra-semi': [2],
'@stylistic/no-floating-decimal': [0],
'@stylistic/no-mixed-operators': [0],
'@stylistic/no-mixed-spaces-and-tabs': [2],
'@stylistic/js/no-multi-spaces': [2, {
'@stylistic/no-multi-spaces': [2, {
ignoreEOLComments: true,
exceptions: {
@ -132,60 +132,60 @@ export default tseslint.config(
},
}],
'@stylistic/js/no-multiple-empty-lines': [2, {
'@stylistic/no-multiple-empty-lines': [2, {
max: 1,
maxEOF: 0,
maxBOF: 0,
}],
'@stylistic/js/no-tabs': [2],
'@stylistic/js/no-trailing-spaces': [2],
'@stylistic/js/no-whitespace-before-property': [2],
'@stylistic/js/nonblock-statement-body-position': [2],
'@stylistic/js/object-curly-newline': [0],
'@stylistic/js/object-curly-spacing': [2, 'never'],
'@stylistic/js/object-property-newline': [0],
'@stylistic/js/one-var-declaration-per-line': [0],
'@stylistic/js/operator-linebreak': [2, 'after'],
'@stylistic/js/padded-blocks': [2, 'never'],
'@stylistic/js/padding-line-between-statements': [0],
'@stylistic/js/quote-props': [0],
'@stylistic/no-tabs': [2],
'@stylistic/no-trailing-spaces': [2],
'@stylistic/no-whitespace-before-property': [2],
'@stylistic/nonblock-statement-body-position': [2],
'@stylistic/object-curly-newline': [0],
'@stylistic/object-curly-spacing': [2, 'never'],
'@stylistic/object-property-newline': [0],
'@stylistic/one-var-declaration-per-line': [0],
'@stylistic/operator-linebreak': [2, 'after'],
'@stylistic/padded-blocks': [2, 'never'],
'@stylistic/padding-line-between-statements': [0],
'@stylistic/quote-props': [0],
'@stylistic/js/quotes': [2, 'single', {
'@stylistic/quotes': [2, 'single', {
avoidEscape: true,
allowTemplateLiterals: true,
}],
'@stylistic/js/rest-spread-spacing': [2, 'never'],
'@stylistic/rest-spread-spacing': [2, 'never'],
'@stylistic/js/semi': [2, 'always', {
'@stylistic/semi': [2, 'always', {
omitLastInOneLineBlock: true,
}],
'@stylistic/js/semi-spacing': [2, {
'@stylistic/semi-spacing': [2, {
before: false,
after: true,
}],
'@stylistic/js/semi-style': [2, 'last'],
'@stylistic/js/space-before-blocks': [2, 'always'],
'@stylistic/semi-style': [2, 'last'],
'@stylistic/space-before-blocks': [2, 'always'],
'@stylistic/js/space-before-function-paren': [2, {
'@stylistic/space-before-function-paren': [2, {
anonymous: 'ignore',
named: 'never',
asyncArrow: 'always',
}],
'@stylistic/js/space-in-parens': [2, 'never'],
'@stylistic/js/space-infix-ops': [2],
'@stylistic/js/space-unary-ops': [2],
'@stylistic/js/spaced-comment': [2, 'always'],
'@stylistic/js/switch-colon-spacing': [2],
'@stylistic/js/template-curly-spacing': [2, 'never'],
'@stylistic/js/template-tag-spacing': [2, 'never'],
'@stylistic/js/wrap-iife': [2, 'inside'],
'@stylistic/js/wrap-regex': [0],
'@stylistic/js/yield-star-spacing': [2, 'after'],
'@stylistic/space-in-parens': [2, 'never'],
'@stylistic/space-infix-ops': [2],
'@stylistic/space-unary-ops': [2],
'@stylistic/spaced-comment': [2, 'always'],
'@stylistic/switch-colon-spacing': [2],
'@stylistic/template-curly-spacing': [2, 'never'],
'@stylistic/template-tag-spacing': [2, 'never'],
'@stylistic/wrap-iife': [2, 'inside'],
'@stylistic/wrap-regex': [0],
'@stylistic/yield-star-spacing': [2, 'after'],
'accessor-pairs': [2],
'array-callback-return': [2, {

6
go.mod
View file

@ -41,7 +41,7 @@ require (
github.com/gliderlabs/ssh v0.3.8
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.2.1
github.com/go-chi/chi/v5 v5.2.2
github.com/go-chi/cors v1.2.1
github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.9.2
@ -76,7 +76,7 @@ require (
github.com/meilisearch/meilisearch-go v0.31.0
github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.27
github.com/minio/minio-go/v7 v7.0.93
github.com/minio/minio-go/v7 v7.0.94
github.com/msteinert/pam/v2 v2.1.0
github.com/nektos/act v0.2.52
github.com/niklasfasching/go-org v1.8.0
@ -242,7 +242,7 @@ require (
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.26.0
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.28.0
replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1

12
go.sum
View file

@ -4,8 +4,8 @@ code.forgejo.org/f3/gof3/v3 v3.11.0 h1:f/xToKwqTgxG6PYxvewywjDQyCcyHEEJ6sZqUitFs
code.forgejo.org/f3/gof3/v3 v3.11.0/go.mod h1:4FaRUNSQGBiD1M0DuB0yNv+Z2wMtlOeckgygHSSq4KQ=
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU=
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM=
code.forgejo.org/forgejo/act v1.26.0 h1:6mTmoaw7d/WpYiw/Pw6AaypxFdgJog5OFi/PMEgEbxs=
code.forgejo.org/forgejo/act v1.26.0/go.mod h1:HFDFrXPrqfM9aH2RCnMiBdo/3ThxDmZjp58InPjGOfo=
code.forgejo.org/forgejo/act v1.28.0 h1:96njNC7C1YNyjWq5OWvLZMF/nw0PMthzIA8Nwbnn7jo=
code.forgejo.org/forgejo/act v1.28.0/go.mod h1:dFuiwAmD5vyrzecysHB2kL/GM3wRpoVPl+WdbCTC8Bs=
code.forgejo.org/forgejo/archiver/v3 v3.5.1 h1:UmmbA7D5550uf71SQjarmrn6yKwOGxtEjb3jaYYtmSE=
code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
code.forgejo.org/forgejo/go-rpmutils v1.0.0 h1:RZGGeKt70p/WaIEL97pyT6uiiEIoN8/aLmS5Z6WmX0M=
@ -213,8 +213,8 @@ github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5La
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
@ -411,8 +411,8 @@ github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY
github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.93 h1:lAB4QJp8Nq3vDMOU0eKgMuyBiEGMNlXQ5Glc8qAxqSU=
github.com/minio/minio-go/v7 v7.0.93/go.mod h1:71t2CqDt3ThzESgZUlU1rBN54mksGGlkLcFgguDnnAc=
github.com/minio/minio-go/v7 v7.0.94 h1:1ZoksIKPyaSt64AVOyaQvhDOgVC3MfZsWM6mZXRUGtM=
github.com/minio/minio-go/v7 v7.0.94/go.mod h1:71t2CqDt3ThzESgZUlU1rBN54mksGGlkLcFgguDnnAc=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=

View file

@ -55,6 +55,7 @@ type ActionRun struct {
PreviousDuration time.Duration
Created timeutil.TimeStamp `xorm:"created"`
Updated timeutil.TimeStamp `xorm:"updated"`
NotifyEmail bool
}
func init() {

View file

@ -442,6 +442,12 @@ func (a *Action) GetIssueContent(ctx context.Context) string {
return a.Issue.Content
}
func GetActivityByID(ctx context.Context, id int64) (*Action, error) {
var act Action
_, err := db.GetEngine(ctx).ID(id).Get(&act)
return &act, err
}
// GetFeedsOptions options for retrieving feeds
type GetFeedsOptions struct {
db.ListOptions
@ -595,13 +601,14 @@ func DeleteOldActions(ctx context.Context, olderThan time.Duration) (err error)
}
// NotifyWatchers creates batch of actions for every watcher.
func NotifyWatchers(ctx context.Context, actions ...*Action) error {
func NotifyWatchers(ctx context.Context, actions ...*Action) ([]Action, error) {
var watchers []*repo_model.Watch
var repo *repo_model.Repository
var err error
var permCode []bool
var permIssue []bool
var permPR []bool
var out []Action
e := db.GetEngine(ctx)
@ -612,14 +619,14 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
// Add feeds for user self and all watchers.
watchers, err = repo_model.GetWatchers(ctx, act.RepoID)
if err != nil {
return fmt.Errorf("get watchers: %w", err)
return nil, fmt.Errorf("get watchers: %w", err)
}
// Be aware that optimizing this correctly into the `GetWatchers` SQL
// query is for most cases less performant than doing this.
blockedDoerUserIDs, err := user_model.ListBlockedByUsersID(ctx, act.ActUserID)
if err != nil {
return fmt.Errorf("user_model.ListBlockedByUsersID: %w", err)
return nil, fmt.Errorf("user_model.ListBlockedByUsersID: %w", err)
}
if len(blockedDoerUserIDs) > 0 {
@ -634,8 +641,9 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
// Add feed for actioner.
act.UserID = act.ActUserID
if _, err = e.Insert(act); err != nil {
return fmt.Errorf("insert new actioner: %w", err)
return nil, fmt.Errorf("insert new actioner: %w", err)
}
out = append(out, *act)
if repoChanged {
act.loadRepo(ctx)
@ -643,7 +651,7 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
// check repo owner exist.
if err := act.Repo.LoadOwner(ctx); err != nil {
return fmt.Errorf("can't get repo owner: %w", err)
return nil, fmt.Errorf("can't get repo owner: %w", err)
}
} else if act.Repo == nil {
act.Repo = repo
@ -654,7 +662,7 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
act.ID = 0
act.UserID = act.Repo.Owner.ID
if err = db.Insert(ctx, act); err != nil {
return fmt.Errorf("insert new actioner: %w", err)
return nil, fmt.Errorf("insert new actioner: %w", err)
}
}
@ -707,26 +715,29 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
}
if err = db.Insert(ctx, act); err != nil {
return fmt.Errorf("insert new action: %w", err)
return nil, fmt.Errorf("insert new action: %w", err)
}
}
}
return nil
return out, nil
}
// NotifyWatchersActions creates batch of actions for every watcher.
func NotifyWatchersActions(ctx context.Context, acts []*Action) error {
func NotifyWatchersActions(ctx context.Context, acts []*Action) ([]Action, error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
return nil, err
}
defer committer.Close()
var out []Action
for _, act := range acts {
if err := NotifyWatchers(ctx, act); err != nil {
return err
as, err := NotifyWatchers(ctx, act)
if err != nil {
return nil, err
}
out = append(out, as...)
}
return committer.Commit()
return out, committer.Commit()
}
// DeleteIssueActions delete all actions related with issueID

View file

@ -197,7 +197,8 @@ func TestNotifyWatchers(t *testing.T) {
RepoID: 1,
OpType: activities_model.ActionStarRepo,
}
require.NoError(t, activities_model.NotifyWatchers(db.DefaultContext, action))
_, err := activities_model.NotifyWatchers(db.DefaultContext, action)
require.NoError(t, err)
// One watchers are inactive, thus action is only created for user 8, 1, 4, 11
unittest.AssertExistsAndLoadBean(t, &activities_model.Action{

View file

@ -0,0 +1,106 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package activities
import (
"context"
"fmt"
"forgejo.org/models/db"
user_model "forgejo.org/models/user"
"forgejo.org/modules/json"
"forgejo.org/modules/log"
"forgejo.org/modules/timeutil"
"forgejo.org/modules/validation"
ap "github.com/go-ap/activitypub"
)
type FederatedUserActivity struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
ActorID int64
ActorURI string
Actor *user_model.User `xorm:"-"` // transient
NoteContent string `xorm:"TEXT"`
NoteURL string `xorm:"VARCHAR(255)"`
OriginalNote string `xorm:"TEXT"`
Created timeutil.TimeStamp `xorm:"created"`
}
func init() {
db.RegisterModel(new(FederatedUserActivity))
}
func NewFederatedUserActivity(userID, actorID int64, actorURI, noteContent, noteURL string, originalNote ap.Activity) (FederatedUserActivity, error) {
jsonString, err := json.Marshal(originalNote)
if err != nil {
return FederatedUserActivity{}, err
}
result := FederatedUserActivity{
UserID: userID,
ActorID: actorID,
ActorURI: actorURI,
NoteContent: noteContent,
NoteURL: noteURL,
OriginalNote: string(jsonString),
}
if valid, err := validation.IsValid(result); !valid {
return FederatedUserActivity{}, err
}
return result, nil
}
func (federatedUserActivity FederatedUserActivity) Validate() []string {
var result []string
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.UserID, "UserID")...)
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.ActorID, "ActorID")...)
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.ActorURI, "ActorURI")...)
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.NoteContent, "NoteContent")...)
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.NoteURL, "NoteURL")...)
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.OriginalNote, "OriginalNote")...)
return result
}
func CreateUserActivity(ctx context.Context, federatedUserActivity *FederatedUserActivity) error {
if valid, err := validation.IsValid(federatedUserActivity); !valid {
return err
}
_, err := db.GetEngine(ctx).Insert(federatedUserActivity)
return err
}
type GetFollowingFeedsOptions struct {
db.ListOptions
}
func GetFollowingFeeds(ctx context.Context, actorID int64, opts GetFollowingFeedsOptions) ([]*FederatedUserActivity, int64, error) {
log.Debug("user_id = %s", actorID)
sess := db.GetEngine(ctx).Where("user_id = ?", actorID)
opts.SetDefaultValues()
sess = db.SetSessionPagination(sess, &opts)
actions := make([]*FederatedUserActivity, 0, opts.PageSize)
count, err := sess.FindAndCount(&actions)
if err != nil {
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
}
for _, act := range actions {
if err := act.loadActor(ctx); err != nil {
return nil, 0, err
}
}
return actions, count, err
}
func (federatedUserActivity *FederatedUserActivity) loadActor(ctx context.Context) error {
log.Debug("for activity %s", federatedUserActivity)
actorUser, _, err := user_model.GetFederatedUserByUserID(ctx, federatedUserActivity.ActorID)
if err != nil {
return err
}
federatedUserActivity.Actor = actorUser
return nil
}

View file

@ -0,0 +1,24 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package activities
import (
"testing"
"forgejo.org/modules/validation"
)
func Test_FederatedUserActivityValidation(t *testing.T) {
sut := FederatedUserActivity{}
sut.UserID = 13
sut.ActorID = 33
sut.ActorURI = "33"
sut.NoteContent = "Any content!"
sut.NoteURL = "https://example.org/note/17"
sut.OriginalNote = "federatedUserActivityNote-17"
if res, _ := validation.IsValid(sut); !res {
t.Errorf("sut expected to be valid: %v\n", sut.Validate())
}
}

View file

@ -113,3 +113,344 @@
review_id: 22
assignee_id: 5
created_unix: 946684817
-
id: 13
type: 29 # push
poster_id: 2
issue_id: 19 # in repo_id 58
content: '{"is_force_push":false,"commit_ids":["4ca8bcaf27e28504df7bf996819665986b01c847","96cef4a7b72b3c208340ae6f0cf55a93e9077c93","c5626fc9eff57eb1bb7b796b01d4d0f2f3f792a2"]}'
created_unix: 1688672373
-
id: 14
type: 29 # push
poster_id: 2
issue_id: 19 # in repo_id 58
content: '{"is_force_push":false,"commit_ids":["23576dd018294e476c06e569b6b0f170d0558705"]}'
created_unix: 1688672374
-
id: 15
type: 29 # push
poster_id: 2
issue_id: 19 # in repo_id 58
content: '{"is_force_push":false,"commit_ids":["3e64625bd6eb5bcba69ac97de6c8f507402df861", "c704db5794097441aa2d9dd834d5b7e2f8f08108"]}'
created_unix: 1688672375
-
id: 16
type: 29 # push
poster_id: 2
issue_id: 19 # in repo_id 58
content: '{"is_force_push":false,"commit_ids":["811d46c7e518f4f180afb862c0db5cb8c80529ce", "747ddb3506a4fa04a7747808eb56ae16f9e933dc", "837d5c8125633d7d258f93b998e867eab0145520", "1978192d98bb1b65e11c2cf37da854fbf94bffd6"]}'
created_unix: 1688672376
-
id: 17
type: 29 # push
poster_id: 2
issue_id: 19 # in repo_id 58
content: '{"is_force_push":true,"commit_ids":["1978192d98bb1b65e11c2cf37da854fbf94bffd6", "9b93963cf6de4dc33f915bb67f192d099c301f43"]}'
created_unix: 1749734240
-
id: 2000
type: 8 # milestone
poster_id: 1
issue_id: 1 # in repo_id 1
milestone_id: 1
old_milestone_id: 0
created_unix: 946684820
-
id: 2001
type: 8 # milestone
poster_id: 1
issue_id: 1 # in repo_id 1
milestone_id: 2
old_milestone_id: 1
created_unix: 946684920
-
id: 2002
type: 8 # milestone
poster_id: 1
issue_id: 1 # in repo_id 1
milestone_id: 0
old_milestone_id: 2
created_unix: 946685020
-
id: 2003
type: 8 # milestone
poster_id: 1
issue_id: 1 # in repo_id 1
milestone_id: 10 # not exsting milestone
old_milestone_id: 0
created_unix: 946685080
-
id: 2010
type: 30 # project
poster_id: 1
issue_id: 1 # in repo_id 1
project_id: 1
old_project_id: 0
created_unix: 946685120
-
id: 2011
type: 30 # project
poster_id: 1
issue_id: 1 # in repo_id 1
project_id: 2
old_project_id: 1
created_unix: 946685220
-
id: 2012
type: 30 # project
poster_id: 1
issue_id: 1 # in repo_id 1
project_id: 0
old_project_id: 2
created_unix: 946685320
-
id: 2013
type: 30 # project
poster_id: 1
issue_id: 1 # in repo_id 1
project_id: 10 # not existing project
old_project_id: 0
created_unix: 946685420
-
id: 2020
type: 7 # label
poster_id: 1
issue_id: 1 # in repo_id 1
label_id: 1
content: 1 # add label
created_unix: 946685520
-
id: 2021
type: 7 # label
poster_id: 1
issue_id: 1
label_id: 2
content: 1 # add label
created_unix: 946685620
-
id: 2022
type: 7 # label
poster_id: 2
issue_id: 1 # in repo_id 1
label_id: 1
content: 0 # remove label
created_unix: 946685720
-
id: 2023
type: 7 # label
poster_id: 1
issue_id: 1 # in repo_id 1
label_id: 1
content: 1 # add label
created_unix: 946685720
-
id: 2024
type: 7 # label
poster_id: 1
issue_id: 1 # in repo_id 1
label_id: 2
content: 0 # remove label
created_unix: 946685720
-
id: 2025
type: 7 # label
poster_id: 2
issue_id: 1 # in repo_id 1
label_id: 2
content: 1 # add label
created_unix: 946685820
-
id: 2026
type: 7 # label
poster_id: 1
issue_id: 1 # in repo_id 1
label_id: 1
content: 0 # remove label
created_unix: 946685920
-
id: 2027
type: 7 # label
poster_id: 1
issue_id: 1 # in repo_id 1
label_id: 2
content: 0 # remove label
created_unix: 946685920
- id: 2040
type: 9 # assignee
poster_id: 1
issue_id: 1 # in repo_id 1
assignee_id: 1 # self
removed_assignee: false # add assignee
created_unix: 946688020
- id: 2041
type: 9 # assignee
poster_id: 2
issue_id: 1 # in repo_id 1
assignee_id: 1
removed_assignee: true # remove assignee
created_unix: 946688120
- id: 2042
type: 9 # assignee
poster_id: 1
issue_id: 1 # in repo_id 1
assignee_id: 2
removed_assignee: false # add assignee
created_unix: 946688220
- id: 2043
type: 9 # assignee
poster_id: 2
issue_id: 1 # in repo_id 1
assignee_id: 2 # self
removed_assignee: true # remove assignee
created_unix: 946688320
- id: 2050
type: 23 # lock
poster_id: 1
issue_id: 1 # in repo_id 1
created_unix: 946688420
- id: 2051
type: 24 # unlock
poster_id: 1
issue_id: 1 # in repo_id 1
created_unix: 946688520
- id: 2052
type: 23 # lock
poster_id: 1
issue_id: 1 # in repo_id 1
content: "Too heated"
created_unix: 946688620
- id: 2053
type: 24 # unlock
poster_id: 1
issue_id: 1 # in repo_id 1
created_unix: 946688720
- id: 2060
type: 36 # pin
poster_id: 1
issue_id: 1 # in repo_id 1
created_unix: 946688820
- id: 2061
type: 37 # unpin
poster_id: 1
issue_id: 1 # in repo_id 1
created_unix: 946688920
- id: 2070
type: 2 # close
poster_id: 1
issue_id: 1 # in repo_id 1
created_unix: 946689020
- id: 2071
type: 1 # reopen
poster_id: 2
issue_id: 1 # in repo_id 1
created_unix: 946689220
- id: 2072
type: 2 # close
poster_id: 1
issue_id: 2 # pull in repo_id 1
created_unix: 946689320
- id: 2073
type: 1 # reopen
poster_id: 2
issue_id: 2 # pull in repo_id 1
created_unix: 946689420
- id: 2080
type: 3 # issue reference
poster_id: 1
issue_id: 1 # issue in repo_id 1
ref_repo_id: 1
ref_issue_id: 5 # issue in repo_id 1
created_unix: 946689500
- id: 2081
type: 3 # issue reference
poster_id: 1
issue_id: 1 # issue in repo_id 1
ref_repo_id: 1
ref_issue_id: 2 # pull in repo_id 1
created_unix: 946689600
- id: 2082
type: 3 # issue reference
poster_id: 1
issue_id: 1 # issue in repo_id 1
ref_repo_id: 32
ref_issue_id: 16 # issue in repo_id 32
created_unix: 946689700
- id: 2083
type: 3 # issue reference
poster_id: 1
issue_id: 1 # issue in repo_id 1
ref_repo_id: 10
ref_issue_id: 8 # pull in repo_id 10
created_unix: 946689800
- id: 2090
type: 6 # pull reference
poster_id: 1
issue_id: 2 # pull in repo_id 1
ref_repo_id: 1
ref_issue_id: 1 # issue in repo_id 1
created_unix: 946689900
- id: 2091
type: 6 # pull reference
poster_id: 1
issue_id: 2 # pull in repo_id 1
ref_repo_id: 1
ref_issue_id: 2 # pull in repo_id 1
created_unix: 946690000
- id: 2092
type: 6 # pull reference
poster_id: 1
issue_id: 2 # pull in repo_id 1
ref_repo_id: 32
ref_issue_id: 16 # issue in repo_id 32
created_unix: 946690050
- id: 2093
type: 6 # pull reference
poster_id: 1
issue_id: 2 # pull in repo_id 1
ref_repo_id: 10
ref_issue_id: 8 # pull in repo_id 10
created_unix: 946690100

View file

@ -0,0 +1 @@
[] # empty

View file

@ -6,6 +6,7 @@ package forgefed
import (
"database/sql"
"fmt"
"net/url"
"strings"
"time"
@ -17,9 +18,9 @@ import (
// swagger:model
type FederationHost struct {
ID int64 `xorm:"pk autoincr"`
HostFqdn string `xorm:"host_fqdn UNIQUE INDEX VARCHAR(255) NOT NULL"`
HostFqdn string `xorm:"host_fqdn UNIQUE(federation_host) INDEX VARCHAR(255) NOT NULL"`
HostPort uint16 `xorm:" UNIQUE(federation_host) INDEX NOT NULL DEFAULT 443"`
NodeInfo NodeInfo `xorm:"extends NOT NULL"`
HostPort uint16 `xorm:"NOT NULL DEFAULT 443"`
HostSchema string `xorm:"NOT NULL DEFAULT 'https'"`
LatestActivity time.Time `xorm:"NOT NULL"`
KeyID sql.NullString `xorm:"key_id UNIQUE"`
@ -42,6 +43,13 @@ func NewFederationHost(hostFqdn string, nodeInfo NodeInfo, port uint16, schema s
return result, nil
}
func (host FederationHost) AsURL() url.URL {
return url.URL{
Scheme: host.HostSchema,
Host: fmt.Sprintf("%v:%v", host.HostFqdn, host.HostPort),
}
}
// Validate collects error strings in a slice and returns this
func (host FederationHost) Validate() []string {
var result []string

View file

@ -17,12 +17,14 @@ type (
)
const (
ForgejoSourceType SoftwareNameType = "forgejo"
GiteaSourceType SoftwareNameType = "gitea"
ForgejoSourceType SoftwareNameType = "forgejo"
GiteaSourceType SoftwareNameType = "gitea"
MastodonSourceType SoftwareNameType = "mastodon"
GoToSocialSourceType SoftwareNameType = "gotosocial"
)
var KnownSourceTypes = []any{
ForgejoSourceType, GiteaSourceType,
ForgejoSourceType, GiteaSourceType, MastodonSourceType, GoToSocialSourceType,
}
// ------------------------------------------------ NodeInfoWellKnown ------------------------------------------------

View file

@ -103,6 +103,12 @@ var migrations = []*Migration{
NewMigration("Normalize repository.topics to empty slice instead of null", SetTopicsAsEmptySlice),
// v31 -> v32
NewMigration("Migrate maven package name concatenation", ChangeMavenArtifactConcatenation),
// v32 -> v33
NewMigration("Add federated user activity tables, update the `federated_user` table & add indexes", FederatedUserActivityMigration),
// v33 -> v34
NewMigration("Add `notify-email` column to `action_run` table", AddNotifyEmailToActionRun),
// v34 -> v35
NewMigration("Add index to `stopped` column in `action_run` table", AddIndexToActionRunStopped),
}
// GetCurrentDBVersion returns the current Forgejo database version.

View file

@ -0,0 +1,126 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgejo_migrations //nolint:revive
import (
"fmt"
"forgejo.org/modules/log"
"forgejo.org/modules/timeutil"
"xorm.io/xorm"
)
func dropOldFederationHostIndexes(x *xorm.Engine) {
// drop unique index on HostFqdn
type FederationHost struct {
ID int64 `xorm:"pk autoincr"`
HostFqdn string `xorm:"host_fqdn UNIQUE INDEX VARCHAR(255) NOT NULL"`
}
err := x.DropIndexes(FederationHost{})
if err != nil {
log.Warn("migration[33]: There was an issue: %v", err)
return
}
}
func addFederatedUserActivityTables(x *xorm.Engine) {
type FederatedUserActivity struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL INDEX user_id"`
ActorID int64
ActorURI string
NoteContent string `xorm:"TEXT"`
NoteURL string `xorm:"VARCHAR(255)"`
OriginalNote string `xorm:"TEXT"`
Created timeutil.TimeStamp `xorm:"created"`
}
// add unique index on HostFqdn+HostPort
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"`
}
type FederatedUserFollower struct {
ID int64 `xorm:"pk autoincr"`
FollowedUserID int64 `xorm:"NOT NULL unique(fuf_rel)"`
FollowingUserID int64 `xorm:"NOT NULL unique(fuf_rel)"`
}
// Add InboxPath to FederatedUser & add index fo UserID
type FederatedUser struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL INDEX user_id"`
InboxPath string
}
var err error
err = x.Sync(&FederationHost{})
if err != nil {
log.Warn("migration[33]: There was an issue: %v", err)
return
}
err = x.Sync(&FederatedUserActivity{})
if err != nil {
log.Warn("migration[33]: There was an issue: %v", err)
return
}
err = x.Sync(&FederatedUserFollower{})
if err != nil {
log.Warn("migration[33]: There was an issue: %v", err)
return
}
err = x.Sync(&FederatedUser{})
if err != nil {
log.Warn("migration[33]: There was an issue: %v", err)
return
}
// Migrate
sessMigration := x.NewSession()
defer sessMigration.Close()
if err := sessMigration.Begin(); err != nil {
log.Warn("migration[33]: There was an issue: %v", err)
return
}
federatedUsers := make([]*FederatedUser, 0)
err = sessMigration.OrderBy("id").Find(&federatedUsers)
if err != nil {
log.Warn("migration[33]: There was an issue: %v", err)
return
}
for _, federatedUser := range federatedUsers {
if federatedUser.InboxPath != "" {
log.Info("migration[33]: This user was already migrated: %v", federatedUser)
} else {
// Migrate User.InboxPath
sql := "UPDATE `federated_user` SET `inbox_path` = ? WHERE `id` = ?"
if _, err := sessMigration.Exec(sql, fmt.Sprintf("/api/v1/activitypub/user-id/%v/inbox", federatedUser.UserID), federatedUser.ID); err != nil {
log.Warn("migration[33]: There was an issue: %v", err)
return
}
}
}
err = sessMigration.Commit()
if err != nil {
log.Warn("migration[33]: There was an issue: %v", err)
return
}
}
func FederatedUserActivityMigration(x *xorm.Engine) error {
dropOldFederationHostIndexes(x)
addFederatedUserActivityTables(x)
return nil
}

View file

@ -0,0 +1,46 @@
// Copyright 2025 The Forgejo Authors.
// SPDX-License-Identifier: GPL-3.0-or-later
package forgejo_migrations //nolint:revive
import (
"testing"
"time"
migration_tests "forgejo.org/models/migrations/test"
"forgejo.org/modules/log"
ft "forgejo.org/modules/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_FederatedUserActivityMigration(t *testing.T) {
lc, cl := ft.NewLogChecker(log.DEFAULT, log.WARN)
lc.Filter("migration[33]")
defer cl()
// intentionally conflicting definition
type FederatedUser struct {
ID int64 `xorm:"pk autoincr"`
UserID string
}
// Prepare TestEnv
x, deferable := migration_tests.PrepareTestEnv(t, 0,
new(FederatedUser),
)
sessTest := x.NewSession()
sessTest.Insert(FederatedUser{UserID: "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" +
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" +
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"})
sessTest.Commit()
defer deferable()
if x == nil || t.Failed() {
return
}
require.NoError(t, FederatedUserActivityMigration(x))
logFiltered, _ := lc.Check(5 * time.Second)
assert.NotEmpty(t, logFiltered)
}

View file

@ -0,0 +1,14 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package forgejo_migrations //nolint:revive
import "xorm.io/xorm"
func AddNotifyEmailToActionRun(x *xorm.Engine) error {
type ActionRun struct {
ID int64
NotifyEmail bool
}
return x.Sync(new(ActionRun))
}

View file

@ -0,0 +1,19 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package forgejo_migrations //nolint:revive
import (
"forgejo.org/modules/timeutil"
"xorm.io/xorm"
)
func AddIndexToActionRunStopped(x *xorm.Engine) error {
type ActionRun struct {
ID int64
Stopped timeutil.TimeStamp `xorm:"index"`
}
return x.Sync(&ActionRun{})
}

View file

@ -66,6 +66,8 @@ func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {
sess.Asc("issue.created_unix").Asc("issue.id")
case "recentupdate":
sess.Desc("issue.updated_unix").Desc("issue.created_unix").Desc("issue.id")
case "recentclose":
sess.Desc("issue.closed_unix").Desc("issue.created_unix").Desc("issue.id")
case "leastupdate":
sess.Asc("issue.updated_unix").Asc("issue.created_unix").Asc("issue.id")
case "mostcomment":

View file

@ -5,6 +5,7 @@
package issues
import (
"bufio"
"context"
"errors"
"fmt"
@ -923,31 +924,30 @@ func MergeBlockedByOutdatedBranch(protectBranch *git_model.ProtectedBranch, pr *
return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0
}
// GetCodeOwnersFromContent returns the code owners configuration
// Return empty slice if files missing
// GetCodeOwnersFromReader returns the code owners configuration
// Return warning messages on parsing errors
// We're trying to do the best we can when parsing a file.
// Invalid lines are skipped. Non-existent users and teams too.
func GetCodeOwnersFromContent(ctx context.Context, data string) ([]*CodeOwnerRule, []string) {
if len(data) == 0 {
return nil, nil
}
func GetCodeOwnersFromReader(ctx context.Context, rc io.ReadCloser, truncated bool) ([]*CodeOwnerRule, []string) {
defer rc.Close()
scanner := bufio.NewScanner(rc)
rules := make([]*CodeOwnerRule, 0)
lines := strings.Split(data, "\n")
warnings := make([]string, 0)
var rules []*CodeOwnerRule
var warnings []string
line := 0
for scanner.Scan() {
line++
for i, line := range lines {
tokens := TokenizeCodeOwnersLine(line)
tokens := TokenizeCodeOwnersLine(scanner.Text())
if len(tokens) == 0 {
continue
} else if len(tokens) < 2 {
warnings = append(warnings, fmt.Sprintf("Line: %d: incorrect format", i+1))
warnings = append(warnings, fmt.Sprintf("Line: %d: incorrect format", line))
continue
}
rule, wr := ParseCodeOwnersLine(ctx, tokens)
for _, w := range wr {
warnings = append(warnings, fmt.Sprintf("Line: %d: %s", i+1, w))
warnings = append(warnings, fmt.Sprintf("Line: %d: %s", line, w))
}
if rule == nil {
continue
@ -955,6 +955,12 @@ func GetCodeOwnersFromContent(ctx context.Context, data string) ([]*CodeOwnerRul
rules = append(rules, rule)
}
if err := scanner.Err(); err != nil {
warnings = append(warnings, err.Error())
}
if truncated {
warnings = append(warnings, fmt.Sprintf("File too big: truncated while on line %d", line))
}
return rules, warnings
}

View file

@ -152,7 +152,8 @@ func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptio
applySorts(findSession, opts.SortType, 0)
findSession = db.SetSessionPagination(findSession, opts)
prs := make([]*PullRequest, 0, opts.PageSize)
return prs, maxResults, findSession.Find(&prs)
found := findSession.Find(&prs)
return prs, maxResults, found
}
// PullRequestList defines a list of pull requests

View file

@ -79,6 +79,47 @@ func TestPullRequestsNewest(t *testing.T) {
}
}
func TestPullRequests_Closed_RecentSortType(t *testing.T) {
// Issue ID | Closed At. | Updated At
// 2 | 1707270001 | 1707270001
// 3 | 1707271000 | 1707279999
// 11 | 1707279999 | 1707275555
tests := []struct {
sortType string
expectedIssueIDOrder []int64
}{
{"recentupdate", []int64{3, 11, 2}},
{"recentclose", []int64{11, 3, 2}},
}
require.NoError(t, unittest.PrepareTestDatabase())
_, err := db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707270001, updated_unix = 1707270001, is_closed = true WHERE id = 2")
require.NoError(t, err)
_, err = db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707271000, updated_unix = 1707279999, is_closed = true WHERE id = 3")
require.NoError(t, err)
_, err = db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707279999, updated_unix = 1707275555, is_closed = true WHERE id = 11")
require.NoError(t, err)
for _, test := range tests {
t.Run(test.sortType, func(t *testing.T) {
prs, _, err := issues_model.PullRequests(db.DefaultContext, 1, &issues_model.PullRequestsOptions{
ListOptions: db.ListOptions{
Page: 1,
},
State: "closed",
SortType: test.sortType,
})
require.NoError(t, err)
if assert.Len(t, prs, len(test.expectedIssueIDOrder)) {
for i := range test.expectedIssueIDOrder {
assert.Equal(t, test.expectedIssueIDOrder[i], prs[i].IssueID)
}
}
})
}
}
func TestLoadRequestedReviewers(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())

View file

@ -10,6 +10,7 @@ import (
"forgejo.org/models/db"
repo_model "forgejo.org/models/repo"
user_model "forgejo.org/models/user"
"forgejo.org/modules/log"
"forgejo.org/modules/timeutil"
)
@ -58,13 +59,15 @@ func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64,
return ErrAlreadyScheduledToAutoMerge{PullID: pullID}
}
_, err := db.GetEngine(ctx).Insert(&AutoMerge{
scheduledPRM, err := db.GetEngine(ctx).Insert(&AutoMerge{
DoerID: doer.ID,
PullID: pullID,
MergeStyle: style,
Message: message,
DeleteBranchAfterMerge: deleteBranch,
})
log.Trace("ScheduleAutoMerge %+v for PR %d", scheduledPRM, pullID)
return err
}
@ -81,6 +84,8 @@ func GetScheduledMergeByPullID(ctx context.Context, pullID int64) (bool, *AutoMe
return false, nil, err
}
log.Trace("GetScheduledMergeByPullID found %+v for PR %d", scheduledPRM, pullID)
scheduledPRM.Doer = doer
return true, scheduledPRM, nil
}
@ -94,6 +99,8 @@ func DeleteScheduledAutoMerge(ctx context.Context, pullID int64) error {
return db.ErrNotExist{Resource: "auto_merge", ID: pullID}
}
log.Trace("DeleteScheduledAutoMerge %+v for PR %d", scheduledPRM, pullID)
_, err = db.GetEngine(ctx).ID(scheduledPRM.ID).Delete(&AutoMerge{})
return err
}

View file

@ -11,19 +11,21 @@ import (
type FederatedUser struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
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"`
NormalizedOriginalURL string // This field is just to keep original information. Pls. do not use for search or as ID!
InboxPath string
NormalizedOriginalURL string // This field is just to keep original information. Pls. do not use for search or as ID!
}
func NewFederatedUser(userID int64, externalID string, federationHostID int64, normalizedOriginalURL string) (FederatedUser, error) {
func NewFederatedUser(userID int64, externalID string, federationHostID int64, inboxPath, normalizedOriginalURL string) (FederatedUser, error) {
result := FederatedUser{
UserID: userID,
ExternalID: externalID,
FederationHostID: federationHostID,
InboxPath: inboxPath,
NormalizedOriginalURL: normalizedOriginalURL,
}
if valid, err := validation.IsValid(result); !valid {
@ -32,10 +34,11 @@ func NewFederatedUser(userID int64, externalID string, federationHostID int64, n
return result, nil
}
func (user FederatedUser) Validate() []string {
func (federatedUser FederatedUser) Validate() []string {
var result []string
result = append(result, validation.ValidateNotEmpty(user.UserID, "UserID")...)
result = append(result, validation.ValidateNotEmpty(user.ExternalID, "ExternalID")...)
result = append(result, validation.ValidateNotEmpty(user.FederationHostID, "FederationHostID")...)
result = append(result, validation.ValidateNotEmpty(federatedUser.UserID, "UserID")...)
result = append(result, validation.ValidateNotEmpty(federatedUser.ExternalID, "ExternalID")...)
result = append(result, validation.ValidateNotEmpty(federatedUser.FederationHostID, "FederationHostID")...)
result = append(result, validation.ValidateNotEmpty(federatedUser.InboxPath, "InboxPath")...)
return result
}

View file

@ -0,0 +1,30 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
import "forgejo.org/modules/validation"
type FederatedUserFollower struct {
ID int64 `xorm:"pk autoincr"`
FollowedUserID int64 `xorm:"NOT NULL unique(fuf_rel)"`
FollowingUserID int64 `xorm:"NOT NULL unique(fuf_rel)"`
}
func NewFederatedUserFollower(followedUserID, federatedUserID int64) (FederatedUserFollower, error) {
result := FederatedUserFollower{
FollowedUserID: followedUserID,
FollowingUserID: federatedUserID,
}
if valid, err := validation.IsValid(result); !valid {
return FederatedUserFollower{}, err
}
return result, nil
}
func (user FederatedUserFollower) Validate() []string {
var result []string
result = append(result, validation.ValidateNotEmpty(user.FollowedUserID, "FollowedUserID")...)
result = append(result, validation.ValidateNotEmpty(user.FollowingUserID, "FollowingUserID")...)
return result
}

View file

@ -0,0 +1,27 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
import (
"testing"
"forgejo.org/modules/validation"
"github.com/stretchr/testify/assert"
)
func Test_FederatedUserFollowerValidation(t *testing.T) {
sut := FederatedUserFollower{
FollowedUserID: 12,
FollowingUserID: 1,
}
res, err := validation.IsValid(sut)
assert.Truef(t, res, "sut should be valid but was %q", err)
sut = FederatedUserFollower{
FollowedUserID: 1,
}
res, _ = validation.IsValid(sut)
assert.False(t, res, "sut should be invalid")
}

View file

@ -14,6 +14,7 @@ func Test_FederatedUserValidation(t *testing.T) {
UserID: 12,
ExternalID: "12",
FederationHostID: 1,
InboxPath: "/api/v1/activitypub/user-id/12/inbox",
}
if res, err := validation.IsValid(sut); !res {
t.Errorf("sut should be valid but was %q", err)
@ -22,6 +23,7 @@ func Test_FederatedUserValidation(t *testing.T) {
sut = FederatedUser{
ExternalID: "12",
FederationHostID: 1,
InboxPath: "/api/v1/activitypub/user-id/12/inbox",
}
if res, _ := validation.IsValid(sut); res {
t.Error("sut should be invalid")

View file

@ -11,6 +11,7 @@ import (
)
// Follow represents relations of user and their followers.
// TODO: We should unify Activity-pub-following and classical following (see models/user/user_repository.go#IsFollowingAp)
type Follow struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(follow)"`

View file

@ -1,4 +1,4 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// Copyright 2024, 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
@ -8,12 +8,14 @@ import (
"fmt"
"forgejo.org/models/db"
"forgejo.org/modules/log"
"forgejo.org/modules/optional"
"forgejo.org/modules/validation"
)
func init() {
db.RegisterModel(new(FederatedUser))
db.RegisterModel(new(FederatedUserFollower))
}
func CreateFederatedUser(ctx context.Context, user *User, federatedUser *FederatedUser) error {
@ -30,7 +32,12 @@ func CreateFederatedUser(ctx context.Context, user *User, federatedUser *Federat
if err != nil {
return err
}
defer committer.Close()
defer func() {
err := committer.Close()
if err != nil {
log.Error("Error closing committer: %v", err)
}
}()
if err := CreateUser(ctx, user, &overwrite); err != nil {
return err
@ -50,6 +57,14 @@ func CreateFederatedUser(ctx context.Context, user *User, federatedUser *Federat
return committer.Commit()
}
func (federatedUser *FederatedUser) UpdateFederatedUser(ctx context.Context) error {
if _, err := validation.IsValid(federatedUser); err != nil {
return err
}
_, err := db.GetEngine(ctx).ID(federatedUser.ID).Cols("inbox_path").Update(federatedUser)
return err
}
func FindFederatedUser(ctx context.Context, externalID string, federationHostID int64) (*User, *FederatedUser, error) {
federatedUser := new(FederatedUser)
user := new(User)
@ -75,6 +90,41 @@ func FindFederatedUser(ctx context.Context, externalID string, federationHostID
return user, federatedUser, nil
}
func GetFederatedUser(ctx context.Context, externalID string, federationHostID int64) (*User, *FederatedUser, error) {
user, federatedUser, err := FindFederatedUser(ctx, externalID, federationHostID)
if err != nil {
return nil, nil, err
} else if federatedUser == nil {
return nil, nil, fmt.Errorf("FederatedUser for externalId = %v and federationHostId = %v does not exist", externalID, federationHostID)
}
return user, federatedUser, nil
}
func GetFederatedUserByUserID(ctx context.Context, userID int64) (*User, *FederatedUser, error) {
federatedUser := new(FederatedUser)
user := new(User)
has, err := db.GetEngine(ctx).Where("user_id=?", userID).Get(federatedUser)
if err != nil {
return nil, nil, err
} else if !has {
return nil, nil, fmt.Errorf("Federated user %v does not exist", federatedUser.UserID)
}
has, err = db.GetEngine(ctx).ID(federatedUser.UserID).Get(user)
if err != nil {
return nil, nil, err
} else if !has {
return nil, nil, fmt.Errorf("User %v for federated user is missing", federatedUser.UserID)
}
if res, err := validation.IsValid(*user); !res {
return nil, nil, err
}
if res, err := validation.IsValid(*federatedUser); !res {
return nil, nil, err
}
return user, federatedUser, nil
}
func FindFederatedUserByKeyID(ctx context.Context, keyID string) (*User, *FederatedUser, error) {
federatedUser := new(FederatedUser)
user := new(User)
@ -101,7 +151,85 @@ func FindFederatedUserByKeyID(ctx context.Context, keyID string) (*User, *Federa
return user, federatedUser, nil
}
func UpdateFederatedUser(ctx context.Context, federatedUser *FederatedUser) error {
if res, err := validation.IsValid(federatedUser); !res {
return err
}
_, err := db.GetEngine(ctx).ID(federatedUser.ID).Update(federatedUser)
return err
}
func DeleteFederatedUser(ctx context.Context, userID int64) error {
_, err := db.GetEngine(ctx).Delete(&FederatedUser{UserID: userID})
return err
}
func GetFollowersForUser(ctx context.Context, user *User) ([]*FederatedUserFollower, error) {
if res, err := validation.IsValid(user); !res {
return nil, err
}
followers := make([]*FederatedUserFollower, 0, 8)
err := db.GetEngine(ctx).
Where("followed_user_id = ?", user.ID).
Find(&followers)
if err != nil {
return nil, err
}
for _, element := range followers {
if res, err := validation.IsValid(*element); !res {
return nil, err
}
}
return followers, nil
}
func AddFollower(ctx context.Context, followedUser *User, followingUser *FederatedUser) (*FederatedUserFollower, error) {
if res, err := validation.IsValid(followedUser); !res {
return nil, err
}
if res, err := validation.IsValid(followingUser); !res {
return nil, err
}
federatedUserFollower, err := NewFederatedUserFollower(followedUser.ID, followingUser.UserID)
if err != nil {
return nil, err
}
_, err = db.GetEngine(ctx).Insert(&federatedUserFollower)
if err != nil {
return nil, err
}
return &federatedUserFollower, err
}
func RemoveFollower(ctx context.Context, followedUser *User, followingUser *FederatedUser) error {
if res, err := validation.IsValid(followedUser); !res {
return err
}
if res, err := validation.IsValid(followingUser); !res {
return err
}
_, err := db.GetEngine(ctx).Delete(&FederatedUserFollower{
FollowedUserID: followedUser.ID,
FollowingUserID: followingUser.UserID,
})
return err
}
// TODO: We should unify Activity-pub-following and classical following (see models/user/follow.go)
func IsFollowingAp(ctx context.Context, followedUser *User, followingUser *FederatedUser) (bool, error) {
if res, err := validation.IsValid(followedUser); !res {
return false, err
}
if res, err := validation.IsValid(followingUser); !res {
return false, err
}
return db.GetEngine(ctx).Get(&FederatedUserFollower{
FollowedUserID: followedUser.ID,
FollowingUserID: followingUser.UserID,
})
}

View file

@ -12,6 +12,13 @@ import (
"forgejo.org/modules/structs"
)
// IsSystem returns true if the user has a fixed
// negative ID, is never stored in the database and
// is generated on the fly when needed.
func (u *User) IsSystem() bool {
return u.IsGhost() || u.IsActions()
}
const (
GhostUserID = -1
GhostUserName = "Ghost"

View file

@ -148,7 +148,7 @@ func TestAPActorID_APActorID(t *testing.T) {
assert.Equal(t, expected, url)
}
func TestAPActorKeyID(t *testing.T) {
func TestKeyID(t *testing.T) {
user := user_model.User{ID: 1}
url := user.APActorKeyID()
expected := "https://try.gitea.io/api/v1/activitypub/user-id/1#main-key"

View file

@ -323,6 +323,10 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa
matchTimes++
}
case "paths":
if refName.IsTag() {
matchTimes++
break
}
filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before)
if err != nil {
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err)
@ -336,6 +340,10 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa
}
}
case "paths-ignore":
if refName.IsTag() {
matchTimes++
break
}
filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before)
if err != nil {
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err)

View file

@ -150,6 +150,24 @@ func TestDetectMatched(t *testing.T) {
yamlOn: "on: workflow_dispatch",
expected: true,
},
{
desc: "push to tag matches workflow with paths condition (should skip paths check)",
triggeredEvent: webhook_module.HookEventPush,
payload: &api.PushPayload{
Ref: "refs/tags/v1.0.0",
Before: "0000000",
Commits: []*api.PayloadCommit{
{
ID: "abcdef123456",
Added: []string{"src/main.go"},
Message: "Release v1.0.0",
},
},
},
commit: nil,
yamlOn: "on:\n push:\n paths:\n - src/**",
expected: true,
},
}
for _, tc := range testCases {

View file

@ -12,7 +12,6 @@ import (
"forgejo.org/modules/log"
"forgejo.org/modules/typesniffer"
"forgejo.org/modules/util"
)
// Blob represents a Git object.
@ -25,42 +24,25 @@ type Blob struct {
repo *Repository
}
// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
// Calling the Close function on the result will discard all unread output.
func (b *Blob) DataAsync() (io.ReadCloser, error) {
func (b *Blob) newReader() (*bufio.Reader, int64, func(), error) {
wr, rd, cancel, err := b.repo.CatFileBatch(b.repo.Ctx)
if err != nil {
return nil, err
return nil, 0, nil, err
}
_, err = wr.Write([]byte(b.ID.String() + "\n"))
if err != nil {
cancel()
return nil, err
return nil, 0, nil, err
}
_, _, size, err := ReadBatchLine(rd)
if err != nil {
cancel()
return nil, err
return nil, 0, nil, err
}
b.gotSize = true
b.size = size
if size < 4096 {
bs, err := io.ReadAll(io.LimitReader(rd, size))
defer cancel()
if err != nil {
return nil, err
}
_, err = rd.Discard(1)
return io.NopCloser(bytes.NewReader(bs)), err
}
return &blobReader{
rd: rd,
n: size,
cancel: cancel,
}, nil
return rd, size, cancel, err
}
// Size returns the uncompressed size of the blob
@ -91,10 +73,36 @@ func (b *Blob) Size() int64 {
return b.size
}
// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
// Calling the Close function on the result will discard all unread output.
func (b *Blob) DataAsync() (io.ReadCloser, error) {
rd, size, cancel, err := b.newReader()
if err != nil {
return nil, err
}
if size < 4096 {
bs, err := io.ReadAll(io.LimitReader(rd, size))
defer cancel()
if err != nil {
return nil, err
}
_, err = rd.Discard(1)
return io.NopCloser(bytes.NewReader(bs)), err
}
return &blobReader{
rd: rd,
n: size,
cancel: cancel,
}, nil
}
type blobReader struct {
rd *bufio.Reader
n int64
cancel func()
rd *bufio.Reader
n int64 // number of bytes to read
additionalDiscard int64 // additional number of bytes to discard
cancel func()
}
func (b *blobReader) Read(p []byte) (n int, err error) {
@ -117,7 +125,8 @@ func (b *blobReader) Close() error {
defer b.cancel()
if err := DiscardFull(b.rd, b.n+1); err != nil {
// discard the unread bytes, the truncated bytes and the trailing newline
if err := DiscardFull(b.rd, b.n+b.additionalDiscard+1); err != nil {
return err
}
@ -131,17 +140,35 @@ func (b *Blob) Name() string {
return b.name
}
// GetBlobContent Gets the limited content of the blob as raw text
// NewTruncatedReader return a blob-reader which silently truncates when the limit is reached (io.EOF will be returned)
func (b *Blob) NewTruncatedReader(limit int64) (rc io.ReadCloser, fullSize int64, err error) {
r, fullSize, cancel, err := b.newReader()
if err != nil {
return nil, fullSize, err
}
limit = min(limit, fullSize)
return &blobReader{
rd: r,
n: limit,
additionalDiscard: fullSize - limit,
cancel: cancel,
}, fullSize, nil
}
// GetBlobContent Gets the truncated content of the blob as raw text
func (b *Blob) GetBlobContent(limit int64) (string, error) {
if limit <= 0 {
return "", nil
}
dataRc, err := b.DataAsync()
rc, fullSize, err := b.NewTruncatedReader(limit)
if err != nil {
return "", err
}
defer dataRc.Close()
buf, err := util.ReadWithLimit(dataRc, int(limit))
defer rc.Close()
buf := make([]byte, min(fullSize, limit))
_, err = io.ReadFull(rc, buf)
return string(buf), err
}

View file

@ -35,6 +35,106 @@ func TestBlob_Data(t *testing.T) {
assert.Equal(t, output, string(data))
}
func TestBlob(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
repo, err := openRepositoryWithDefaultContext(bareRepo1Path)
require.NoError(t, err)
defer repo.Close()
testBlob, err := repo.GetBlob("6c493ff740f9380390d5c9ddef4af18697ac9375")
require.NoError(t, err)
t.Run("GetBlobContent", func(t *testing.T) {
r, err := testBlob.GetBlobContent(100)
require.NoError(t, err)
require.Equal(t, "file2\n", r)
r, err = testBlob.GetBlobContent(-1)
require.NoError(t, err)
require.Empty(t, r)
r, err = testBlob.GetBlobContent(4)
require.NoError(t, err)
require.Equal(t, "file", r)
r, err = testBlob.GetBlobContent(6)
require.NoError(t, err)
require.Equal(t, "file2\n", r)
})
t.Run("NewTruncatedReader", func(t *testing.T) {
// read fewer than available
rc, size, err := testBlob.NewTruncatedReader(100)
require.NoError(t, err)
require.Equal(t, int64(6), size)
buf := make([]byte, 1)
n, err := rc.Read(buf)
require.NoError(t, err)
require.Equal(t, 1, n)
require.Equal(t, "f", string(buf))
n, err = rc.Read(buf)
require.NoError(t, err)
require.Equal(t, 1, n)
require.Equal(t, "i", string(buf))
require.NoError(t, rc.Close())
// read more than available
rc, size, err = testBlob.NewTruncatedReader(100)
require.NoError(t, err)
require.Equal(t, int64(6), size)
buf = make([]byte, 100)
n, err = rc.Read(buf)
require.NoError(t, err)
require.Equal(t, 6, n)
require.Equal(t, "file2\n", string(buf[:n]))
n, err = rc.Read(buf)
require.Error(t, err)
require.Equal(t, io.EOF, err)
require.Equal(t, 0, n)
require.NoError(t, rc.Close())
// read more than truncated
rc, size, err = testBlob.NewTruncatedReader(4)
require.NoError(t, err)
require.Equal(t, int64(6), size)
buf = make([]byte, 10)
n, err = rc.Read(buf)
require.NoError(t, err)
require.Equal(t, 4, n)
require.Equal(t, "file", string(buf[:n]))
n, err = rc.Read(buf)
require.Error(t, err)
require.Equal(t, io.EOF, err)
require.Equal(t, 0, n)
require.NoError(t, rc.Close())
})
t.Run("NonExisting", func(t *testing.T) {
nonExistingBlob, err := repo.GetBlob("00003ff740f9380390d5c9ddef4af18690000000")
require.NoError(t, err)
r, err := nonExistingBlob.GetBlobContent(100)
require.Error(t, err)
require.IsType(t, ErrNotExist{}, err)
require.Empty(t, r)
rc, size, err := nonExistingBlob.NewTruncatedReader(100)
require.Error(t, err)
require.IsType(t, ErrNotExist{}, err)
require.Empty(t, rc)
require.Empty(t, size)
})
}
func Benchmark_Blob_Data(b *testing.B) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
repo, err := openRepositoryWithDefaultContext(bareRepo1Path)

View file

@ -267,8 +267,13 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
// RenderString renders Markdown string to HTML with all specific handling stuff and return string
func RenderString(ctx *markup.RenderContext, content string) (template.HTML, error) {
return RenderReader(ctx, strings.NewReader(content))
}
// RenderReader renders Markdown io.Reader to HTML with all specific handling stuff and return string
func RenderReader(ctx *markup.RenderContext, input io.Reader) (template.HTML, error) {
var buf strings.Builder
if err := Render(ctx, strings.NewReader(content), &buf); err != nil {
if err := Render(ctx, input, &buf); err != nil {
return "", err
}
return template.HTML(buf.String()), nil

View file

@ -23,6 +23,11 @@ var wellKnownMimeTypesLower = map[string]string{
".wasm": "application/wasm",
".webp": "image/webp",
".xml": "text/xml; charset=utf-8",
".glb": "model/gltf-binary",
".gltf": "model/gltf+json",
".obj": "model/obj",
".stl": "model/stl",
".3mf": "model/3mf",
// well, there are some types missing from the builtin list
".txt": "text/plain; charset=utf-8",

View file

@ -78,3 +78,9 @@ type ActionRun struct {
// the url of this action run
HTMLURL string `json:"html_url"`
}
// ListActionRunResponse return a list of ActionRun
type ListActionRunResponse struct {
Entries []*ActionRun `json:"workflow_runs"`
TotalCount int64 `json:"total_count"`
}

View file

@ -32,23 +32,3 @@ type ActionTaskResponse struct {
Entries []*ActionTask `json:"workflow_runs"`
TotalCount int64 `json:"total_count"`
}
// ActionRun represents an ActionRun
type RepoActionRun struct {
ID int64 `json:"id"`
Name string `json:"name"`
RunNumber int64 `json:"run_number"`
Event string `json:"event"`
Status string `json:"status"`
HeadBranch string `json:"head_branch"`
HeadSHA string `json:"head_sha"`
WorkflowID string `json:"workflow_id"`
URL string `json:"url"`
TriggeringActor *User `json:"triggering_actor"`
}
// ListActionRunResponse return a list of ActionRun
type ListRepoActionRunResponse struct {
Entries []*RepoActionRun `json:"workflow_runs"`
TotalCount int64 `json:"total_count"`
}

View file

@ -24,6 +24,16 @@ const (
AvifMimeType = "image/avif"
// ApplicationOctetStream MIME type of binary files.
ApplicationOctetStream = "application/octet-stream"
// GLTFMimeType MIME type of GLTF files.
GLTFMimeType = "model/gltf+json"
// GLBMimeType MIME type of GLB files.
GLBMimeType = "model/gltf-binary"
// OBJMimeType MIME type of OBJ files.
OBJMimeType = "model/obj"
// STLMimeType MIME type of STL files.
STLMimeType = "model/stl"
// 3MFMimeType MIME type of 3MF files.
ThreeMFMimeType = "model/3mf"
)
var (
@ -67,6 +77,36 @@ func (ct SniffedType) IsAudio() bool {
return strings.Contains(ct.contentType, "audio/")
}
// Is3DModel detects if data is a 3D format
func (ct SniffedType) Is3DModel() bool {
return strings.Contains(ct.contentType, "model/")
}
// IsGLTFFile detects if data is an SVG image format
func (ct SniffedType) IsGLTF() bool {
return strings.Contains(ct.contentType, GLTFMimeType)
}
// IsGLBFile detects if data is an GLB image format
func (ct SniffedType) IsGLB() bool {
return strings.Contains(ct.contentType, GLBMimeType)
}
// IsOBJFile detects if data is an OBJ image format
func (ct SniffedType) IsOBJ() bool {
return strings.Contains(ct.contentType, OBJMimeType)
}
// IsSTLTextFile detects if data is an STL text format
func (ct SniffedType) IsSTL() bool {
return strings.Contains(ct.contentType, STLMimeType)
}
// Is3MFFile detects if data is an 3MF image format
func (ct SniffedType) Is3MF() bool {
return strings.Contains(ct.contentType, ThreeMFMimeType)
}
// IsRepresentableAsText returns true if file content can be represented as
// plain text or is empty.
func (ct SniffedType) IsRepresentableAsText() bool {
@ -75,7 +115,7 @@ func (ct SniffedType) IsRepresentableAsText() bool {
// IsBrowsableBinaryType returns whether a non-text type can be displayed in a browser
func (ct SniffedType) IsBrowsableBinaryType() bool {
return ct.IsImage() || ct.IsSvgImage() || ct.IsPDF() || ct.IsVideo() || ct.IsAudio()
return ct.IsImage() || ct.IsSvgImage() || ct.IsPDF() || ct.IsVideo() || ct.IsAudio() || ct.Is3DModel()
}
// GetMimeType returns the mime type
@ -135,6 +175,13 @@ func DetectContentType(data []byte) SniffedType {
ct = "audio/ogg" // for most cases, it is used as an audio container
}
}
// GLTF is unsupported by http.DetectContentType
// hexdump -n 4 -C glTF.glb
if bytes.HasPrefix(data, []byte("glTF")) {
ct = GLBMimeType
}
return SniffedType{ct}
}

View file

@ -117,6 +117,14 @@ func TestIsAudio(t *testing.T) {
assert.True(t, DetectContentType([]byte("ID3Toy\n====\t* hi 🌞, ..."+"🌛"[0:2])).IsText()) // test ID3 tag with incomplete UTF8 char
}
func TestIsGLB(t *testing.T) {
glb, _ := hex.DecodeString("676c5446")
assert.True(t, DetectContentType(glb).IsGLB())
assert.True(t, DetectContentType(glb).Is3DModel())
assert.False(t, DetectContentType([]byte("plain text")).IsGLB())
assert.False(t, DetectContentType([]byte("plain text")).Is3DModel())
}
func TestDetectContentTypeFromReader(t *testing.T) {
mp3, _ := base64.StdEncoding.DecodeString("SUQzBAAAAAABAFRYWFgAAAASAAADbWFqb3JfYnJhbmQAbXA0MgBUWFhYAAAAEQAAA21pbm9yX3Zl")
st, err := DetectContentTypeFromReader(bytes.NewReader(mp3))
@ -145,3 +153,15 @@ func TestDetectContentTypeAvif(t *testing.T) {
assert.True(t, st.IsImage())
}
func TestDetectContentTypeModelGLB(t *testing.T) {
glb, err := hex.DecodeString("676c5446")
require.NoError(t, err)
st, err := DetectContentTypeFromReader(bytes.NewReader(glb))
require.NoError(t, err)
// print st for debugging
assert.Equal(t, "model/gltf-binary", st.GetMimeType())
assert.True(t, st.IsGLB())
}

View file

@ -4,7 +4,6 @@
package util
import (
"bytes"
"errors"
"io"
)
@ -20,42 +19,6 @@ func ReadAtMost(r io.Reader, buf []byte) (n int, err error) {
return n, err
}
// ReadWithLimit reads at most "limit" bytes from r into buf.
// If EOF or ErrUnexpectedEOF occurs while reading, err will be nil.
func ReadWithLimit(r io.Reader, n int) (buf []byte, err error) {
return readWithLimit(r, 1024, n)
}
func readWithLimit(r io.Reader, batch, limit int) ([]byte, error) {
if limit <= batch {
buf := make([]byte, limit)
n, err := ReadAtMost(r, buf)
if err != nil {
return nil, err
}
return buf[:n], nil
}
res := bytes.NewBuffer(make([]byte, 0, batch))
bufFix := make([]byte, batch)
eof := false
for res.Len() < limit && !eof {
bufTmp := bufFix
if res.Len()+batch > limit {
bufTmp = bufFix[:limit-res.Len()]
}
n, err := io.ReadFull(r, bufTmp)
if err == io.EOF || err == io.ErrUnexpectedEOF {
eof = true
} else if err != nil {
return nil, err
}
if _, err = res.Write(bufTmp[:n]); err != nil {
return nil, err
}
}
return res.Bytes(), nil
}
// ErrNotEmpty is an error reported when there is a non-empty reader
var ErrNotEmpty = errors.New("not-empty")

View file

@ -1,67 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package util
import (
"bytes"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type readerWithError struct {
buf *bytes.Buffer
}
func (r *readerWithError) Read(p []byte) (n int, err error) {
if r.buf.Len() < 2 {
return 0, errors.New("test error")
}
return r.buf.Read(p)
}
func TestReadWithLimit(t *testing.T) {
bs := []byte("0123456789abcdef")
// normal test
buf, err := readWithLimit(bytes.NewBuffer(bs), 5, 2)
require.NoError(t, err)
assert.Equal(t, []byte("01"), buf)
buf, err = readWithLimit(bytes.NewBuffer(bs), 5, 5)
require.NoError(t, err)
assert.Equal(t, []byte("01234"), buf)
buf, err = readWithLimit(bytes.NewBuffer(bs), 5, 6)
require.NoError(t, err)
assert.Equal(t, []byte("012345"), buf)
buf, err = readWithLimit(bytes.NewBuffer(bs), 5, len(bs))
require.NoError(t, err)
assert.Equal(t, []byte("0123456789abcdef"), buf)
buf, err = readWithLimit(bytes.NewBuffer(bs), 5, 100)
require.NoError(t, err)
assert.Equal(t, []byte("0123456789abcdef"), buf)
// test with error
buf, err = readWithLimit(&readerWithError{bytes.NewBuffer(bs)}, 5, 10)
require.NoError(t, err)
assert.Equal(t, []byte("0123456789"), buf)
buf, err = readWithLimit(&readerWithError{bytes.NewBuffer(bs)}, 5, 100)
require.ErrorContains(t, err, "test error")
assert.Empty(t, buf)
// test public function
buf, err = ReadWithLimit(bytes.NewBuffer(bs), 2)
require.NoError(t, err)
assert.Equal(t, []byte("01"), buf)
buf, err = ReadWithLimit(bytes.NewBuffer(bs), 9999999)
require.NoError(t, err)
assert.Equal(t, []byte("0123456789abcdef"), buf)
}

View file

@ -54,3 +54,12 @@ func SplitTrimSpace(input, sep string) []string {
return stringList
}
// TruncateRunes returns a truncated string with given rune limit,
// it returns input string if its rune length doesn't exceed the limit.
func TruncateRunes(str string, limit int) string {
if utf8.RuneCountInString(str) < limit {
return str
}
return string([]rune(str)[:limit])
}

View file

@ -44,3 +44,18 @@ func TestSplitString(t *testing.T) {
}
test(tc, SplitStringAtByteN)
}
func TestTruncateRunes(t *testing.T) {
assert.Empty(t, TruncateRunes("", 0))
assert.Empty(t, TruncateRunes("", 1))
assert.Empty(t, TruncateRunes("ab", 0))
assert.Equal(t, "a", TruncateRunes("ab", 1))
assert.Equal(t, "ab", TruncateRunes("ab", 2))
assert.Equal(t, "ab", TruncateRunes("ab", 3))
assert.Empty(t, TruncateRunes("测试", 0))
assert.Equal(t, "测", TruncateRunes("测试", 1))
assert.Equal(t, "测试", TruncateRunes("测试", 2))
assert.Equal(t, "测试", TruncateRunes("测试", 3))
}

View file

@ -2922,6 +2922,7 @@ settings.event_action_success = Úspěch
settings.event_action_success_desc = Běh akce byl úspěšný.
settings.event_header_action = Události běhu akce
settings.event_action_recover_desc = Běh akce byl úspěšný, předchozí běh akce ve stejném workflow selhal.
issues.filter_type.all_pull_requests = Všechny žádosti o sloučení
[graphs]
component_loading_info = Tohle může chvíli trvat…

View file

@ -2735,6 +2735,7 @@ settings.event_action_success = Success
settings.event_action_recover_desc = Handlingskørsel lykkedes efter at den sidste handlingskørsel i samme arbejdsgang mislykkedes.
settings.event_action_failure_desc = Handlingskørsel sluttede som en fejl.
settings.event_action_recover = Gendan
issues.filter_type.all_pull_requests = Alle pull-anmodninger
[notification]
watching = Overvåger

View file

@ -2924,6 +2924,7 @@ settings.event_action_success = Erfolg
settings.event_header_action = Action-Run-Ereignisse
settings.event_action_recover_desc = Action-Run war erfolgreich, nachdem der letzte Action-Run im selben Arbeitsablauf fehlgeschlagen ist.
settings.event_action_recover = Wiederherstellen
issues.filter_type.all_pull_requests = Alle Pull-Requests
[graphs]
component_loading_failed = Konnte %s nicht laden

View file

@ -768,8 +768,8 @@ update_profile_success = Your profile has been updated.
change_username = Your username has been changed.
change_username_prompt = Note: Changing your username also changes your account URL.
change_username_redirect_prompt = The old username will redirect until someone claims it.
change_username_redirect_prompt.with_cooldown.one = The old username will be available to everyone after a cooldown period of %[1]d day, you can still reclaim the old username during the cooldown period.
change_username_redirect_prompt.with_cooldown.few = The old username will be available to everyone after a cooldown period of %[1]d days, you can still reclaim the old username during the cooldown period.
change_username_redirect_prompt.with_cooldown.one = The old username will be available to everyone after a cooldown period of %[1]d day. You can still reclaim the old username during the cooldown period.
change_username_redirect_prompt.with_cooldown.few = The old username will be available to everyone after a cooldown period of %[1]d days. You can still reclaim the old username during the cooldown period.
continue = Continue
cancel = Cancel
language = Language
@ -1628,6 +1628,7 @@ issues.filter_poster = Author
issues.filter_poster_no_select = All authors
issues.filter_type = Type
issues.filter_type.all_issues = All issues
issues.filter_type.all_pull_requests = All pull requests
issues.filter_type.assigned_to_you = Assigned to you
issues.filter_type.created_by_you = Created by you
issues.filter_type.mentioning_you = Mentioning you
@ -1693,15 +1694,13 @@ issues.close_comment_issue = Close with comment
issues.reopen_issue = Reopen
issues.reopen_comment_issue = Reopen with comment
issues.create_comment = Comment
issues.closed_at = `closed this issue <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at = `reopened this issue <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commit_ref_at = `referenced this issue from a commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_issue_from = `<a href="%[3]s">referenced this issue %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_pull_from = `<a href="%[3]s">referenced this pull request %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closing_from = `<a href="%[3]s">referenced this issue from a pull request %[4]s that will close it</a>, <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopening_from = `<a href="%[3]s">referenced this issue from a pull request %[4]s that will reopen it</a>, <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closed_from = `<a href="%[3]s">closed this issue %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopened_from = `<a href="%[3]s">reopened this issue %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.closed_at = `closed this issue %s`
issues.reopened_at = `reopened this issue %s`
issues.commit_ref_at = `referenced this issue from a commit %s`
issues.ref_issue_from = `<a href="%[2]s">referenced this issue %[3]s</a> %[1]s`
issues.ref_pull_from = `<a href="%[2]s">referenced this pull request %[3]s</a> %[1]s`
issues.ref_closing_from = `<a href="%[2]s">referenced this issue from a pull request %[3]s that will close it</a>, %[1]s`
issues.ref_reopening_from = `<a href="%[2]s">referenced this issue from a pull request %[3]s that will reopen it</a>, %[1]s`
issues.ref_from = `from %[1]s`
issues.author = Author
issues.author.tooltip.issue = This user is the author of this issue.
@ -2013,9 +2012,9 @@ pulls.update_branch_success = Branch update was successful
pulls.update_not_allowed = You are not allowed to update branch
pulls.outdated_with_base_branch = This branch is out-of-date with the base branch
pulls.close = Close pull request
pulls.closed_at = `closed this pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.reopened_at = `reopened this pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.commit_ref_at = `referenced this pull request from a commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.closed_at = `closed this pull request %s`
pulls.reopened_at = `reopened this pull request %s`
pulls.commit_ref_at = `referenced this pull request from a commit %s`
pulls.cmd_instruction_hint = View command line instructions
pulls.cmd_instruction_checkout_title = Checkout
pulls.cmd_instruction_checkout_desc = From your project repository, check out a new branch and test the changes.
@ -2932,8 +2931,8 @@ settings.update_settings = Update settings
settings.update_setting_success = Organization settings have been updated.
settings.change_orgname_prompt = Note: Changing the organization name will also change your organization's URL and free the old name.
settings.change_orgname_redirect_prompt = The old name will redirect until it is claimed.
settings.change_orgname_redirect_prompt.with_cooldown.one = The old organization name will be available to everyone after a cooldown period of %[1]d day, you can still reclaim the old name during the cooldown period.
settings.change_orgname_redirect_prompt.with_cooldown.few = The old organization name will be available to everyone after a cooldown period of %[1]d days, you can still reclaim the old name during the cooldown period.
settings.change_orgname_redirect_prompt.with_cooldown.one = The old organization name will be available to everyone after a cooldown period of %[1]d day. You can still reclaim the old name during the cooldown period.
settings.change_orgname_redirect_prompt.with_cooldown.few = The old organization name will be available to everyone after a cooldown period of %[1]d days. You can still reclaim the old name during the cooldown period.
settings.update_avatar_success = The organization's avatar has been updated.
settings.delete = Delete organization
settings.delete_account = Delete this organization

View file

@ -1500,7 +1500,7 @@ issues.content_history.created = ginawa
editor.patching = Pina-patch:
editor.fail_to_apply_patch = Hindi malapat ang patch na "%s"
settings.danger_zone = Mapanganib na lugar
issues.closed_at = `isinara ang isyung <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.closed_at = `isinara ang isyung ito <a id="%[1]s" href="#%[1]s">%[2]s</a>`
settings.collaboration.admin = Tagapangasiwa
settings.admin_settings = Mga setting ng tagapangasiwa
issues.start_tracking_history = `sinimulan ang trabaho %s`
@ -2777,6 +2777,9 @@ settings.event_header_action = Mga event sa run ng aksyon
settings.event_action_failure = Pagkabigo
settings.event_action_failure_desc = Natapos ang action run bilang pagkabigo.
settings.event_action_recover = I-recover
settings.event_action_success = Matagumpay
settings.event_action_success_desc = Matagumpay na natapos ang Action Run.
settings.event_action_recover_desc = Matagumpay na natapos ang Action Run pagkatapos na nabigo ang huling Action Run sa katulad na workflow.
[search]
commit_kind = Maghanap ng mga commit…

View file

@ -2920,6 +2920,7 @@ settings.event_header_action = Événements d'exécution d'action
settings.event_action_success_desc = L'exécution de l'action a réussi.
settings.event_action_failure_desc = L'exécution de l'action a échoué.
settings.event_action_recover_desc = L'exécution de l'action a réussi après l'échec de la dernière exécution de l'action dans le même workflow.
issues.filter_type.all_pull_requests = Toutes les demandes d'ajout
[graphs]
component_loading = Chargement %s…

View file

@ -2920,6 +2920,7 @@ settings.event_action_recover = Atgūt
settings.event_action_recover_desc = Darbības izpilde bija sekmīga pēc kļūmes iepriekšējā darbības izpildē tajā pašā darbplūsmā.
settings.event_action_success = Sekmīgi
settings.event_action_success_desc = Darbības izpilde bija sekmīga.
issues.filter_type.all_pull_requests = Visi izmaiņu pieprasījumi
[graphs]
component_loading=Ielādē %s…

View file

@ -2621,6 +2621,7 @@ settings.event_action_recover = Verhaalt
settings.event_header_action = Aktioons-Loop-Vörfallen
settings.event_action_failure_desc = Aktioons-Loop is as fehlslagen ennt.
settings.event_action_recover_desc = Aktioons-Loop is daankregen worden, nadeem de leste Aktioons-Loop in de sülven Warkwies fehlslagen is.
issues.filter_type.all_pull_requests = All Haalvörslagen
[repo.permissions]
code.read = <b>Lesen:</b> De Quelltext vun deesem Repositorium ankieken un klonen.

View file

@ -1408,7 +1408,7 @@ editor.fail_to_update_file=Не удалось обновить/создать
editor.fail_to_update_file_summary=Ошибка:
editor.push_rejected_no_message=Изменение отклонено сервером без сообщения. Пожалуйста, проверьте Git-хуки.
editor.push_rejected=Изменение отклонено сервером. Пожалуйста, проверьте Git-хуки.
editor.push_rejected_summary=Причина отклонения:
editor.push_rejected_summary=Полная причина отклонения:
editor.add_subdir=Добавить каталог…
editor.unable_to_upload_files=Не удалось загрузить файлы в «%s» из-за ошибки: %v
editor.upload_file_is_locked=Файл «%s» заблокирован %s.
@ -1545,27 +1545,27 @@ issues.add_labels=добавлены метки %s %s
issues.remove_label=удалил(а) метку %s %s
issues.remove_labels=удалил(а) метки %s %s
issues.add_remove_labels=добавлены метки %s и убраны метки %s %s
issues.add_milestone_at=`добавлено в этап <b>%s</b> %s`
issues.add_project_at=`добавлено в проект <b>%s</b> %s`
issues.change_milestone_at=`изменил(а) целевой этап с <b>%s</b> на <b>%s</b> %s`
issues.change_project_at=`изменил(а) проект с <b>%s</b> на <b>%s</b> %s`
issues.remove_milestone_at=`удалил(а) это из этапа <b>%s</b> %s`
issues.remove_project_at=`удалил(а) это из проекта <b>%s</b> %s`
issues.add_milestone_at=`добавление в этап <b>%s</b> %s`
issues.add_project_at=`добавление в проект <b>%s</b> %s`
issues.change_milestone_at=`этап изменён с <b>%s</b> на <b>%s</b> %s`
issues.change_project_at=`проект изменён с <b>%s</b> на <b>%s</b> %s`
issues.remove_milestone_at=`удаление из этапа <b>%s</b> %s`
issues.remove_project_at=`удаление из проекта <b>%s</b> %s`
issues.deleted_milestone=`(удалено)`
issues.deleted_project=`(удалено)`
issues.self_assign_at=`назначил(а) на себя %s`
issues.add_assignee_at=`был(а) назначен(а) <b>%s</b> %s`
issues.remove_assignee_at=`был снят с назначения <b>%s</b> %s`
issues.remove_self_assignment=`убрал(а) их назначение %s`
issues.change_title_at=`изменил(а) заголовок с <b><strike>%s</strike></b> на <b>%s</b> %s`
issues.change_ref_at=`изменил(а) ссылку с <b><strike>%s</strike></b> на <b>%s</b> %s`
issues.remove_ref_at=`убрал(а) ссылку <b>%s</b> %s`
issues.add_ref_at=`добавлена ссылка <b>%s</b> %s`
issues.self_assign_at=`назначение себя %s`
issues.add_assignee_at=`назначение <b>%s</b> %s`
issues.remove_assignee_at=`снятие с назначения <b>%s</b> %s`
issues.remove_self_assignment=`снято назначение с себя %s`
issues.change_title_at=`заголовок изменён с <b><strike>%s</strike></b> на <b>%s</b> %s`
issues.change_ref_at=`изменена ссылка с <b><strike>%s</strike></b> на <b>%s</b> %s`
issues.remove_ref_at=`убрана ссылка на <b>%s</b> %s`
issues.add_ref_at=`добавлена ссылка на <b>%s</b> %s`
issues.delete_branch_at=`удалена ветвь <b>%s</b> %s`
issues.filter_label=Метка
issues.filter_label_exclude=`Используйте <code>alt</code> + <code>click/enter</code>, чтобы исключить метки`
issues.filter_label_no_select=Все метки
issues.filter_label_select_no_label=Нет метки
issues.filter_label=Метки
issues.filter_label_exclude=`Исключайте метки с помощью <code>alt</code> + <code>лкм/enter</code>`
issues.filter_label_no_select=Любые метки
issues.filter_label_select_no_label=Без меток
issues.filter_milestone=Этап
issues.filter_milestone_all=Все этапы
issues.filter_milestone_none=Нет этапов
@ -2923,6 +2923,7 @@ settings.event_action_recover = Восстановлен
settings.event_action_recover_desc = После неудачи повторное выполнение рабочего потока было успешно.
settings.event_action_success = Успех
settings.event_action_success_desc = Выполнение завершилось успешно.
issues.filter_type.all_pull_requests = Все запросы на слияние
[graphs]
component_loading_failed = Не удалось загрузить %s

View file

@ -201,6 +201,9 @@ table_modal.placeholder.content = Innehåll
table_modal.label.rows = Rader
table_modal.label.columns = Kolumner
buttons.switch_to_legacy.tooltip = Använd legacy-redigeraren istället
link_modal.url = Url
link_modal.description = Beskrivning
link_modal.header = Lägg till en länk
[filter]
string.asc = A - Ö
@ -329,6 +332,7 @@ invalid_app_data_path = Sökvägen för appdata är ogiltig: %v
internal_token_failed = Misslyckades att generera intern token: %v
password_algorithm = Hashalgoritm för lösenord
invalid_password_algorithm = Ogiltig hashalgoritm för lösenord
env_config_keys_prompt = Följande miljövariabler kommer också att tillämpas på din konfigurationsfil:
[home]
uname_holder=Användarnamn eller e-postadress
@ -462,6 +466,41 @@ reply = eller svara på detta e-postmeddelande direkt
hi_user_x = Hej <b>%s</b>,
admin.new_user.user_info = Användarinformation
admin.new_user.text = Vänligen <a href="%s">klicka här</a> för att hantera denna användare från administratörspanelen.
admin.new_user.subject = Ny användare %s har just registrerat sig
totp_disabled.no_2fa = Det finns inga andra 2FA-metoder konfigurerade längre, vilket innebär att det inte längre är nödvändigt att logga in på ditt konto med 2FA.
removed_security_key.text_1 = Säkerhetsnyckeln ”%[1]s” har just tagits bort från ditt konto.
repo.transfer.to_you = dig
repo.transfer.body = För att acceptera eller avvisa det, besök %s eller ignorera det helt enkelt.
removed_security_key.no_2fa = Det finns inga andra 2FA-metoder konfigurerade längre, vilket innebär att det inte längre är nödvändigt att logga in på ditt konto med 2FA.
release.note = Notera:
totp_enrolled.subject = Du har aktiverat TOTP som 2FA-metod
totp_enrolled.text_1.no_webauthn = Du har just aktiverat TOTP för ditt konto. Det innebär att du måste använda TOTP som 2FA-metod vid alla framtida inloggningar på ditt konto.
totp_enrolled.text_1.has_webauthn = Du har just aktiverat TOTP för ditt konto. Det innebär att du vid alla framtida inloggningar på ditt konto kan använda TOTP som 2FA-metod eller någon av dina säkerhetsnycklar.
link_not_working_do_paste = Fungerar inte länken? Prova att kopiera och klistra in den i webbläsarens adressfält.
primary_mail_change.text_1 = Den primära e-postadressen för ditt konto har just ändrats till %[1]s. Det innebär att denna e-postadress inte längre kommer att ta emot e-postmeddelanden för ditt konto.
totp_disabled.subject = TOTP har inaktiverats
totp_disabled.text_1 = Tidsbaserat engångslösenord (TOTP) på ditt konto har just inaktiverats.
account_security_caution.text_2 = Om detta inte var du, har ditt konto blivit kompromitterat. Kontakta administratören för denna webbplats.
account_security_caution.text_1 = Om detta var du, kan du tryggt ignorera detta meddelande.
activate_account.text_2 = Klicka på följande länk för att aktivera ditt konto inom <b>%s</b>:
activate_email.text = Klicka på följande länk för att verifiera din e-postadress inom <b>%s</b>:
register_notify.text_3 = Om någon annan har skapat det här kontot åt dig måste du först <a href="%s">ställa in ditt lösenord</a>.
issue.x_mentioned_you = <b>@%s2</b> nämnde dig:
repo.collaborator.added.subject = %s har lagt till dig som medarbetare i %s
repo.collaborator.added.text = Du har lagts till som medarbetare i förrådet:
team_invite.subject = %[1]s har bjudit in dig att gå med i organisationen %[2]s
register_notify.text_1 = detta är din registreringsbekräftelse via e-post för %s!
release.downloads = Hämtningar:
release.download.zip = Källkod (ZIP)
release.download.targz = Källkod (TAR.GZ)
repo.transfer.subject_to = %s vill överföra förrådet ”%s” till %s
removed_security_key.subject = En säkerhetsnyckel har tagits bort
issue_assigned.pull = @%[1] har tilldelat dig pull-begäran %[2]s i förrådet %[3]s.
issue_assigned.issue = @%[1] har tilldelat dig ärendet %[2] i förrådet %[3].
register_notify.text_2 = Du kan logga in på ditt konto med ditt användarnamn: %s
reset_password.text = Om detta var du, klicka på följande länk för att återställa ditt konto inom <b>%s</b>:
issue.action.force_push = <b>%[1]s2</b> gjorde en force-push av <b>%[2]s</b> från %[3]s till %[4]s.
repo.transfer.subject_to_you = %s vill överföra förrådet ”%s” till dig
@ -545,6 +584,12 @@ auth_failed=Autentisering misslyckades: %v
target_branch_not_exist=Målgrenen finns inte.
org_still_own_repo = Denna organisation äger fortfarande ett eller flera förråd, ta bort eller överför dem först.
must_use_public_key = Den nyckel du angav är en privat nyckel. Skicka inte upp din privata nyckel någonstans. Använd istället din publika nyckel.
unable_verify_ssh_key = SSH-nyckeln kan inte verifieras. Kontrollera att den är korrekt.
still_own_repo = Ditt konto äger ett eller flera förråd, ta bort eller överför dem först.
still_has_org = Ditt konto är medlem i en eller flera organisationer. Lämna dem först.
still_own_packages = Ditt konto har ett eller flera paket, ta bort dem först.
[user]
@ -560,6 +605,13 @@ follow=Följ
unfollow=Sluta följa
user_bio=Biografi
disabled_public_activity=Den här användaren har inaktiverat den publika synligheten av aktiviteten.
code = Kod
watched = Övervakade förråd
unblock = Avblockera
email_visibility.limited = Din e-postadress är synlig för alla autentiserade användare
show_on_map = Visa denna plats på en karta
settings = Användarinställningar
block = Blockera
[settings]
@ -760,6 +812,17 @@ email_notifications.submit=Ställ in e-postpreferenser
visibility.public=Offentlig
visibility.private=Privat
change_password = Byt lösenord
user_block_success = Användaren har blockerats.
blocked_since = Blockerad sedan %s
user_unblock_success = Användaren har blivit avblockerad.
visibility.limited = Begränsad
visibility.limited_tooltip = Synlig endast för inloggade användare
visibility.private_tooltip = Synlig endast för medlemmar i organisationer som du har gått med i
select_permissions = Välj behörigheter
permission_no_access = Ingen åtkomst
permission_write = Läs och skriv
user_block_yourself = Du kan inte blockera dig själv.
gpg_token_help = Du kan skapa en signatur med hjälp av:
[repo]
owner=Ägare
@ -1693,6 +1756,21 @@ topic.manage_topics=Hantera ämnen
topic.done=Klar
topic.count_prompt=Du kan inte välja fler än 25 ämnen
settings.enter_repo_name = Ange ägar- och utvecklingskatalog-namnet exakt som det visas:
release = Utgåva
commitstatus.success = Lyckades
visibility_helper = Gör förrådet privat
download_bundle = Hämta BUNDLE
download_zip = Hämta ZIP
download_tar = Hämta TAR.GZ
repo_desc_helper = Ange kort beskrivning (valfritt)
all_branches = Alla grenar
fork_no_valid_owners = Detta förråd kan inte förgrenas eftersom det inte finns några giltiga ägare.
fork_to_different_account = Förgrena till ett annat konto
size_format = %[1]s: %[2]s, %[3]s: %[4]s
already_forked = Du har redan förgrenat %s
commitstatus.failure = Fel
ext_issues = Externa fel
open_with_editor = Öppna med %s
@ -2255,7 +2333,7 @@ project_kind = Sök projekt...
search = Sök…
type_tooltip = Söktyp
team_kind = Sök lag...
org_kind = Sök organisationer...
org_kind = Sök organisationer
issue_kind = Sök ärenden...
regexp_tooltip = Tolka söktermen som ett reguljärt uttryck
code_search_unavailable = Kodsökning är för närvarande inte tillgänglig. Vänligen kontakta webbplatsadministratören.

View file

@ -668,6 +668,7 @@ username_error_no_dots = ` sadece alfanumerik karakterler ("0-9","a-z","A-Z"), t
unset_password = Oturum açma kullanıcısı parola belirlemedi.
unsupported_login_type = Oturum açma türü hesap silmeyi desteklemiyor.
email_domain_is_not_allowed = Kullanıcı e-posta adresi <b>%s</b> alan adı EMAIL_DOMAIN_ALLOWLIST veya EMAIL_DOMAIN_BLOCKLIST ile çelişiyor. Lütfen işleminizin beklendiğinden emin olun.
[user]
change_avatar=Profil resmini değiştir…
@ -2163,7 +2164,7 @@ settings.pulls.allow_rebase_update=Değişiklik isteği dalının yeniden yapıl
settings.pulls.default_delete_branch_after_merge=Varsayılan olarak birleştirmeden sonra değişiklik isteği dalını sil
settings.pulls.default_allow_edits_from_maintainers=Bakımcıların düzenlemelerine izin ver
settings.releases_desc=Depo Sürümlerini Etkinleştir
settings.packages_desc=Depo Paket Kütüğünü Etkinleştir
settings.packages_desc=Depo paket kütüğünü etkinleştir
settings.projects_desc=Depo Projelerini Etkinleştir
settings.actions_desc=Depo İşlemlerini Etkinleştir
settings.admin_settings=Yönetici Ayarları
@ -3492,7 +3493,7 @@ error.unit_not_allowed=Bu depo bölümüne erişme izniniz yok.
title=Paketler
desc=Depo paketlerini yönet.
empty=Henüz hiçbir paket yok.
empty.documentation=Paket kütüğü hakkında daha fazla bilgi için, <a target="_blank" rel="noopener noreferrer" href="%s">belgeye</a> bakabilirsiniz.
empty.documentation=Paket deposu hakkında daha fazla bilgi için, <a target="_blank" rel="noopener noreferrer" href="%s">belgeye</a> bakabilirsiniz.
empty.repo=Bir paket yüklediniz ama burada gösterilmiyor mu? <a href="%[1]s">Paket ayarları</a>na gidin ve bu depoya bağlantı verin.
registry.documentation=%s kütüğü hakkında daha fazla bilgi için, <a target="_blank" rel="noopener noreferrer" href="%s">belgeye</a> bakabilirsiniz.
filter.type=Tür
@ -3635,9 +3636,9 @@ owner.settings.cleanuprules.remove.days=Şundan eski sürümleri kaldır
owner.settings.cleanuprules.remove.pattern=Eşleşen sürümlari kaldır
owner.settings.cleanuprules.success.update=Temizleme kuralı güncellendi.
owner.settings.cleanuprules.success.delete=Temizleme kuralı silindi.
owner.settings.chef.title=Chef Kütüğü
owner.settings.chef.title=Chef deposu
owner.settings.chef.keypair=Anahtar çifti üret
owner.settings.chef.keypair.description=Chef kütüğünde kimlik doğrulaması için bir anahtar çifti gereklidir. Eğer daha önce bir anahtar çifti ürettiyseniz, yeni bir anahtar çifti üretmek eski anahtar çiftini ıskartaya çıkartacaktır.
owner.settings.chef.keypair.description=Chef kayıt defterine gönderilen istekler kimlik doğrulama yöntemi olarak kriptografik olarak imzalanmalıdır. Bir anahtar çifti oluştururken, yalnızca genel anahtar Forgejo'da saklanır. Özel anahtar size knife ile kullanılmak üzere sağlanır. Yeni bir anahtar çifti oluşturmak öncekini geçersiz kılar.
npm.dependencies.bundle = Paketlenmiş Bağımlılıklar
rpm.repository.multiple_groups = Bu paket birçok grupta mevcut.

View file

@ -2668,6 +2668,7 @@ settings.event_action_success = Успіх
settings.event_action_recover = Відновлено
commitstatus.success = Успіх
commitstatus.failure = Збій
issues.filter_type.all_pull_requests = Усі запити на злиття
[graphs]
contributors.what = внески
@ -3231,7 +3232,7 @@ notices.view_detail_header=Переглянути деталі повідомл
notices.select_all=Вибрати все
notices.deselect_all=Скасувати виділення
notices.inverse_selection=Інвертувати виділене
notices.delete_selected=Видалити обране
notices.delete_selected=Видалити вибране
notices.delete_all=Видалити всі cповіщення
notices.type=Тип
notices.type_1=Репозиторій

View file

@ -9,7 +9,7 @@ sign_up = Đăng ký
link_account = Liên kết tài khoản
register = Đăng ký
version = Phiên bản
powered_by = Sử dụng %s
powered_by = Được cung cấp bởi %s
page = Trang
template = Mẫu
language = Ngôn ngữ
@ -25,7 +25,7 @@ access_token = Mã truy cập
captcha = CAPTCHA
twofa = Xác thực hai lớp
webauthn_insert_key = Cắm khóa bảo mật của bạn vào
copy_hash = Chép chuỗi băm
copy_hash = Sao chép chuỗi băm
sign_in_with_provider = Đăng nhập bằng %s
webauthn_press_button = Hãy nhấn nút trên khóa bảo mật…
webauthn_use_twofa = Dùng mã xác thực hai lớp ở trên điện thoại
@ -36,7 +36,7 @@ webauthn_error_insecure = WebAuthn chỉ hỗ trợ kết nối mã hóa. Nếu
webauthn_error_unable_to_process = Máy chủ không thể xử lý yêu cầu của bạn.
webauthn_error_empty = Bạn phải đặt tên cho khóa này.
webauthn_error_timeout = Hết thời gian đọc khóa mất rồi. Hãy tải lại trang và thử lại.
copy_type_unsupported = Không chép được
copy_type_unsupported = Không thể sao chép loại tệp này
repository = Kho mã
organization = Tổ chức
new_fork = Tạo một nhánh mới
@ -55,17 +55,17 @@ all = Tất cả
sources = Nguồn
forks = Các phân nhánh
activities = Hoạt động
pull_requests = Yêu cầu thêm
pull_requests = Yêu cầu kéo
save = Lưu
issues =
issues =Vấn đề
enabled = Bật
disabled = Tắt
copy = Chép
copy_generic = Chép vào bộ nhớ tạm
copy_url = Chép URL
copy_content = Chép nội dung
copy_success = Đã chép!
copy_error = Không chép được
copy = Sao chép
copy_generic = Sao chép vào bộ nhớ tạm
copy_url = Sao chép URL
copy_content = Sao chép nội dung
copy_success = Đã sao chép!
copy_error = Sao chép thất bại
write = Viết
preview = Xem trước
error = Lỗi
@ -73,7 +73,7 @@ error413 = Bạn đã dùng hết định mức.
go_back = Quay lại
invalid_data = Dữ liệu không hợp lệ: %v
never = Không bao giờ
unknown = Không biết
unknown = Không xác định
unpin = Bỏ ghim
pin = Ghim
archived = Đã lưu trữ
@ -81,6 +81,60 @@ signed_in_as = Đăng nhập bằng
re_type = Xác nhận mật khẩu
webauthn_sign_in = Nhấn nút trên khóa bảo mật, nếu không có nút thì bạn hãy rút ra rồi cắm lại.
new_org.link = Tạo tổ chức
error404 = Trang bạn đang tìm <strong>không tồn tại</strong> hoặc <strong>bạn không có quyền xem</strong>.
error404 = Trang bạn đang tìm <strong>không tồn tại</strong>, <strong>đã bị xoá</strong> hoặc <strong>bạn không có quyền</strong> để xem nó.
edit = Chỉnh sửa
filter = Lọc
filter = Bộ lọc
dashboard = Trang quản lý
logo = Logo
toc = Mục lục
user_profile_and_more = Hồ sơ và cài đặt…
passcode = Mã xác thực
webauthn_error_duplicated = Khóa bảo mật không được phép cho yêu cầu này. Vui lòng đảm bảo rằng khóa chưa được đăng ký trước đó.
mirror = Bản sao
new_mirror = Tạo bản sao mới
your_starred = Đã đánh sao
mirrors = Các bản sao
concept_system_global = Chung
concept_user_individual = Cá nhân
show_log_seconds = Hiện giây
show_full_screen = Toàn màn hình
download_logs = Tải xuống nhật ký
confirm_delete_selected = Xác nhận xoá tất cả mục được chọn?
name = Tên
filter.clear = Xoá bộ lọc
filter.not_fork = Không phải phân nhánh
filter.not_archived = Không bị lưu trữ
filter.is_archived = Bị lưu trữ
filter.is_fork = Phân nhánh
filter.is_mirror = Bản sao
filter.is_template = Mẫu
filter.not_template = Không phải mẫu
filter.public = Công khai
filter.private = Riêng tư
twofa_scratch = Mã xác thực 2 lớp dự phòng
collaborative = Cộng tác
milestones = Cột mốc
cancel = Huỷ bỏ
retry = Thử lại
rerun = Chạy lại
rerun_all = Chạy lại tất cả
ok = Đồng ý
add = Thêm
add_all = Thêm tất cả
remove = Xoá
remove_all = Xoá tất cả
remove_label_str = Xoá "%s"
locked = Bị khoá
copy_branch = Sao chép tên nhánh
loading = Đang tải…
rss_feed = Nguồn RSS
confirm_delete_artifact = Bạn có chắc muốn xoá "%s" ?
value = Giá trị
copy_path = Sao chép đường dẫn
filter.not_mirror = Không phải bản sao
show_timestamps = Hiện mốc thời gian
concept_code_repository = Kho mã
concept_user_organization = Tổ chức
[search]
search = Tìm kiếm…

View file

@ -102,5 +102,8 @@
"editor.textarea.tab_hint": "Řádek je již odsazen. Pro opuštění editoru stiskněte znovu <kbd>Tab</kbd> nebo <kbd>Escape</kbd>.",
"editor.textarea.shift_tab_hint": "Na tomto řádku není žádné odsazení. Pro opuštění editoru stiskněte znovu <kbd>Shift</kbd> + <kbd>Tab</kbd> nebo <kbd>Escape</kbd>.",
"admin.dashboard.cleanup_offline_runners": "Vymazat offline runnery",
"settings.visibility.description": "Viditelnost profilu ovlivňuje možnost ostatních přistupovat k vašim veřejným repozitářům. <a href=\"%s\" target=\"_blank\">Zjistit více</a>"
"settings.visibility.description": "Viditelnost profilu ovlivňuje možnost ostatních přistupovat k vašim veřejným repozitářům. <a href=\"%s\" target=\"_blank\">Zjistit více</a>",
"avatar.constraints_hint": "Velikost vlastního avataru nesmí překročit %[1]s nebo být větší než %[2]dx%[3]d pixelů",
"repo.diff.commit.next-short": "Další",
"repo.diff.commit.previous-short": "Předchozí"
}

View file

@ -92,5 +92,10 @@
"followers.outgoing.list.self.none": "Du følger ikke nogen.",
"followers.outgoing.list.none": "%s følger ikke nogen.",
"editor.textarea.tab_hint": "Linjen er allerede indrykket. Tryk på <kbd>Tab</kbd> igen eller <kbd>Escape</kbd> for at forlade editoren.",
"editor.textarea.shift_tab_hint": "Ingen indrykning på denne linje. Tryk på <kbd>Shift</kbd> + <kbd>Tab</kbd> igen eller <kbd>Escape</kbd> for at forlade editoren."
"editor.textarea.shift_tab_hint": "Ingen indrykning på denne linje. Tryk på <kbd>Shift</kbd> + <kbd>Tab</kbd> igen eller <kbd>Escape</kbd> for at forlade editoren.",
"admin.dashboard.cleanup_offline_runners": "Ryd op offline runners",
"settings.visibility.description": "Profilsynlighed påvirker andres adgang til dine ikke-private depoter. <a href=\"%s\" target=\"_blank\">Læs mere</a>",
"avatar.constraints_hint": "Brugerdefineret avatar må ikke overstige %[1]s i størrelse eller være større end %[2]dx%[3]d pixels",
"repo.diff.commit.next-short": "Næste",
"repo.diff.commit.previous-short": "Forrige"
}

View file

@ -93,5 +93,9 @@
"followers.incoming.list.none": "Niemand folgt diesem Benutzer.",
"editor.textarea.tab_hint": "Zeile bereits eingerückt. Drücke nochmals <kbd>Tab</kbd> oder <kbd>Escape</kbd> um den Editor zu verlassen.",
"editor.textarea.shift_tab_hint": "Keine Einrückung auf dieser Zeile. Drücke nochmals <kbd>Shift</kbd> + <kbd>Tab</kbd> oder <kbd>Escape</kbd> um den Editor zu verlassen.",
"admin.dashboard.cleanup_offline_runners": "Aufräumen der offline Runner"
"admin.dashboard.cleanup_offline_runners": "Aufräumen der offline Runner",
"settings.visibility.description": "Die Profilsichtbarkeit beeinflusst die Möglichkeit anderer, auf deine nicht-privaten Repositorys zuzugreifen. <a href=\"%s\" target=\"_blank\">Erfahre mehr</a>",
"avatar.constraints_hint": "Individuelles Profilbild darf %[1]s in der Größe nicht überschreiten, und nicht größer als %[2]dx%[3]d Pixel sein",
"repo.diff.commit.next-short": "Nächste",
"repo.diff.commit.previous-short": "Vorherige"
}

View file

@ -89,6 +89,8 @@
"mail.actions.run_info_previous_status": "Previous Run's Status: %[1]s",
"mail.actions.run_info_ref": "Branch: %[1]s (%[2]s)",
"mail.actions.run_info_trigger": "Triggered because: %[1]s by: %[2]s",
"repo.diff.commit.next-short": "Next",
"repo.diff.commit.previous-short": "Prev",
"discussion.locked": "This discussion has been locked. Commenting is limited to contributors.",
"editor.textarea.tab_hint": "Line already indented. Press <kbd>Tab</kbd> again or <kbd>Escape</kbd> to leave the editor.",
"editor.textarea.shift_tab_hint": "No indentation on this line. Press <kbd>Shift</kbd> + <kbd>Tab</kbd> again or <kbd>Escape</kbd> to leave the editor.",

View file

@ -97,5 +97,11 @@
"repo.issue_indexer.title": "Indexeur de problèmes",
"stars.list.none": "Personne n'a mis d'étoiles sur ce dépôt.",
"watch.list.none": "Personne ne consulte ce dépôt.",
"repo.form.cannot_create": "Tous les espaces dans lesquels vous pouvez créer des dépôts ont atteint la limite de dépôts."
"repo.form.cannot_create": "Tous les espaces dans lesquels vous pouvez créer des dépôts ont atteint la limite de dépôts.",
"admin.dashboard.cleanup_offline_runners": "Nettoyer les exécuteurs hors ligne",
"mail.actions.run_info_trigger": "Déclenché parce que : %[1]s par : %[2]s",
"settings.visibility.description": "La visibilité du profil affecte la capacité des autres à accéder à vos dépôts non-privés. <a href=\"%s\" target=\"_blank\">Voir plus</a>",
"editor.textarea.shift_tab_hint": "Pas d'indentation sur cette ligne. Appuyez sur <kbd>Maj</kbd> + <kbd>Tab</kbd> une nouvelle fois ou sur <kbd>Échap</kbd> pour quitter l'éditeur.",
"avatar.constraints_hint": "L'avatar personnalisé ne doit pas dépasser une taille de %[1]s ou être plus grand que %[2]dx%[3]d pixels",
"editor.textarea.tab_hint": "Ligne déjà indentée. Appuyez sur <kbd>Tab</kbd> une nouvelle fois ou sur <kbd>Échap</kbd> pour quitter l'éditeur."
}

View file

@ -102,5 +102,8 @@
"editor.textarea.tab_hint": "Rinda jau ir ar atkāpi. Spied <kbd>Tab</kbd> vēlreiz vai <kbd>Escape</kbd>, lai izietu no redaktora!",
"editor.textarea.shift_tab_hint": "Šajā rindā nav atkāpes. Spied <kbd>Shift</kbd> + <kbd>Tab</kbd> vēlreiz vai <kbd>Escape</kbd>, lai izietu no redaktora!",
"admin.dashboard.cleanup_offline_runners": "Notīrīt bezsaistes izpildītājus",
"settings.visibility.description": "Profila redzamība ietekmē iespēju citiem piekļūt Tavām glabātavām, kas nav privātas. <a href=\"%s\" target=\"_blank\">Uzzināt vairāk</a>"
"settings.visibility.description": "Profila redzamība ietekmē iespēju citiem piekļūt Tavām glabātavām, kas nav privātas. <a href=\"%s\" target=\"_blank\">Uzzināt vairāk</a>",
"avatar.constraints_hint": "Pielāgots profila attēls nevar pārsniegt %[1]s vai būt lielāks par %[2]dx%[3]d pikseļiem",
"repo.diff.commit.next-short": "Nāk.",
"repo.diff.commit.previous-short": "Iepr."
}

View file

@ -94,5 +94,8 @@
"editor.textarea.tab_hint": "Rieg al inschuven. Drück weer <kbd>Tab</kbd> of <kbd>Esc</kbd>, um de Bewarker to verlaten.",
"editor.textarea.shift_tab_hint": "Keen Inschuuv in deeser Rieg. Drück weer <kbd>Umschalt</kbd>+<kbd>Tab</kbd> of <kbd>Esc</kbd>, um de Bewarker to verlaten.",
"admin.dashboard.cleanup_offline_runners": "Nich verbunnen Lopers uprümen",
"settings.visibility.description": "De Profil-Sichtbaarkeid maakt daar wat an, of un wo anner Lüü diene nich-privaaten Repositoriums ankieken könen. <a href=\"%s\" target=\"_blank\">Mehr unnerhören</a>"
"settings.visibility.description": "De Profil-Sichtbaarkeid maakt daar wat an, of un wo anner Lüü diene nich-privaaten Repositoriums ankieken könen. <a href=\"%s\" target=\"_blank\">Mehr unnerhören</a>",
"avatar.constraints_hint": "Dat eegene Kontobill düür nich groter as %[1]s wesen of groter as %[2]d×%[3]d Billtüttels wesen",
"repo.diff.commit.next-short": "Anner",
"repo.diff.commit.previous-short": "Vörig"
}

View file

@ -100,5 +100,8 @@
"stars.list.none": "Ninguém favoritou este repositório.",
"followers.outgoing.list.self.none": "Você não está seguindo ninguém.",
"editor.textarea.tab_hint": "Linha já indentada. Pressione <kbd>Tab</kbd> novamente ou <kbd>Esc</kbd> para sair do editor.",
"editor.textarea.shift_tab_hint": "Sem indentação nesta linha. Pressione <kbd>Shift</kbd> + <kbd>Tab</kbd> novamente ou <kbd>Esc</kbd> para sair do editor."
"editor.textarea.shift_tab_hint": "Sem indentação nesta linha. Pressione <kbd>Shift</kbd> + <kbd>Tab</kbd> novamente ou <kbd>Esc</kbd> para sair do editor.",
"admin.dashboard.cleanup_offline_runners": "Limpar runners desconectados",
"avatar.constraints_hint": "Imagem de perfil personalizada não pode exceder %[1]s em tamanho ou ser maior que %[2]dx%[3]d pixels",
"settings.visibility.description": "A visibilidade do perfil afeta a habilidade de acessarem seus repositórios não-privados. <a href=\"%s\" target=\"_blank\">Saiba mais</a>"
}

View file

@ -101,5 +101,7 @@
"editor.textarea.tab_hint": "Linha já indentada. Pressione <kbd>Tab</kbd> novamente ou <kbd>Escape</kbd> para sair do editor.",
"editor.textarea.shift_tab_hint": "Sem indentação nesta linha. Pressione <kbd>Shift</kbd> + <kbd>Tab</kbd> novamente ou <kbd>Escape</kbd> para sair do editor.",
"stars.list.none": "Ninguém juntou este repositório aos favoritos.",
"admin.dashboard.cleanup_offline_runners": "Limpeza de executores offline"
"admin.dashboard.cleanup_offline_runners": "Limpeza de executores offline",
"settings.visibility.description": "A visibilidade do perfil afecta a capacidade de outros acederem aos seus repositórios não privados. <a href=\"%s\" target=\"_blank\">Ler mais</a>",
"avatar.constraints_hint": "O avatar personalizado não pode exceder %[1]s de tamanho ou ser maior do que %[2]dx%[3]d pixéis"
}

View file

@ -101,5 +101,9 @@
"moderation.report_abuse_form.header": "Жалоба администрации",
"editor.textarea.tab_hint": "Отступ уже добавлен. Нажмите <kbd>Tab</kbd> снова или <kbd>Escape</kbd>, чтобы покинуть редактор.",
"editor.textarea.shift_tab_hint": "В строке нет отступов. Нажмите <kbd>Shift</kbd> + <kbd>Tab</kbd> снова или <kbd>Escape</kbd>, чтобы покинуть редактор.",
"admin.dashboard.cleanup_offline_runners": "Удалить недоступных исполнителей"
"admin.dashboard.cleanup_offline_runners": "Удалить недоступных исполнителей",
"avatar.constraints_hint": "Изображение профиля не может быть более %[1]s и крупнее %[2]dx%[3]d пикселей",
"settings.visibility.description": "Видимость профиля влияет на доступ других до ваших не частных репозиториев. <a href=\"%s\" target=\"_blank\">Подробнее</a>",
"repo.diff.commit.previous-short": "Предыдущий",
"repo.diff.commit.next-short": "Далее"
}

View file

@ -56,7 +56,7 @@
"mail.actions.successful_run_after_failure": "Arbetsflöde %[1]s återställdes i förrådet %[2]s",
"mail.actions.not_successful_run": "Arbetsflödet %[1]s misslyckades i förrådet %[2]s",
"editor.textarea.shift_tab_hint": "Ingen indragning på den här raden. Tryck på <kbd>Shift</kbd> + <kbd>Tab</kbd> igen eller <kbd>Escape</kbd> för att lämna redigeringsläget.",
"meta.last_line": "Tack för att du översatte Forgejo! Daniel Nylander heter jag. https://www.danielnylander.se",
"meta.last_line": "Daniel Nylander heter jag och har översatt Forgejo. Mer information om mig på https://www.danielnylander.se",
"editor.textarea.tab_hint": "Raden är redan indragen. Tryck på <kbd>Tab</kbd> igen eller <kbd>Escape</kbd> för att lämna redigeringsläget.",
"home.welcome.no_activity": "Ingen aktivitet",
"home.welcome.activity_hint": "Det finns inget i ditt flöde ännu. Dina åtgärder och aktivitet från förråd som du bevakar kommer att visas här.",
@ -93,5 +93,7 @@
"moderation.abuse_category": "Kategori",
"moderation.abuse_category.placeholder": "Välj en kategori",
"moderation.abuse_category.spam": "Skräppost",
"moderation.abuse_category.malware": "Skadlig kod"
"moderation.abuse_category.malware": "Skadlig kod",
"settings.visibility.description": "Profilens synlighet påverkar andras möjlighet att komma åt dina icke-privata förråd. <a href=\"%s\" target=\"_blank\">Läs mer</a>",
"avatar.constraints_hint": "Anpassade avatarer får inte vara större än %[1] eller %[2]dx%[3] bildpunkter"
}

View file

@ -88,10 +88,10 @@
"moderation.report_abuse_form.details": "Використовуйте цю форму, щоб повідомити про користувачів, які створюють спам-профілі, репозиторії, задачі, коментарі або поводяться неналежним чином.",
"moderation.abuse_category": "Категорія",
"moderation.abuse_category.other_violations": "Інші порушення правил платформи",
"moderation.reporting_failed": "Не вдалося надіслати нове повідомлення про порушення: %v",
"moderation.reporting_failed": "Не вдалося надіслати повідомлення про порушення: %v",
"moderation.report_remarks": "Подробиці",
"moderation.reported_thank_you": "Дякуємо за ваше повідомлення. Адміністрацію поінформовано про нього.",
"repo.form.cannot_create": "Усі простори, в яких ви можете створювати репозиторії, досягли максимальної кількості репозиторіїв.",
"repo.form.cannot_create": "У всіх просторах, де ви можете створювати репозиторії, досягнуто обмеження кількості репозиторіїв.",
"repo.issue_indexer.title": "Індексатор задач",
"followers.incoming.list.self.none": "Ніхто не стежить за вашим профілем.",
"followers.incoming.list.none": "Ніхто не стежить за цим користувачем.",
@ -102,5 +102,8 @@
"editor.textarea.tab_hint": "У рядку вже є відступ. Натисніть <kbd>Tab</kbd> ще раз або <kbd>Esc</kbd>, щоб вийти з редактора.",
"editor.textarea.shift_tab_hint": "У цьому рядку немає відступів. Натисніть <kbd>Shift</kbd> + <kbd>Tab</kbd> ще раз або <kbd>Esc</kbd>, щоб вийти з редактора.",
"admin.dashboard.cleanup_offline_runners": "Очистити неактивні раннери",
"settings.visibility.description": "Видимість профілю впливає на можливість інших користувачів отримати доступ до ваших неприватних репозиторіїв. <a href=\"%s\" target=\"_blank\">Дізнатися більше</a>"
"settings.visibility.description": "Видимість профілю впливає на можливість інших користувачів отримати доступ до ваших неприватних репозиторіїв. <a href=\"%s\" target=\"_blank\">Дізнатися більше</a>",
"avatar.constraints_hint": "Розмір користувацького аватара не може перевищувати %[1]s або бути більшим за %[2]d×%[3]d пікселів",
"repo.diff.commit.next-short": "Наступний",
"repo.diff.commit.previous-short": "Попередній"
}

View file

@ -69,5 +69,7 @@
"followers.incoming.list.self.none": "没有人关注你的个人资料。",
"editor.textarea.tab_hint": "此行已缩进。再次按 <kbd>Tab</kbd> 或按 <kbd>Escape</kbd> 退出编辑器。",
"editor.textarea.shift_tab_hint": "此行无缩进。再次按 <kbd>Shift</kbd> + <kbd>Tab</kbd> 或按 <kbd>Escape</kbd> 退出编辑器。",
"admin.dashboard.cleanup_offline_runners": "清理离线运行器"
"admin.dashboard.cleanup_offline_runners": "清理离线运行器",
"settings.visibility.description": "个人资料可见性设置会影响他人对您的非私有仓库的访问。<a href=\"%s\" target=\"_blank\">了解更多</a>",
"avatar.constraints_hint": "自定义头像大小不得超过 %[1]s或大于 %[2]d×%[3]d 像素"
}

190
package-lock.json generated
View file

@ -12,11 +12,12 @@
"@github/markdown-toolbar-element": "2.2.3",
"@github/quote-selection": "2.1.0",
"@github/text-expander-element": "2.8.0",
"@google/model-viewer": "4.1.0",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@primer/octicons": "19.14.0",
"ansi_up": "6.0.5",
"asciinema-player": "3.8.2",
"chart.js": "4.4.9",
"chart.js": "4.5.0",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.2.0",
"clippie": "4.1.7",
@ -62,9 +63,9 @@
"devDependencies": {
"@axe-core/playwright": "4.10.2",
"@eslint-community/eslint-plugin-eslint-comments": "4.5.0",
"@playwright/test": "1.53.0",
"@playwright/test": "1.52.0",
"@stoplight/spectral-cli": "6.15.0",
"@stylistic/eslint-plugin-js": "4.4.1",
"@stylistic/eslint-plugin": "4.4.1",
"@stylistic/stylelint-plugin": "3.1.2",
"@vitejs/plugin-vue": "5.2.4",
"@vitest/coverage-v8": "3.2.3",
@ -84,7 +85,7 @@
"eslint-plugin-vitest-globals": "1.5.0",
"eslint-plugin-vue": "10.2.0",
"eslint-plugin-vue-scoped-css": "2.10.0",
"eslint-plugin-wc": "2.2.1",
"eslint-plugin-wc": "3.0.1",
"globals": "16.1.0",
"happy-dom": "18.0.0",
"license-checker-rseidelsohn": "4.4.2",
@ -1222,6 +1223,22 @@
"dom-input-range": "^1.2.0"
}
},
"node_modules/@google/model-viewer": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@google/model-viewer/-/model-viewer-4.1.0.tgz",
"integrity": "sha512-7WB/jS6wfBfRl/tWhsUUvDMKFE1KlKME97coDLlZQfvJD0nCwjhES1lJ+k7wnmf7T3zMvCfn9mIjM/mgZapuig==",
"license": "Apache-2.0",
"dependencies": {
"@monogrid/gainmap-js": "^3.1.0",
"lit": "^3.2.1"
},
"engines": {
"node": ">=6.0.0"
},
"peerDependencies": {
"three": "^0.172.0"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@ -2004,6 +2021,21 @@
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT"
},
"node_modules/@lit-labs/ssr-dom-shim": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz",
"integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==",
"license": "BSD-3-Clause"
},
"node_modules/@lit/reactive-element": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.0.tgz",
"integrity": "sha512-L2qyoZSQClcBmq0qajBVbhYEcG6iK0XfLn66ifLe/RfC0/ihpc+pl0Wdn8bJ8o+hj38cG0fGXRgSS20MuXn7qA==",
"license": "BSD-3-Clause",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.2.0"
}
},
"node_modules/@mcaptcha/core-glue": {
"version": "0.1.0-alpha-5",
"resolved": "https://registry.npmjs.org/@mcaptcha/core-glue/-/core-glue-0.1.0-alpha-5.tgz",
@ -2064,6 +2096,18 @@
"langium": "3.3.1"
}
},
"node_modules/@monogrid/gainmap-js": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.1.0.tgz",
"integrity": "sha512-Obb0/gEd/HReTlg8ttaYk+0m62gQJmCblMOjHSMHRrBP2zdfKMHLCRbh/6ex9fSUJMKdjjIEiohwkbGD3wj2Nw==",
"license": "MIT",
"dependencies": {
"promise-worker-transferable": "^1.0.4"
},
"peerDependencies": {
"three": ">= 0.159.0"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz",
@ -2143,13 +2187,13 @@
}
},
"node_modules/@playwright/test": {
"version": "1.53.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.0.tgz",
"integrity": "sha512-15hjKreZDcp7t6TL/7jkAo6Df5STZN09jGiv5dbP9A6vMVncXRqE7/B2SncsyOwrkZRBH2i6/TPOL8BVmm3c7w==",
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz",
"integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.53.0"
"playwright": "1.52.0"
},
"bin": {
"playwright": "cli.js"
@ -3011,15 +3055,18 @@
"node": "^12.20 || >=14.13"
}
},
"node_modules/@stylistic/eslint-plugin-js": {
"node_modules/@stylistic/eslint-plugin": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-4.4.1.tgz",
"integrity": "sha512-eLisyHvx7Sel8vcFZOEwDEBGmYsYM1SqDn81BWgmbqEXfXRf8oe6Rwp+ryM/8odNjlxtaaxp0Ihmt86CnLAxKg==",
"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.4.1.tgz",
"integrity": "sha512-CEigAk7eOLyHvdgmpZsKFwtiqS2wFwI1fn4j09IU9GmD4euFM4jEBAViWeCqaNLlbX2k2+A/Fq9cje4HQBXuJQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/utils": "^8.32.1",
"eslint-visitor-keys": "^4.2.0",
"espree": "^10.3.0"
"espree": "^10.3.0",
"estraverse": "^5.3.0",
"picomatch": "^4.0.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -3028,6 +3075,19 @@
"eslint": ">=9.0.0"
}
},
"node_modules/@stylistic/eslint-plugin/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@stylistic/stylelint-plugin": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.2.tgz",
@ -3477,8 +3537,7 @@
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
"license": "MIT"
},
"node_modules/@types/unist": {
"version": "2.0.11",
@ -5373,9 +5432,9 @@
}
},
"node_modules/chart.js": {
"version": "4.4.9",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz",
"integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==",
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
@ -7683,14 +7742,14 @@
}
},
"node_modules/eslint-plugin-wc": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-2.2.1.tgz",
"integrity": "sha512-KstLqGmyQz088DvFlDYHg0sHih+w2QeulreCi1D1ftr357klO2zqHdG/bbnNMmuQdVFDuNkopNIyNhmG0XCT/g==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-3.0.1.tgz",
"integrity": "sha512-0p1wkSlA2Ue3FA4qW+5LZ+15sy0p1nUyVl1eyBMLq4rtN1LtE9IdI49BXNWMz8N8bM/y7Ulx8SWGAni5f8XO5g==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-valid-element-name": "^1.0.0",
"js-levenshtein-esm": "^1.2.0"
"js-levenshtein-esm": "^2.0.0"
},
"peerDependencies": {
"eslint": ">=8.40.0"
@ -8752,6 +8811,12 @@
"node": ">= 4"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/immer": {
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
@ -9291,6 +9356,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/is-promise": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
"license": "MIT"
},
"node_modules/is-proto-prop": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/is-proto-prop/-/is-proto-prop-3.0.1.tgz",
@ -9692,9 +9763,9 @@
}
},
"node_modules/js-levenshtein-esm": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz",
"integrity": "sha512-fzreKVq1eD7eGcQr7MtRpQH94f8gIfhdrc7yeih38xh684TNMK9v5aAu2wxfIRMk/GpAJRrzcirMAPIaSDaByQ==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-2.0.0.tgz",
"integrity": "sha512-1n4LEPOL4wRXY8rOQcuA7Iuaphe5xCMayvufCzlLAi+hRsnBRDbSS6XPuV58CBVJxj5D9ApFLyjQ7KzFToyHBw==",
"dev": true,
"license": "MIT"
},
@ -10007,6 +10078,15 @@
"npm": ">=8"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/lilconfig": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
@ -10035,6 +10115,37 @@
"uc.micro": "^2.0.0"
}
},
"node_modules/lit": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lit/-/lit-3.3.0.tgz",
"integrity": "sha512-DGVsqsOIHBww2DqnuZzW7QsuCdahp50ojuDaBPC7jUDRpYoH0z7kHBBYZewRzer75FwtrkmkKk7iOAwSaWdBmw==",
"license": "BSD-3-Clause",
"dependencies": {
"@lit/reactive-element": "^2.1.0",
"lit-element": "^4.2.0",
"lit-html": "^3.3.0"
}
},
"node_modules/lit-element": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.0.tgz",
"integrity": "sha512-MGrXJVAI5x+Bfth/pU9Kst1iWID6GHDLEzFEnyULB/sFiRLgkd8NPK/PeeXxktA3T6EIIaq8U3KcbTU5XFcP2Q==",
"license": "BSD-3-Clause",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.2.0",
"@lit/reactive-element": "^2.1.0",
"lit-html": "^3.3.0"
}
},
"node_modules/lit-html": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.0.tgz",
"integrity": "sha512-RHoswrFAxY2d8Cf2mm4OZ1DgzCoBKUKSPvA1fhtSELxUERq2aQQ2h05pO9j81gS1o7RIRJ+CePLogfyahwmynw==",
"license": "BSD-3-Clause",
"dependencies": {
"@types/trusted-types": "^2.0.2"
}
},
"node_modules/loader-runner": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
@ -11843,13 +11954,13 @@
}
},
"node_modules/playwright": {
"version": "1.53.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.0.tgz",
"integrity": "sha512-ghGNnIEYZC4E+YtclRn4/p6oYbdPiASELBIYkBXfaTVKreQUYbMUYQDwS12a8F0/HtIjr/CkGjtwABeFPGcS4Q==",
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz",
"integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.53.0"
"playwright-core": "1.52.0"
},
"bin": {
"playwright": "cli.js"
@ -11862,9 +11973,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.53.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0.tgz",
"integrity": "sha512-mGLg8m0pm4+mmtB7M89Xw/GSqoNC+twivl8ITteqvAndachozYe2ZA7srU6uleV1vEdAHYqjq+SV8SNxRRFYBw==",
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz",
"integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@ -12347,6 +12458,16 @@
"dev": true,
"license": "Unlicense"
},
"node_modules/promise-worker-transferable": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
"integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==",
"license": "Apache-2.0",
"dependencies": {
"is-promise": "^2.1.0",
"lie": "^3.0.2"
}
},
"node_modules/proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@ -14409,6 +14530,13 @@
"node": ">=0.8"
}
},
"node_modules/three": {
"version": "0.172.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.172.0.tgz",
"integrity": "sha512-6HMgMlzU97MsV7D/tY8Va38b83kz8YJX+BefKjspMNAv0Vx6dxMogHOrnRl/sbMIs3BPUKijPqDqJ/+UwJbIow==",
"license": "MIT",
"peer": true
},
"node_modules/throttle-debounce": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.0.tgz",

View file

@ -11,11 +11,12 @@
"@github/markdown-toolbar-element": "2.2.3",
"@github/quote-selection": "2.1.0",
"@github/text-expander-element": "2.8.0",
"@google/model-viewer": "4.1.0",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@primer/octicons": "19.14.0",
"ansi_up": "6.0.5",
"asciinema-player": "3.8.2",
"chart.js": "4.4.9",
"chart.js": "4.5.0",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.2.0",
"clippie": "4.1.7",
@ -61,9 +62,9 @@
"devDependencies": {
"@axe-core/playwright": "4.10.2",
"@eslint-community/eslint-plugin-eslint-comments": "4.5.0",
"@playwright/test": "1.53.0",
"@playwright/test": "1.52.0",
"@stoplight/spectral-cli": "6.15.0",
"@stylistic/eslint-plugin-js": "4.4.1",
"@stylistic/eslint-plugin": "4.4.1",
"@stylistic/stylelint-plugin": "3.1.2",
"@vitejs/plugin-vue": "5.2.4",
"@vitest/coverage-v8": "3.2.3",
@ -78,12 +79,12 @@
"eslint-plugin-playwright": "2.2.0",
"eslint-plugin-regexp": "2.9.0",
"eslint-plugin-sonarjs": "3.0.2",
"eslint-plugin-unicorn": "59.0.1",
"eslint-plugin-toml": "0.12.0",
"eslint-plugin-unicorn": "59.0.1",
"eslint-plugin-vitest-globals": "1.5.0",
"eslint-plugin-vue": "10.2.0",
"eslint-plugin-vue-scoped-css": "2.10.0",
"eslint-plugin-wc": "2.2.1",
"eslint-plugin-wc": "3.0.1",
"globals": "16.1.0",
"happy-dom": "18.0.0",
"license-checker-rseidelsohn": "4.4.2",

View file

@ -0,0 +1,33 @@
<!--start release-notes-assistant-->
## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- Features
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7986) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7991)): <!--number 7991 --><!--line 0 --><!--description ZmVhdDogbWFrZSBGb3JnZWpvIEFjdGlvbnMgc2VydmVyIGxvZ3MgbGVzcyBub2lzeQ==-->feat: make Forgejo Actions server logs less noisy<!--description-->
- Bug fixes
- [PR](https://codeberg.org/forgejo/forgejo/pulls/8155) ([backported](https://codeberg.org/forgejo/forgejo/pulls/8167)): <!--number 8167 --><!--line 0 --><!--description Zml4OiBkbyBub3QgZmFpbCB3aGVuIHJlbGVhc2Ugb3Igd2lraSBpcyBzZXQgaW4gYC9yZXBvcy9taWdyYXRlYCBBUEk=-->fix: do not fail when release or wiki is set in `/repos/migrate` API<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7976) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7985)): <!--number 7985 --><!--line 0 --><!--description Zml4OiBpZ25vcmUgZXhwaXJlZCBhcnRpZmFjdHMgZm9yIHF1b3RhIGNhbGN1bGF0aW9u-->fix: ignore expired artifacts for quota calculation<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7979) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7983)): <!--number 7983 --><!--line 0 --><!--description Zml4OiBwdWxsIHJlcXVlc3QgY3Jvc3MgcmVmZXJlbmNlcw==-->fix: pull request cross references<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7883) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7886)): <!--number 7886 --><!--line 0 --><!--description Zml4OiBxdW90ZSByZXBseSBpbiBDaHJvbWl1bQ==-->fix: quote reply in Chromium<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7775) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7779)): <!--number 7779 --><!--line 0 --><!--description Zml4OiBtYWtlIGhhc2ggcGF0dGVybiBtb3JlIHN0cmljdA==-->fix: make hash pattern more strict<!--description-->
- Included for completeness but not worth a release note
- [PR](https://codeberg.org/forgejo/forgejo/pulls/8112) ([backported](https://codeberg.org/forgejo/forgejo/pulls/8120)): <!--number 8120 --><!--line 0 --><!--description Zml4OiByZW1vdmUgZG93bmxvYWQgYXR0cmlidXRlIGZyb20gZXh0ZXJuYWwgYXNzZXRz-->fix: remove download attribute from external assets<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/8110): <!--number 8110 --><!--line 0 --><!--description VXBkYXRlIGJsZXZlIHRvIHYyLjUuMiB3aXRoIGNoYW5nZXMgbWFkZSBpbiBiYWNrcG9ydCBvZiAyLjUuMA==-->Update bleve to v2.5.2 with changes made in backport of 2.5.0<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/8094) ([backported](https://codeberg.org/forgejo/forgejo/pulls/8095)): <!--number 8095 --><!--line 0 --><!--description Zml4OiBzaG93IG1lbWJlcnNoaXAgb2YgbGltaXRlZCBvcmdz-->fix: show membership of limited orgs<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/8059): <!--number 8059 --><!--line 0 --><!--description VXBkYXRlIGRlcGVuZGVuY3kgZ28gdG8gdjEuMjQuMyAodjExLjAvZm9yZ2Vqbyk=-->Update dependency go to v1.24.3 (v11.0/forgejo)<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/8057): <!--number 8057 --><!--line 0 --><!--description Y2hvcmU6IGRyb3AgdW51c2VkIGBAdHlwZXNjcmlwdC1lc2xpbnQvcGFyc2VyYCBwYWNrYWdl-->chore: drop unused `@typescript-eslint/parser` package<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/8021) ([backported](https://codeberg.org/forgejo/forgejo/pulls/8022)): <!--number 8022 --><!--line 0 --><!--description Y2hvcmUoY2xlYW51cCk6IHN1cHByZXNzIG5vbiBhY3Rpb25hYmxlIFhPUk0gd2FybmluZ3M=-->chore(cleanup): suppress non actionable XORM warnings<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7987) ([backported](https://codeberg.org/forgejo/forgejo/pulls/8000)): <!--number 8000 --><!--line 0 --><!--description Zml4OiBhZ2dyZWdhdGUgZGVsZXRlZCB0ZWFtIGFzIGdob3N0IHRlYW0=-->fix: aggregate deleted team as ghost team<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7925) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7937)): <!--number 7937 --><!--line 0 --><!--description Zml4KHVpKTogY2VudGVyIGZvb3RlciBsaW5rcw==-->fix(ui): center footer links<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7894) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7903)): <!--number 7903 --><!--line 0 --><!--description Zml4KHVpKTogZml4IGZvcmNlLXB1c2ggY29tcGFyZSBsaW5lIGxheW91dA==-->fix(ui): fix force-push compare line layout<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7884) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7887)): <!--number 7887 --><!--line 0 --><!--description Zml4OiBwYXJzZSBgY2hhbmdlLWlkYCBpbiB0aGUgZ2l0IGNvbW1pdCBoZWFkZXI=-->fix: parse `change-id` in the git commit header<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7885): <!--number 7885 --><!--line 0 --><!--description VXBkYXRlIG1vZHVsZSBnaXRodWIuY29tL2JsZXZlc2VhcmNoL2JsZXZlL3YyIHRvIHYyLjUuMSAodjExLjAvZm9yZ2VqbykgLSBhYmFuZG9uZWQ=-->Update module github.com/blevesearch/bleve/v2 to v2.5.1 (v11.0/forgejo) - abandoned<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7746) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7871)): <!--number 7871 --><!--line 0 --><!--description Zml4KHVpKTogaW1wcm92ZSBmb3JjZS1wdXNoIGNvbXBhcmUgbGluZSBsYXlvdXQ=-->fix(ui): improve force-push compare line layout<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7640) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7869)): <!--number 7869 --><!--line 0 --><!--description Zml4OiBSZW1vdmUgImNyZWF0ZSBicmFuY2giIGJ1dHRvbiBvbiBtaXJyb3JlZCByZXBvcw==-->fix: Remove "create branch" button on mirrored repos<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7858): <!--number 7858 --><!--line 0 --><!--description VXBkYXRlIG1vZHVsZSBnaXRodWIuY29tL21zdGVpbmVydC9wYW0vdjIgdG8gdjIuMS4wICh2MTEuMC9mb3JnZWpvKQ==-->Update module github.com/msteinert/pam/v2 to v2.1.0 (v11.0/forgejo)<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7817) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7821)): <!--number 7821 --><!--line 0 --><!--description Zml4OiByZXBsYWNlIMOfIHdpdGggc3MgaW4gbm9ybWFsaXplVXNlck5hbWU=-->fix: replace ß with ss in normalizeUserName<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7784) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7786)): <!--number 7786 --><!--line 0 --><!--description Zml4KGFwaSk6IGRvY3VtZW50IGBpc19zeXN0ZW1fd2ViaG9va2AgZmllbGQ=-->fix(api): document `is_system_webhook` field<!--description-->
- [PR](https://codeberg.org/forgejo/forgejo/pulls/7773) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7774)): <!--number 7774 --><!--line 0 --><!--description Zml4OiByZW1vdmUgYXJ0aWZpY2lhbCBkZWxheSBmb3IgUFIgdXBkYXRl-->fix: remove artificial delay for PR update<!--description-->
<!--end release-notes-assistant-->

View file

@ -748,7 +748,7 @@ func ListActionRuns(ctx *context.APIContext) {
// type: string
// responses:
// "200":
// "$ref": "#/responses/RepoActionRunList"
// "$ref": "#/responses/ActionRunList"
// "400":
// "$ref": "#/responses/error"
// "403":
@ -779,16 +779,16 @@ func ListActionRuns(ctx *context.APIContext) {
return
}
res := new(api.ListRepoActionRunResponse)
res := new(api.ListActionRunResponse)
res.TotalCount = total
res.Entries = make([]*api.RepoActionRun, len(runs))
res.Entries = make([]*api.ActionRun, len(runs))
for i, r := range runs {
cr, err := convert.ToRepoActionRun(ctx, r)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ToActionRun", err)
if err := r.LoadAttributes(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return
}
cr := convert.ToActionRun(ctx, r, ctx.Doer)
res.Entries[i] = cr
}
@ -821,7 +821,7 @@ func GetActionRun(ctx *context.APIContext) {
// required: true
// responses:
// "200":
// "$ref": "#/responses/RepoActionRun"
// "$ref": "#/responses/ActionRun"
// "400":
// "$ref": "#/responses/error"
// "403":
@ -839,16 +839,17 @@ func GetActionRun(ctx *context.APIContext) {
return
}
// Action runs lives in its own table, therefore we check that the
// run with the requested ID is owned by the repository
if ctx.Repo.Repository.ID != run.RepoID {
ctx.Error(http.StatusNotFound, "GetRunById", util.ErrNotExist)
return
}
res, err := convert.ToRepoActionRun(ctx, run)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ToRepoActionRun", err)
if err := run.LoadAttributes(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return
}
ctx.JSON(http.StatusOK, res)
ctx.JSON(http.StatusOK, convert.ToActionRun(ctx, run, ctx.Doer))
}

View file

@ -71,7 +71,7 @@ func ListPullRequests(ctx *context.APIContext) {
// in: query
// description: Type of sort
// type: string
// enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority]
// enum: [oldest, recentupdate, recentclose, leastupdate, mostcomment, leastcomment, priority]
// - name: milestone
// in: query
// description: ID of the milestone

View file

@ -463,16 +463,16 @@ type swaggerSyncForkInfo struct {
Body []api.SyncForkInfo `json:"body"`
}
// RepoActionRunList
// swagger:response RepoActionRunList
type swaggerRepoActionRunList struct {
// ActionRunList
// swagger:response ActionRunList
type swaggerActionRunList struct {
// in:body
Body api.ListRepoActionRunResponse `json:"body"`
Body api.ListActionRunResponse `json:"body"`
}
// RepoActionRun
// swagger:response RepoActionRun
type swaggerRepoActionRun struct {
// ActionRun
// swagger:response ActionRun
type swaggerActionRun struct {
// in:body
Body api.RepoActionRun `json:"body"`
Body api.ActionRun `json:"body"`
}

View file

@ -175,10 +175,12 @@ func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repositor
return
}
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
log.Error("failed to GetBlobContent: %v", err)
if rc, _, err := profileReadme.NewTruncatedReader(setting.UI.MaxDisplayFileSize); err != nil {
log.Error("failed to NewTruncatedReader: %v", err)
} else {
if profileContent, err := markdown.RenderString(&markup.RenderContext{
defer rc.Close()
if profileContent, err := markdown.RenderReader(&markup.RenderContext{
Ctx: ctx,
GitRepo: profileGitRepo,
Links: markup.Links{
@ -188,7 +190,7 @@ func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repositor
BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
},
Metas: map[string]string{"mode": "document"},
}, bytes); err != nil {
}, rc); err != nil {
log.Error("failed to RenderString: %v", err)
} else {
ctx.Data["ProfileReadme"] = profileContent

View file

@ -1308,7 +1308,7 @@ func roleDescriptor(ctx stdCtx.Context, repo *repo_model.Repository, poster *use
}
// Special user that can't have associated contributions and permissions in the repo.
if poster.IsGhost() || poster.IsActions() || poster.IsAPServerActor() {
if poster.IsSystem() || poster.IsAPServerActor() {
return roleDescriptor, nil
}

View file

@ -10,13 +10,16 @@ import (
"errors"
"fmt"
"html"
"html/template"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"forgejo.org/models"
activities_model "forgejo.org/models/activities"
asymkey_model "forgejo.org/models/asymkey"
"forgejo.org/models/db"
git_model "forgejo.org/models/git"
issues_model "forgejo.org/models/issues"
@ -28,11 +31,13 @@ import (
"forgejo.org/models/unit"
user_model "forgejo.org/models/user"
"forgejo.org/modules/base"
"forgejo.org/modules/charset"
"forgejo.org/modules/emoji"
"forgejo.org/modules/git"
"forgejo.org/modules/gitrepo"
issue_template "forgejo.org/modules/issue/template"
"forgejo.org/modules/log"
"forgejo.org/modules/markup"
"forgejo.org/modules/optional"
"forgejo.org/modules/setting"
"forgejo.org/modules/structs"
@ -498,6 +503,7 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
ctx.Data["IsPullRequestBroken"] = true
ctx.Data["BaseTarget"] = pull.BaseBranch
ctx.Data["NumCommits"] = 0
ctx.Data["CommitIDs"] = map[string]bool{}
ctx.Data["NumFiles"] = 0
return nil
}
@ -508,6 +514,12 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
ctx.Data["NumCommits"] = len(compareInfo.Commits)
ctx.Data["NumFiles"] = compareInfo.NumFiles
commitIDs := map[string]bool{}
for _, commit := range compareInfo.Commits {
commitIDs[commit.ID.String()] = true
}
ctx.Data["CommitIDs"] = commitIDs
if len(compareInfo.Commits) != 0 {
sha := compareInfo.Commits[0].ID.String()
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptionsAll)
@ -591,6 +603,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
ctx.Data["IsPullRequestBroken"] = true
ctx.Data["BaseTarget"] = pull.BaseBranch
ctx.Data["NumCommits"] = 0
ctx.Data["CommitIDs"] = map[string]bool{}
ctx.Data["NumFiles"] = 0
return nil
}
@ -601,6 +614,13 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
ctx.Data["NumCommits"] = len(compareInfo.Commits)
ctx.Data["NumFiles"] = compareInfo.NumFiles
commitIDs := map[string]bool{}
for _, commit := range compareInfo.Commits {
commitIDs[commit.ID.String()] = true
}
ctx.Data["CommitIDs"] = commitIDs
return compareInfo
}
@ -659,6 +679,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
}
ctx.Data["BaseTarget"] = pull.BaseBranch
ctx.Data["NumCommits"] = 0
ctx.Data["CommitIDs"] = map[string]bool{}
ctx.Data["NumFiles"] = 0
return nil
}
@ -736,6 +757,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
ctx.Data["IsPullRequestBroken"] = true
ctx.Data["BaseTarget"] = pull.BaseBranch
ctx.Data["NumCommits"] = 0
ctx.Data["CommitIDs"] = map[string]bool{}
ctx.Data["NumFiles"] = 0
return nil
}
@ -760,6 +782,13 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
ctx.Data["NumCommits"] = len(compareInfo.Commits)
ctx.Data["NumFiles"] = compareInfo.NumFiles
commitIDs := map[string]bool{}
for _, commit := range compareInfo.Commits {
commitIDs[commit.ID.String()] = true
}
ctx.Data["CommitIDs"] = commitIDs
return compareInfo
}
@ -919,7 +948,78 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
ctx.Data["IsShowingOnlySingleCommit"] = willShowSpecifiedCommit
if willShowSpecifiedCommit || willShowSpecifiedCommitRange {
if willShowSpecifiedCommit {
commitID := specifiedEndCommit
ctx.Data["CommitID"] = commitID
var prevCommit, curCommit, nextCommit *git.Commit
// Iterate in reverse to properly map "previous" and "next" buttons
for i := len(prInfo.Commits) - 1; i >= 0; i-- {
commit := prInfo.Commits[i]
if curCommit != nil {
nextCommit = commit
break
}
if commit.ID.String() == commitID {
curCommit = commit
} else {
prevCommit = commit
}
}
if curCommit == nil {
ctx.ServerError("Repo.GitRepo.viewPullFiles", git.ErrNotExist{ID: commitID})
return
}
ctx.Data["Commit"] = curCommit
if prevCommit != nil {
ctx.Data["PrevCommitLink"] = path.Join(ctx.Repo.RepoLink, "pulls", strconv.FormatInt(issue.Index, 10), "commits", prevCommit.ID.String())
}
if nextCommit != nil {
ctx.Data["NextCommitLink"] = path.Join(ctx.Repo.RepoLink, "pulls", strconv.FormatInt(issue.Index, 10), "commits", nextCommit.ID.String())
}
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)
if err != nil {
log.Error("GetLatestCommitStatus: %v", err)
}
ctx.Data["CommitStatus"] = git_model.CalcCommitStatus(statuses)
ctx.Data["CommitStatuses"] = statuses
verification := asymkey_model.ParseCommitWithSignature(ctx, curCommit)
ctx.Data["Verification"] = verification
ctx.Data["Author"] = user_model.ValidateCommitWithEmail(ctx, curCommit)
note := &git.Note{}
err = git.GetNote(ctx, ctx.Repo.GitRepo, specifiedEndCommit, note)
if err == nil {
ctx.Data["NoteCommit"] = note.Commit
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(&markup.RenderContext{
Links: markup.Links{
Base: ctx.Repo.RepoLink,
BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)),
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
if err != nil {
ctx.ServerError("RenderCommitMessage", err)
return
}
}
endCommitID = commitID
startCommitID = prInfo.MergeBase
ctx.Data["IsShowingAllCommits"] = false
} else if willShowSpecifiedCommitRange {
if len(specifiedEndCommit) > 0 {
endCommitID = specifiedEndCommit
} else {
@ -930,6 +1030,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
} else {
startCommitID = prInfo.MergeBase
}
ctx.Data["IsShowingAllCommits"] = false
} else {
endCommitID = headCommitID
@ -937,10 +1038,10 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
ctx.Data["IsShowingAllCommits"] = true
}
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
ctx.Data["AfterCommitID"] = endCommitID
ctx.Data["BeforeCommitID"] = startCommitID
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
fileOnly := ctx.FormBool("file-only")

View file

@ -342,6 +342,20 @@ func LFSFileGet(ctx *context.Context) {
ctx.Data["IsVideoFile"] = true
case st.IsAudio():
ctx.Data["IsAudioFile"] = true
case st.Is3DModel():
ctx.Data["Is3DModelFile"] = true
switch {
case st.IsGLB():
ctx.Data["IsGLBFile"] = true
case st.IsSTL():
ctx.Data["IsSTLFile"] = true
case st.IsGLTF():
ctx.Data["IsGLTFFile"] = true
case st.IsOBJ():
ctx.Data["IsOBJFile"] = true
case st.Is3MF():
ctx.Data["Is3MFFile"] = true
}
case st.IsImage() && (setting.UI.SVG.Enabled || !st.IsSvgImage()):
ctx.Data["IsImageFile"] = true
}

View file

@ -153,11 +153,9 @@ func UnitsPost(ctx *context.Context) {
})
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki)
} else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() {
var wikiPermissions repo_model.UnitAccessMode
wikiPermissions := repo_model.UnitAccessModeUnset
if form.GloballyWriteableWiki {
wikiPermissions = repo_model.UnitAccessModeWrite
} else {
wikiPermissions = repo_model.UnitAccessModeRead
}
units = append(units, repo_model.RepoUnit{
RepoID: repo.ID,

View file

@ -439,8 +439,8 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
ctx.Data["FileError"] = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", workFlowErr.Error())
}
} else if slices.Contains([]string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}, ctx.Repo.TreePath) {
if data, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize); err == nil {
_, warnings := issue_model.GetCodeOwnersFromContent(ctx, data)
if rc, size, err := blob.NewTruncatedReader(setting.UI.MaxDisplayFileSize); err == nil {
_, warnings := issue_model.GetCodeOwnersFromReader(ctx, rc, size > setting.UI.MaxDisplayFileSize)
if len(warnings) > 0 {
ctx.Data["FileWarning"] = strings.Join(warnings, "\n")
}
@ -624,6 +624,20 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
ctx.Data["IsVideoFile"] = true
case fInfo.st.IsAudio():
ctx.Data["IsAudioFile"] = true
case fInfo.st.Is3DModel():
ctx.Data["Is3DModelFile"] = true
switch {
case fInfo.st.IsGLB():
ctx.Data["IsGLBFile"] = true
case fInfo.st.IsSTL():
ctx.Data["IsSTLFile"] = true
case fInfo.st.IsGLTF():
ctx.Data["IsGLTFFile"] = true
case fInfo.st.IsOBJ():
ctx.Data["IsOBJFile"] = true
case fInfo.st.Is3MF():
ctx.Data["Is3MFFile"] = true
}
case fInfo.st.IsImage() && (setting.UI.SVG.Enabled || !fInfo.st.IsSvgImage()):
ctx.Data["IsImageFile"] = true
ctx.Data["CanCopyContent"] = true

View file

@ -70,17 +70,6 @@ func userProfile(ctx *context.Context) {
ctx.Data["OpenGraphURL"] = ctx.ContextUser.HTMLURL()
ctx.Data["OpenGraphDescription"] = ctx.ContextUser.Description
// prepare heatmap data
if setting.Service.EnableUserHeatmap {
data, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserHeatmapDataByUser", err)
return
}
ctx.Data["HeatmapData"] = data
ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data)
}
profileDbRepo, profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer)
defer profileClose()
@ -186,6 +175,17 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
ctx.Data["CardsNoneMsg"] = ctx.Tr("followers.outgoing.list.none", ctx.ContextUser.Name)
}
case "activity":
// prepare heatmap data
if setting.Service.EnableUserHeatmap {
data, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserHeatmapDataByUser", err)
return
}
ctx.Data["HeatmapData"] = data
ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data)
}
date := ctx.FormString("date")
pagingNum = setting.UI.FeedPagingNum
items, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{
@ -264,10 +264,12 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
total = int(count)
case "overview":
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
log.Error("failed to GetBlobContent: %v", err)
if rc, _, err := profileReadme.NewTruncatedReader(setting.UI.MaxDisplayFileSize); err != nil {
log.Error("failed to NewTruncatedReader: %v", err)
} else {
if profileContent, err := markdown.RenderString(&markup.RenderContext{
defer rc.Close()
if profileContent, err := markdown.RenderReader(&markup.RenderContext{
Ctx: ctx,
GitRepo: profileGitRepo,
Links: markup.Links{
@ -280,7 +282,7 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
},
Metas: map[string]string{"mode": "document"},
}, bytes); err != nil {
}, rc); err != nil {
log.Error("failed to RenderString: %v", err)
} else {
ctx.Data["ProfileReadme"] = profileContent

View file

@ -66,6 +66,9 @@ func ProfilePost(ctx *context.Context) {
ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx)
ctx.Data["CooldownPeriod"] = setting.Service.UsernameCooldownPeriod
ctx.Data["CommonPronouns"] = commonPronouns
ctx.Data["MaxAvatarFileSize"] = setting.Avatar.MaxFileSize
ctx.Data["MaxAvatarWidth"] = setting.Avatar.MaxWidth
ctx.Data["MaxAvatarHeight"] = setting.Avatar.MaxHeight
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplSettingsProfile)

View file

@ -1512,7 +1512,10 @@ func registerRoutes(m *web.Route) {
m.Group("/commits", func() {
m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits)
m.Get("/list", context.RepoRef(), repo.GetPullCommits)
m.Get("/{sha:[a-f0-9]{4,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit)
m.Group("/{sha:[a-f0-9]{4,40}}", func() {
m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit)
m.Post("/reviews/submit", context.RepoMustNotBeArchived(), web.Bind(forms.SubmitReviewForm{}), repo.SubmitReview)
})
})
m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), context.EnforceQuotaWeb(quota_model.LimitSubjectSizeGitAll, context.QuotaTargetRepo), repo.MergePullRequest)
m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest)

View file

@ -345,6 +345,14 @@ func handleWorkflows(
Status: actions_model.StatusWaiting,
}
if workflow, err := model.ReadWorkflow(bytes.NewReader(dwf.Content)); err == nil {
notifications, err := workflow.Notifications()
if err != nil {
log.Error("Notifications: %w", err)
}
run.NotifyEmail = notifications
}
need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer)
if err != nil {
log.Error("check if need approval for repo %d with user %d: %v", input.Repo.ID, input.Doer.ID, err)

View file

@ -4,6 +4,7 @@
package actions
import (
"bytes"
"context"
"errors"
"fmt"
@ -18,6 +19,7 @@ import (
webhook_module "forgejo.org/modules/webhook"
"github.com/nektos/act/pkg/jobparser"
act_model "github.com/nektos/act/pkg/model"
"xorm.io/builder"
)
@ -140,6 +142,16 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule)
return err
}
workflow, err := act_model.ReadWorkflow(bytes.NewReader(cron.Content))
if err != nil {
return err
}
notifications, err := workflow.Notifications()
if err != nil {
return err
}
run.NotifyEmail = notifications
// Parse the workflow specification from the cron schedule
workflows, err := jobparser.Parse(cron.Content, jobparser.WithVars(vars))
if err != nil {

View file

@ -0,0 +1,121 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package actions
import (
"testing"
actions_model "forgejo.org/models/actions"
repo_model "forgejo.org/models/repo"
"forgejo.org/models/unittest"
webhook_module "forgejo.org/modules/webhook"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCreateScheduleTask(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2, OwnerID: 2})
assertConstant := func(t *testing.T, cron *actions_model.ActionSchedule, run *actions_model.ActionRun) {
t.Helper()
assert.Equal(t, cron.Title, run.Title)
assert.Equal(t, cron.RepoID, run.RepoID)
assert.Equal(t, cron.OwnerID, run.OwnerID)
assert.Equal(t, cron.WorkflowID, run.WorkflowID)
assert.Equal(t, cron.TriggerUserID, run.TriggerUserID)
assert.Equal(t, cron.Ref, run.Ref)
assert.Equal(t, cron.CommitSHA, run.CommitSHA)
assert.Equal(t, cron.Event, run.Event)
assert.Equal(t, cron.EventPayload, run.EventPayload)
assert.Equal(t, cron.ID, run.ScheduleID)
assert.Equal(t, actions_model.StatusWaiting, run.Status)
}
assertMutable := func(t *testing.T, expected, run *actions_model.ActionRun) {
t.Helper()
assert.Equal(t, expected.NotifyEmail, run.NotifyEmail)
}
testCases := []struct {
name string
cron actions_model.ActionSchedule
want []actions_model.ActionRun
}{
{
name: "simple",
cron: actions_model.ActionSchedule{
Title: "scheduletitle1",
RepoID: repo.ID,
OwnerID: repo.OwnerID,
WorkflowID: "some.yml",
TriggerUserID: repo.OwnerID,
Ref: "branch",
CommitSHA: "fakeSHA",
Event: webhook_module.HookEventSchedule,
EventPayload: "fakepayload",
Content: []byte(
`
name: test
on: push
jobs:
job2:
runs-on: ubuntu-latest
steps:
- run: true
`),
},
want: []actions_model.ActionRun{
{
Title: "scheduletitle1",
NotifyEmail: false,
},
},
},
{
name: "enable-email-notifications is true",
cron: actions_model.ActionSchedule{
Title: "scheduletitle2",
RepoID: repo.ID,
OwnerID: repo.OwnerID,
WorkflowID: "some.yml",
TriggerUserID: repo.OwnerID,
Ref: "branch",
CommitSHA: "fakeSHA",
Event: webhook_module.HookEventSchedule,
EventPayload: "fakepayload",
Content: []byte(
`
name: test
enable-email-notifications: true
on: push
jobs:
job2:
runs-on: ubuntu-latest
steps:
- run: true
`),
},
want: []actions_model.ActionRun{
{
Title: "scheduletitle2",
NotifyEmail: true,
},
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
require.NoError(t, CreateScheduleTask(t.Context(), &testCase.cron))
require.Equal(t, len(testCase.want), unittest.GetCount(t, actions_model.ActionRun{RepoID: repo.ID}))
for _, expected := range testCase.want {
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{Title: expected.Title})
assertConstant(t, &testCase.cron, run)
assertMutable(t, &expected, run)
}
unittest.AssertSuccessfulDelete(t, actions_model.ActionRun{RepoID: repo.ID})
})
}
}

View file

@ -111,6 +111,11 @@ func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGette
return nil, nil, err
}
notifications, err := wf.Notifications()
if err != nil {
return nil, nil, err
}
run := &actions_model.ActionRun{
Title: title,
RepoID: repo.ID,
@ -125,6 +130,7 @@ func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGette
EventPayload: string(p),
TriggerEvent: string(webhook.HookEventWorkflowDispatch),
Status: actions_model.StatusWaiting,
NotifyEmail: notifications,
}
vars, err := actions_model.GetVariablesOfRun(ctx, run)

View file

@ -107,6 +107,7 @@ func handlePullRequestAutoMerge(pullID int64, sha string) {
return
}
if !exists {
log.Trace("GetScheduledMergeByPullID found nothing for PR %d", pullID)
return
}
@ -204,6 +205,10 @@ func handlePullRequestAutoMerge(pullID int64, sha string) {
return
}
if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) {
log.Error("DeleteScheduledAutoMerge[%d]: %v", pr.ID, err)
}
if err := pull_service.Merge(ctx, pr, doer, baseGitRepo, scheduledPRM.MergeStyle, "", scheduledPRM.Message, true); err != nil {
log.Error("pull_service.Merge: %v", err)
// FIXME: if merge failed, we should display some error message to the pull request page.

View file

@ -8,22 +8,17 @@ import (
actions_model "forgejo.org/models/actions"
access_model "forgejo.org/models/perm/access"
user_model "forgejo.org/models/user"
api "forgejo.org/modules/structs"
)
// ToActionRun convert actions_model.User to api.ActionRun
// the run needs all attributes loaded
func ToActionRun(ctx context.Context, run *actions_model.ActionRun) *api.ActionRun {
func ToActionRun(ctx context.Context, run *actions_model.ActionRun, doer *user_model.User) *api.ActionRun {
if run == nil {
return nil
}
// The doer is the one whose perspective is used to view this ActionRun.
// In the best case we use the user that created the webhook.
// Unfortunately we don't know who that was.
// So instead we use the repo owner, who is able to create webhooks and allow others to do so by making them repo admins.
// This is pretty close to perfect.
doer := run.Repo.Owner
permissionInRepo, _ := access_model.GetUserRepoPermission(ctx, run.Repo, doer)
return &api.ActionRun{

View file

@ -222,29 +222,6 @@ func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.Action
}, nil
}
// ToRepoActionRun convert a actions_model.ActionRun to an api.RepoActionRun
func ToRepoActionRun(ctx context.Context, r *actions_model.ActionRun) (*api.RepoActionRun, error) {
if err := r.LoadAttributes(ctx); err != nil {
return nil, err
}
url := strings.TrimSuffix(setting.AppURL, "/") + r.Link()
actor := ToUser(ctx, r.TriggerUser, nil)
return &api.RepoActionRun{
ID: r.ID,
Name: r.Title,
HeadBranch: r.PrettyRef(),
HeadSHA: r.CommitSHA,
RunNumber: r.Index,
Event: r.TriggerEvent,
Status: r.Status.String(),
WorkflowID: r.WorkflowID,
URL: url,
TriggeringActor: actor,
}, nil
}
// ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification {
verif := asymkey_model.ParseCommitWithSignature(ctx, c)

View file

@ -211,6 +211,11 @@ func CreateUserFromAP(ctx context.Context, personID fm.PersonID, federationHostI
return nil, nil, err
}
inbox, err := url.ParseRequestURI(person.Inbox.GetLink().String())
if err != nil {
return nil, nil, err
}
newUser := user.User{
LowerName: strings.ToLower(name),
Name: name,
@ -227,6 +232,7 @@ func CreateUserFromAP(ctx context.Context, personID fm.PersonID, federationHostI
federatedUser := user.FederatedUser{
ExternalID: personID.ID,
FederationHostID: federationHostID,
InboxPath: inbox.Path,
NormalizedOriginalURL: personID.AsURI(),
}

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