Compare commits

..
139 Commits
Author SHA1 Message Date
Giteabotandsilverwind 4f5a3e13e5 fix(repo): don't get stuck generating contributor stats (#40125) (#40156)
Backport #40125 by @silverwind

A failed contributor stats generation never released its lock, so after
the cached error expired, the contributors and code frequency pages kept
saying stats are being generated until a restart. A generation
outlasting the 5 second wait also leaked its goroutine on an unread
channel.

The lock is now claimed atomically and released together with the
completion signal when generation ends, and a cache miss after the wait
counts as still generating.

Co-authored-by: silverwind <me@silverwind.io>
2026-11-01 01:20:24 -07:00
c50d5fc20d fix: list bot users in gitea admin user list and admin users API (#40060) (#40147)
Backport #40060 by @soujasK

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

The admin web UI lists every account type except organizations, but
`gitea admin user list` and `GET /api/v1/admin/users` only returned
individual users. That hid bots created for CI, even though the CLI and
the admin API can both create and manage them.

- The admin web UI, the admin API and the CLI share one type list:
individual, reserved, bot and remote accounts.
- `gitea admin user list` (and `--admin`) gains a `Type` column showing
`User`, `Bot`, `Reserved` or `Remote`.

Signed-off-by: Soujas K <kudchadkarsoujas@gmail.com>
Co-authored-by: soujasK <121026960+soujasK@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-11-01 07:27:30 +00:00
dfdb0438ad fix(auth): only auto-link the account the external identity points to (#40121) (#40137)
Backport https://github.com/go-gitea/gitea/pull/40121

With `[oauth2_client].ACCOUNT_LINKING = auto`, the link account sign-up
form linked the external identity to an existing account matching the
submitted user name or email and signed in as it. Automatic linking now
only happens when the existing user matches the external identity,
otherwise the linking login page asks for the account's password.

`release/v28` lacks `TestOAuth2AvatarFromPicture`, so only the new test
is added.

Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: Giteabot <teabot@gitea.io>
2026-10-31 22:33:10 +00:00
7868ade8f7 perf(pull): load the repository once for all commit statuses (#39930) (#40135)
Backport #39930 by @lunny

Pages showing commit statuses loaded the repository of every status
separately, so a PR with ~75 statuses ran ~75 identical repository
queries per merge box render (about 3.4s in a production trace).
`statusRepoCanReadActions` only loaded the repository inside the
permission cache loader, which never runs once `RepoAssignment` has
published the permission.

- `CommitStatus.loadRepository` resolves through the request context
cache
- `statusRepoCanReadActions` loads the repository before the permission
lookup
- `RepoAssignment` publishes its repository to the context cache next to
the permission
- `GetCommitActionsStatusMap` drops its own repository map

Queries per request with 50 statuses (45 Actions, 5 external) on the
head commit, SQLite:

| Page | Repository queries | Total queries |
|---|---|---|
| PR view | 61 → 11 | 157 → 107 |
| Commit | 6 → 5 | 24 → 23 |
| Branches | 6 → 5 | 33 → 32 |
| Commits | 6 → 5 | 23 → 22 |
| Repo home | 8 → 7 | 35 → 34 |
| `/pulls` | 15 → 15 | 37 → 37 |

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-31 19:12:36 +00:00
67904da6a5 perf: speed up repository issue and pull request lists (#40114) (#40132)
Backport #40114 by @silverwind

The repository issue and pull request lists filter `access` by
`repo_id`, which no index covers, so every load scans the whole table.
Issue counts on larger repositories merge three single-column indices,
and the API pull request list loads the latest reviews with `id IN
(SELECT max(id) ... GROUP BY ...)`, which MySQL runs as a scan of every
review, about 1.1s on
https://gitea.com/api/v1/repos/gitea/gitea-mcp/pulls?limit=1.

- Index `access.repo_id`
- Index `team_repo.repo_id`
- Replace the single `repo_id`, `is_closed` and `is_pull` indices with
one `issue` index on `(repo_id, is_closed, is_pull)`
- Join the latest review ids instead of `id IN (subquery)`

Median times in ms on MySQL 8.4 with tables sized like gitea.com (1M
access rows, 500k issues, 200k reviews), for a copy of
https://gitea.com/gitea/gitea-mcp and a repository with 9.7k issues:

| Page | Before | After |
|---|---|---|
| `/{owner}/{repo}/issues` | 107 | 40 |
| `/{owner}/{repo}/pulls` | 106 | 32 |
| `/{owner}/{repo}/pulls?state=closed` | 153 | 35 |
| `/{owner}/{repo}/issues/{index}` | 141 | 60 |
| `/{owner}/{repo}/issues`, 9.7k issues | 303 | 53 |
| `/api/v1/repos/{owner}/{repo}/pulls?limit=50` | 180 | 46 |
| `/api/v1/repos/{owner}/{repo}/pulls?limit=1` | 137 | 16 |
| `/api/v1/repos/{owner}/{repo}/issues`, 9.7k issues | 248 | 43 |

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-31 18:24:15 +01:00
Giteabotandsilverwind 6f5d66f6dd fix(pull): keep review body in sync when its comment is edited (#40058) (#40133)
Backport #40058 by @silverwind

A review's text is stored on both the review and its timeline comment,
but editing it in the web UI or through the issue comment API only
updated the comment. The reviews API kept returning the old body
afterwards.

Extracted out of https://github.com/go-gitea/gitea/pull/39865.

*Written by Claude.*

Co-authored-by: silverwind <me@silverwind.io>
2026-10-31 18:21:03 +01:00
a1c56dd96a fix(projects): hide linked pull requests the viewer cannot read (#40016) (#40134)
Backport #40016 by @basavaraj-sm05

Project board cards list the pull requests that reference their issue,
but loaded them without checking the viewer's access. A private pull
request mentioning a public issue showed its title, number and state on
that issue's board to anyone who can view the board, anonymous visitors
included.

Repository and organization boards now skip linked pull requests in
repositories where the viewer cannot read pull requests, matching how
the issue page already hides such references.

Co-authored-by: Basavaraj S m <basavaraj@digiscrypt.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: bircni <bircni@icloud.com>
2026-10-31 18:14:26 +01:00
5a3f06430d fix: harden context user access scope (#39945) (#40115)
Backport https://github.com/go-gitea/gitea/pull/39945

API tokens and Actions task tokens could see data beyond their scope
through user-scoped API routes and web profile pages.

- Actions task tokens authenticate as a shared system user with a
negative ID, which org, project and SSH/GPG key filters treated as "no
owner filter". These filters now check `!= 0`, so system users own
nothing. The `/api/v1/user` routes except `GET /api/v1/user` only accept
individual and bot accounts, and repo search no longer skips access
checks for the owner.
- `/{username}` only accepts tokens for RSS/Atom feeds. HTML profile and
org pages render as anonymous for token requests.
- User and org feeds require `read:user` or `read:organization` from
tokens, the same way repo feeds require `read:repository`.

`release/v28` lacks https://github.com/go-gitea/gitea/pull/39967, so
`/{username}` enables basic auth only for feed requests through a
middleware. The profile page token tests from
https://github.com/go-gitea/gitea/pull/39853 are folded into
`TestFeedUser`, because profile pages no longer accept tokens.

Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
2026-10-31 17:40:49 +01:00
bircniandsilverwind 6c8eaaf9e7 fix(actions): don't pick up jobs of repos with Actions disabled (#40106) (#40122)
Backport https://github.com/go-gitea/gitea/pull/40106

Repository-level and instance-level runners still picked up waiting jobs
of repositories with Actions disabled.
https://github.com/go-gitea/gitea/pull/30331 only covered owner-level
runners, by filtering them out in SQL.

Pickup now checks the Actions unit of each job it is about to claim, for
every runner scope, and cancels the job instead when Actions is
disabled, also honoring `DISABLED_REPO_UNITS`. Disabling Actions
therefore cancels its queued jobs once a matching runner polls, instead
of leaving them waiting until `ABANDONED_JOB_TIMEOUT`.

Co-authored-by: silverwind <me@silverwind.io>
2026-10-31 15:56:26 +00:00
GiteabotandLazizbek Ergashev c9e1805cc9 fix(pull): respect max approvers in default merge message (#40118) (#40119)
Backport #40118 by @lazerg

The approver limit was checked before writing each `Reviewed-by:` line
but compared with `>`, so the default merge message got one more trailer
than `DEFAULT_MERGE_MESSAGE_MAX_APPROVERS` allows (2 with a limit of 1,
11 with the default 10). It now stops once the limit is reached.

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

Assisted-by: Claude Code:claude-opus-5-5

Co-authored-by: Lazizbek Ergashev <lazerg2@gmail.com>
2026-10-31 15:07:42 +00:00
Giteabotandsilverwind 5ba69f9af4 fix(actions): run review-triggered workflows on the pull request head (#40099) (#40117)
Backport #40099 by @silverwind

Runs triggered by pull request reviews took their ref and commit from
the review's `commit_id` instead of the pull request. They now use the
pull request head like other pull request events, and their commit
status is set on the commit that ran.

Co-authored-by: silverwind <me@silverwind.io>
2026-10-31 13:13:39 +00:00
e395b4d9dc fix(mail): preserve outer spacing in notification emails (#40104) (#40111)
Backport #40104 by @0xJacky

Remove the `body {margin: 0; padding: 0}` reset from
https://github.com/go-gitea/gitea/pull/38396, which left HTML mails
flush against the edge in Apple Mail and Thunderbird. The devtest
preview stops injecting its own padding, so it matches delivered mails.

These clients render the mail as a document and apply the default 8px
body margin again, as with GitHub and GitLab notifications. Webmail
clients replace `<body>` and keep their own inset.

Fixes: https://github.com/go-gitea/gitea/issues/40103
Assisted-by: Codex:GPT-6.1, Claude:Opus 5.5
Co-authored-by: Jacky <jacky-943572677@qq.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-31 09:29:48 +00:00
308dd93484 perf: speed up the issue and pull request dashboards and fix table indices (#40001) (#40096)
Backport #40001 by @silverwind

The dashboard counts ran queries whose cost grew with the whole instance
instead of the viewing user. On https://gitea.com/pulls the two review
counts alone take about 1.4s per page load.

- Check the owner of the already joined repository with `EXISTS` instead
of listing every public repository and public user
- Hide superseded review requests with `NOT EXISTS` instead of grouping
every review on the instance
- Skip the review counts on `/issues`, which never shows them
- Index `team_user.uid` for permission and team lookups by user
- Fix indices for table `watch`, `package_property` and `action_run`,
fix #40090

Median page times in ms, before → after, on a synthetic dataset sized
like gitea.com (175k users, 40k repos, 120k issues, 1M comments):

| Page | SQLite | PostgreSQL 17 | MySQL 8.4 | MariaDB 13 |
|---|---|---|---|---|
| `/pulls` | 901 → 56 | 330 → 156 | 189 → 124 | 192 → 218 |
| `/pulls?type=review_requested` | 1797 → 66 | 557 → 254 | 307 → 245 |
604 → 499 |
| `/pulls?type=reviewed_by` | 1646 → 118 | 537 → 316 | 515 → 265 | 528 →
682 |
| `/pulls?type=mentioned` | 1403 → 46 | 426 → 119 | 165 → 116 | 179 →
179 |
| `/issues` | 923 → 33 | 293 → 39 | 178 → 65 | 140 → 41 |
| `/api/v1/repos/issues/search` (anonymous) | 870 → 110 | 144 → 104 |
123 → 87 | 170 → 193 |

MariaDB checks the owner per row on long scans across all public
repositories, which makes a few pages slower there.

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-31 07:38:45 +00:00
3162dc4cbf fix(api): answer 409 when repository files already exist on disk (#40095) (#40098)
Backport #40095 by @hktitof

when a repository directory exists on disk without a database row, for
example after a failed delete, POST /user/repos answered 500 with an
empty message

the migrate endpoint already answers 409 for this error and the fork API
got the same treatment in https://github.com/go-gitea/gitea/pull/39909,
so the create handler now maps ErrRepoFilesAlreadyExist to 409 too. this
covers POST /user/repos, POST /orgs/{org}/repos and the admin user route
since they all go through CreateUserRepo

the integration test creates the leftover directory and asserts 409, on
master it gets 500. runs on sqlite as usual

this also touches routers/api/v1/repo/repo.go, which several open PRs
modify, but none of them changes this error mapping

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Signed-off-by: hktitof <hktitof@users.noreply.github.com>
Co-authored-by: Abdellatif Anaflous <62770500+hktitof@users.noreply.github.com>
Co-authored-by: hktitof <hktitof@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-30 22:26:30 +01:00
51736a9706 fix(pull): refuse review comments on locked pull requests (#39960) (#40084)
Backport https://github.com/go-gitea/gitea/pull/39960

Locking a pull request stops users without write access from commenting,
but the review paths never checked the lock, so any signed-in user could
still post reviews, code comments and replies on a locked pull request.

The cherry-pick conflicted on the `repo-diff.ts` imports, because
`release/v28` has no `DiffFileTreeLocale`.

Co-authored-by: Basavaraj S m <basavaraj@digiscrypt.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-30 19:36:51 +00:00
silverwind 40079c7c09 fix(ui): ignore code line anchors below 1, show JS errors in vite dev mode (#39432) (#40087)
Backport https://github.com/go-gitea/gitea/pull/39432

- A `#L0` URL hash made `selectRange` index the line list at -1 and
throw inside `initRepoCodeView`, which aborted every init function after
it on the page. Line numbers below 1 now select nothing.
- The JS error filter only accepted stacks under the asset prefix, but
the vite dev server serves the sources from `/web_src/`, so dev mode
dropped every error without showing a banner. Outside production, the
filter now also accepts stacks from `/web_src/`.
2026-10-30 19:07:17 +00:00
Giteabot 2b11ba861f fix(repo): honor ALLOW_FORK_INTO_SAME_OWNER for organizations (#40025) (#40094)
Backport #40025 by @somaz94

With `[repository] ALLOW_FORK_INTO_SAME_OWNER = true` a user can fork
their own repository, but an organization still cannot fork a repository
it owns. The fork button check and the owner list on the fork page
compared the organization with the repository owner directly instead of
asking `CanUserForkBetweenOwners`, and the walk over parent repositories
dropped the owning organization whenever the setting was on.
Organizations now follow the setting the same way users already do, and
nothing changes when it is off.

Fixes https://github.com/go-gitea/gitea/issues/40021
Assisted-by: Claude Code:claude-opus-5-5
Co-authored-by: somaz <112675579+somaz94@users.noreply.github.com>
2026-10-30 18:06:59 +00:00
9ad9f99060 fix(user): answer the follow of a bot user with the profile card (#39980) (#40086)
Backport https://github.com/go-gitea/gitea/pull/39980

Following a bot user from its profile succeeded but answered with an
error, because the follow handler only rendered a response for
individuals and organizations.

The cherry-pick conflicted in `OwnerProfile`, which still serves the
user feeds on `release/v28`.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-30 07:30:13 +00:00
0964106988 fix: add missing access checks and size limits (#39942) (#40085)
Backport https://github.com/go-gitea/gitea/pull/39942

Several handlers and parsers skipped checks that their siblings already
perform.

- Reject issue, comment, label, milestone and release mutations in
archived repositories via the API with one guard per route group,
matching the web UI
- Hide timeline references and dependencies pointing at issues the doer
cannot read, in the web UI and the API, including pull requests of the
same repository without pull request access
- Reject workflow runs with more than `MaxJobNumPerRun` jobs, including
inline reusable workflow jobs, and show rejected pushes as invalid
workflow runs
- Bound how much of a Helm chart archive is inflated while looking for
`Chart.yaml`

`release/v28` lacks two helpers this needs, so they are included:
`insertInvalidWorkflowRun`, split out of `handleInvalidWorkflows` by
https://github.com/go-gitea/gitea/pull/39480, and
`packages.NewLimitedReader` from
https://github.com/go-gitea/gitea/pull/39366.

Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-30 08:08:32 +01:00
silverwindandBasavaraj S m 8cf0f9798f fix(feed): require code read access for the repo home branch feed (#39964) (#40067)
Backport https://github.com/go-gitea/gitea/pull/39964

The repo home URL served the branch or file commit feed for RSS or Atom
`Accept` headers without the code unit check of the explicit
`/rss/branch/*` routes, exposing commit messages and authors to users
without code access.

Co-authored-by: Basavaraj S m <basavaraj@digiscrypt.com>
2026-10-29 10:04:51 +00:00
5d737d660b perf(actions): is_reusable_caller index makes SQLite read nearly every job row (#40055) (#40064)
Backport #40055 by @myers

On SQLite, the `is_reusable_caller = false` filters (e.g. the admin job
queue page) are planned from `IDX_action_run_job_is_reusable_caller`,
which matches almost every row, so nearly the whole `action_run_job`
table is read. `ANALYZE` does not help: `sqlite_stat1` only keeps
average rows per key.

No query needs that index. `is_reusable_caller = true` is only used by
`expandedCallerCond`, always together with `repo_id` and
`concurrency_group`. Their plans use other indexes on SQLite, PostgreSQL
17 and MySQL 8.4 (PostgreSQL is slightly slower with it).

This removes the index tag. `SyncAllTables` drops the existing index on
the next start, so no migration is needed.

Job queue count on SQLite, before: a search on the `is_reusable_caller`
index; after: a `MULTI-INDEX OR` on the `status` and `pickup` indexes.
On an instance with about 7,000 jobs the admin job queue page went from
`Page: 784ms` to `39ms` (measured with an earlier revision of this PR
that gave the same plan).

Signed-off-by: Myers Carpenter <myers@maski.org>
Co-authored-by: Myers Carpenter <myers@maski.org>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-29 07:40:30 +01:00
silverwind 5be73fa93a fix: size mermaid iframe by the diagram's aspect ratio (#40046) (#40052)
Backport https://github.com/go-gitea/gitea/pull/40046

Mermaid scales diagrams wider than the markup column down to its width,
but the iframe kept the unscaled viewBox height, leaving a large blank
area below them. The iframe now uses a CSS `aspect-ratio` capped at the
viewBox height. Diagrams that don't scale (`useMaxWidth: false`) keep
the fixed height.

The cherry-pick conflicted only on the `srcdoc` line, which uses an
`html` template on `main` but a plain string on `release/v28`.

Fixes: https://github.com/go-gitea/gitea/issues/40045
2026-10-28 19:10:25 +00:00
silverwindandGiteabot 3ed5886066 fix(deps): update module golang.org/x/net to v0.62.0 [security] (#40050) (#40051)
Backport https://github.com/go-gitea/gitea/pull/40050

`release/v28` is still on x/net v0.60.0, so the cherry-pick conflicted.
x/net v0.62.0 raises the minimum x/crypto, x/sync, x/sys, x/text and
x/tools versions, which `go get` and `make tidy` bump to what `main`
already uses.

Co-authored-by: Giteabot <teabot@gitea.io>
2026-10-28 09:57:35 +01:00
12b5af068f fix(repo): use the viewed ref's editorconfig in file view, blame and editor (#39973) (#40019)
Backport #39973 by @Hello-Liar

The file view, blame and web editor read `.editorconfig` from the
default branch, so branch-specific settings like tab width were ignored.
The file view's middleware ran before the ref was resolved, and blame
never loaded it.

- `GetEditorconfig` reads the current commit, falling back to the
default branch only where no ref is resolved
- `Home` and `RefBlame` load the editorconfig after ref resolution

Fixes: https://github.com/go-gitea/gitea/issues/27413
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: Hello-Liar <80651389+Hello-Liar@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-26 22:39:01 +00:00
Giteabotandleo 8e83bb0e61 fix(markup): keep commit title emoji inside links (#39986) (#40032)
Backport #39986 by @Hello-Liar

Render emoji inside the default commit-title link so the whole title
remains clickable, including emoji-only titles. Explicit links and
mentions keep their own destinations.

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

Verified failing-before/passing-after native, shortcode, custom, and
emoji-only cases; markup/template tests and Go lint pass.
Browser-checked actual renderer output; blue outlines show link
boundaries.


![Before](https://github.com/user-attachments/assets/f56a4607-3265-4536-9fa2-ecc297dada6f)

![After](https://github.com/user-attachments/assets/7e0ad96d-6732-4d41-b7c6-cc28e90b747a)

AI-assisted by Codex (GPT-6); prepared and tested by the assistant.

Co-authored-by: leo <80651389+Hello-Liar@users.noreply.github.com>
2026-10-26 20:07:13 +00:00
Giteabotandpetervavrinecz 2042caf945 fix(issue): prevent redirect to external tracker when built-in tracker is enabled (#39997) (#40031)
Backport #39997 by petervavrinecz

Signed-off-by: Vavrinecz Péter <98645804+petervavrinecz@users.noreply.github.com>
Co-authored-by: petervavrinecz <98645804+petervavrinecz@users.noreply.github.com>
2026-10-26 19:38:30 +00:00
wxiaoguang 79189a959f fix: replace null char with space for issue/comment content (#40014) (#40034)
A simple workaround to fix
https://github.com/go-gitea/gitea/issues/40003

Backport of https://github.com/go-gitea/gitea/pull/40014
2026-10-27 03:14:59 +08:00
Giteabotandsilverwind 1b8a7c4d85 fix(git): default to the gc maintenance strategy (#40022) (#40029)
Backport #40022 by @silverwind

Git 2.54 made "geometric" the default auto-maintenance strategy. Pushes
wait for its foreground reflog expiry
(https://github.com/go-gitea/gitea/issues/39693), and its detached
repacks race with filesystem operations, as in
https://github.com/go-gitea/gitea/actions/runs/40236681252/job/121299686933.
Pin the pre-2.54 "gc" strategy, older git ignores it.

https://github.com/go-gitea/gitea/pull/39745 is the more complete fix
which disables the auto-maintenance.

Co-authored-by: silverwind <me@silverwind.io>
2026-10-26 19:43:11 +01:00
dbe80ac05c fix: Fix OneDev migration with OneDev 16.8 and newer (#40017) (#40026)
Backport #40017 by @lunny

OneDev 16.8 moved the server version endpoint from
`/~api/version/server` to `/~api/server/version`. Both are tried, the
new one first, and both errors are reported when neither works.

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-26 13:54:28 +00:00
silverwind 4b669ca620 fix(migrations): update gitea sdk to fix nil pointer panic (#39999)
`gitea.dev/sdk` v1.2.0 loses the error of a failed server version lookup
after the first call, so Gitea-to-Gitea migrations panic on the next
version check. v1.3.0 (https://gitea.com/gitea/go-sdk/pulls/855) keeps
returning the error. `main` already has v1.3.0 via
https://github.com/go-gitea/gitea/pull/39742.

Fixes: https://github.com/go-gitea/gitea/issues/39989
2026-10-24 23:57:14 +02:00
9d335e8783 fix(diff): load both blobs of renamed files for diff rendering (#39969) (#39992)
Backport #39969

Co-authored-by: schop-rob <49682405+schop-rob@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-24 14:05:09 +00:00
85a85878c7 fix: get valid UTF-8 for diff_hunk and composer readme (#39932) (#40000)
Backport #39932 by @fixfon

* Fixes #39929
* Fixes #39994

Co-authored-by: fixfon <12512757+fixfon@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-24 13:38:33 +00:00
ef2ceff6cd fix(actions): drop untrusted Content-Encoding from v1 artifact uploads (#39855) (#39996)
Backport #39855 by @basavaraj-sm05

Legacy (v1 to v3) artifact uploads stored the request's
`Content-Encoding` header in the column that v4 artifacts read as their
`Content-Type`.

- Store only the `gzip` marker on legacy uploads, the only encoding
their clients send
- Pick the PDF and audio/video serve headers only for content types that
`mime.ParseMediaType` accepts

Co-authored-by: Basavaraj S m <basavaraj@digiscrypt.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-24 15:06:46 +02:00
35a6f3d4e0 enhance(project): filter project milestones by linked issues (#39958) (#39984)
Backport #39958 by @SHIVANSHGARG07

The project milestone filter listed every milestone of the repo, or of
all accessible org repos for org projects, including ones with no issue
in the project. That made the dropdown noisy and unhelpful.

Both repo and org projects now list only milestones that have at least
one issue linked to the project, via a new `ProjectID` option on
`FindMilestoneOptions`.

Fixes: https://github.com/go-gitea/gitea/issues/39150
Co-authored-by: Shivansh Garg <shivanshgarg587@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-24 06:24:46 +00:00
Giteabotandsilverwind cf3d325e45 ci(release): stop creating deployments for release tags (#39966) (#39971)
Backport #39966 by @silverwind

Each `release-create-tag` run created a deployment record, which makes
GitHub show a useless "This branch has not been deployed" box on every
PR:

<img width="819" height="113" alt="image"
src="https://github.com/user-attachments/assets/129c8348-1d70-46b9-8996-8872c84a11f2"
/>

With `deployment: false`, the `release-signing` approval and branch
policy still apply.

Co-authored-by: silverwind <me@silverwind.io>
2026-10-23 12:12:33 -07:00
Lunny Xiaoandsilverwind b140311d54 perf(actions): cache successful runner token verification (#39934) (#39959)
Backport of https://github.com/go-gitea/gitea/pull/39934 to release/v28.

Runner token verification (PBKDF2) dominated CPU on instances with many
runners. Successful verifications are now cached in a dedicated LRU.
Conflict resolved by keeping v28's connect v1 interceptor and adding
only the cache.

_Backport prepared with Codet._

Co-authored-by: silverwind <me@silverwind.io>
2026-10-23 02:36:43 -07:00
Giteabotandsilverwind 2b0aa6cfd2 docs: clarify that the team packages permission covers all org packages (#39953) (#39956)
The Packages row in the team settings read "Manage repository
packages.", but packages belong to the owner, so a team's Packages
permission applies to every package of the organization, regardless of
the team's repositories. Reword it to say so. The docs get the same
clarification in https://gitea.com/gitea/docs/pulls/605.

Co-authored-by: silverwind <me@silverwind.io>
2026-10-23 00:13:33 +00:00
silverwindandbircni 9d1d5ceddb fix(actions): bound requested artifact retention (#39894) (#39946)
`actions/upload-artifact` v4 and later send an absolute `expires_at`
computed with the runner's clock, which Gitea stored unchecked. A runner
clock running behind made artifacts expire at the next cleanup, and
workflows could keep artifacts longer than `ARTIFACT_RETENTION_DAYS`.

- Requested expiries are bounded by Gitea's clock, at least 1 day and at
most `ARTIFACT_RETENTION_DAYS` when it is not `0`. Longer
`retention-days` requests now get the instance limit.
- `github.retention_days` now carries `ARTIFACT_RETENTION_DAYS`, so
upload actions cap `retention-days` themselves and log a warning, like
on GitHub.

Co-authored-by: bircni <bircni@icloud.com>
2026-10-22 23:21:37 +00:00
silverwind 9141a6386f ci: update giteabot to v1.3.1 (#39947) (#39948)
Backport https://github.com/go-gitea/gitea/pull/39947

PRs targeting `release/v28` run this branch's `giteabot.yml`, and
`v1.3.1` runs the merge queue when such a PR merges, so the branch needs
the same pin. `giteabot-backport.yml` gets it too to keep the branches
in sync.
2026-10-22 22:56:30 +00:00
a814cf7567 fix(api): adding issue labels by name attaches both the repo and the org label (#39873) (#39951)
Backport #39873 by @nazem-droubi

Fixes #39651

When an org-owned repo has a repo label and an org label with the same
name, `POST`/`PUT /repos/{owner}/{repo}/issues/{index}/labels` with
label names attached both of them.

With this change the repo label is used when the names collide, and org
labels are only looked up for the names that didn't match a repo label.
Names that only exist at the org level still resolve to the org label as
before. I documented the behavior on `IssueLabelsOption.Labels` and
regenerated the swagger files.

Picking the repo label seemed the least surprising choice since it's the
more specific one, but I'm happy to change it (for example to return a
422 on ambiguous names) if you'd prefer that.

Tests: added `TestAPIAddIssueLabelsWithSharedRepoAndOrgLabelName`, which
covers both POST and PUT. It fails on `main` (3 labels attached instead
of 2) and passes with this change. I ran the existing add/replace label
integration tests locally on sqlite, plus `go vet` and gofmt.

AI disclosure: the code and test in this PR were written primarily by an
AI coding agent working on my behalf. I'm happy to walk through it or
revise anything during review.

Signed-off-by: Nazem <214302554+nazem-droubi@users.noreply.github.com>
Co-authored-by: Nazem <nazem98@gmail.com>
Co-authored-by: Nazem <214302554+nazem-droubi@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-22 21:40:40 +00:00
GiteabotandBasavaraj S m 2fd12d5f76 fix(feed): hide release notes in tags feed without releases access (#39944) (#39949)
Backport #39944 by @basavaraj-sm05

The tags feed (`tags.rss` / `tags.atom`) lives in the tags route group,
which only requires code read access, yet it lists every published
release next to the plain tags and includes each release's title and
rendered note. The releases page and `releases.rss` require releases
read access, and the tags page only shows tag names to users without it,
so the feed is the one place that leaks. A team member granted Code but
not Releases on a private repo, or anyone on a public repo whose
Releases unit is disabled, can read all release notes this way. I
noticed it while comparing the feed routes with the tags template. When
the doer cannot read releases, the feed now lists releases as plain
tags, linking to `/src/tag/` like the tags page does.

Co-authored-by: Basavaraj S m <basavaraj@digiscrypt.com>
2026-10-22 14:06:27 -07:00
640b528580 fix(migrations): use cursor pagination for GitHub issue migration too (#39888) (#39941)
Backport #39888 by @yuhongwei380

For repos with many issues, the GitHub API only allows cursor-based
pagination instead of page-based pagination after a certain page.
A full migration of such repos failed with a 422 response.
Now, we dynamically switch how we migrate based on the response from the
GitHub API.

Fixes #39882
Assisted-by: Codex:GPT-6

Signed-off-by: Phoenix <44333391+yuhongwei380@users.noreply.github.com>
Co-authored-by: Phoenix <44333391+yuhongwei380@users.noreply.github.com>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-22 13:33:39 +00:00
3e5256b6ca fix(api): check unit read permission in issue sub-resource endpoints (#39885) (#39938)
Backport #39885 by @basavaraj-sm05

Several `/repos/{owner}/{repo}/issues/{index}/...` API endpoints load
the issue by index without checking
`CanReadIssuesOrPulls(issue.IsPull)`, unlike `GetIssue`,
`ListIssueComments` and `GetIssueReactions`. The route group's
`mustEnableIssuesOrPulls` passes when the caller can read either unit,
so someone who can read issues but not pull requests (a team with only
the Issues unit, or per-unit anonymous access on a public repo) can read
a pull request's whole conversation through `/timeline`, plus its
labels, subscribers, attachments and tracked times, and can subscribe to
it. The reverse holds for issues.

These handlers now return 404 in that case, like their siblings.

Co-authored-by: Basavaraj S m <basavaraj@digiscrypt.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-22 05:27:46 -07:00
3996c2c520 fix(issues): keep one row per issue when sorting by exclusive label order (#39364) (#39928)
Backport #39364 by @shoemoney

Sorting by an exclusive label scope joined `issue_label`, returning one
row per label. Issues with several labels were listed repeatedly and
pushed others off the page, and `IssueIDs` returned inflated totals.

The sort key now comes from a correlated subquery, so each issue yields
one row.

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

This base-branch update was prepared with AI assistance.

Co-authored-by: Jeremy Schoemaker <jeremy@shoemoney.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-22 00:22:16 -07:00
716295015b fix(oauth2): decode form-encoded client credentials in HTTP Basic auth (#39926) (#39931)
Backport #39926 by @fixfon

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

RFC 6749 §2.3.1 has clients form-urlencode the client id and secret
before building the HTTP Basic header. `openid-client` with
`client_secret_basic` does this and sends `-` as `%2D` and `_` as `%5F`.
The token and introspection endpoints used the raw header values, so
these clients failed with `invalid_client`. Both endpoints now decode
the values with `url.QueryUnescape` before the client lookup, the secret
check and the body consistency check, and reject a value that does not
decode. Plain HTTP Basic parsing in `httpauth` is unchanged, since the
encoding only applies to OAuth client authentication.

Clients that don't encode are unaffected, since no client id or secret
Gitea has ever issued contains `+` or `%`.

Co-authored-by: fixfon <12512757+fixfon@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-22 06:22:15 +00:00
Giteabotandsilverwind f50a5d0dab test(e2e): enable async clipboard testing pref in Firefox (#39838) (#39927)
Backport #39838 by @silverwind

When `navigator.clipboard.readText()` runs before the page's copy has
landed, Firefox shows its "Paste" prompt, which cannot be interacted
with when headless. Avoid that and and use
`dom.events.testing.asyncClipboard` which is a pref meant for testing
that disabes this prompt.

Fixes: https://github.com/go-gitea/gitea/actions/runs/39113819632/job/117635434106
Co-authored-by: silverwind <me@silverwind.io>
2026-10-21 22:14:01 -07:00
Shudhanshu Singh bc3e228e78 fix(api): keep milestone due date when it is omitted in edit request (#39869) (#39921)
Backport https://github.com/go-gitea/gitea/pull/39869

`PATCH /repos/{owner}/{repo}/milestones/{id}` always overwrote the due
date with `due_on`, so a request that only changed other fields, such as
`{"state": "closed"}`, silently removed it.

The due date is now kept when `due_on` is omitted, and `"due_on": null`
or `"due_on": ""` remove it.

Only the milestone fix is backported. The issue and pull request
deadline changes of https://github.com/go-gitea/gitea/pull/39869 build
on https://github.com/go-gitea/gitea/pull/38677, which isn't on v28.
2026-10-21 17:59:21 +02:00
193c676946 fix(lfs): send the default branch as ref in batch download requests (#39891) (#39920)
Backport #39891

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

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Shudhanshu Singh <sudhanshuwriterblc@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-21 10:59:03 +00:00
977c9181ad fix(api): return 4xx for invalid fork repository names (#39909) (#39919)
Backport #39909

Fixes #39908

Signed-off-by: Ferdinand-MZ <ferdinandmaulanazafauzi@gmail.com>
Co-authored-by: Ferdinand Maulana Za Fauzi <101077118+Ferdinand-MZ@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-21 10:40:04 +00:00
197e761471 enhance(webhook): mention release assets in notifications (#39631) (#39912)
Backport #39631 by @stackedbyaradhya

Release webhook notifications never mention attached files, although the
payload carries them.

The summary shared by all webhook providers now appends an asset count
like `(assets: 2)` for created and updated releases.

Closes https://github.com/go-gitea/gitea/issues/39630

Co-authored-by: Aradhya Jain <aradhyaj736@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-21 07:05:55 +00:00
01db50f0bf fix: enforce public-only token scope on more routes (#39690) (#39853)
Partial backport of https://github.com/go-gitea/gitea/pull/39690 with
only its public-only token checks, without the refactor.

1. Admin, org owner and repo or org creation APIs reject public-only
tokens.
1. Profile and org pages hide private repos and limited owners from
public-only tokens.
1. Heatmap API, OIDC userinfo and OCI blob mounts respect public-only
tokens.

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
2026-10-21 05:58:07 +00:00
4b743d2e8c fix(packages): reject newline-bearing rubygems dependency metadata (#39782) (#39900)
Backport #39782 by @basavaraj-sm05

The RubyGems compact index (the shared `/versions` file and per-package
`info/<name>` files) is line-based.
https://github.com/go-gitea/gitea/pull/38061 rejected line breaks in the
gem name, but the platform, dependency names and version requirements
from the gemspec still accepted them, letting an uploader forge index
lines for bundler clients.

The parser now rejects a line break in any of those fields. The Marshal
based endpoints are length prefixed and unaffected.

Co-authored-by: Basavaraj S m <basavaraj@digiscrypt.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-21 05:24:52 +00:00
silverwind 31653bdb34 ci: update giteabot to v1.3.0 (#39910) (#39911) 2026-10-21 04:56:59 +00:00
Giteabotandwxiaoguang 7365946851 fix: use clear error message when failed to parse git version (#39898) (#39903)
Backport #39898 by @wxiaoguang

fix  #39892

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-21 04:35:32 +00:00
Giteabotandsilverwind 254e18107e enhance: improve hover border of colored button groups (#39861) (#39899)
Backport #39861 by @silverwind

Hovering a button in a primary or red button group showed a grey frame,
because the generic group hover border applied to colored groups too.
Green groups had no colors at all.

Colored groups now let the hover background fill the border, and green
groups get the same colors as primary and red. The devtest page shows
all three.

<img width="569" height="304" alt="Hovered buttons in uncolored,
primary, red and green button groups, before and after"
src="https://github.com/user-attachments/assets/e0fb6b16-f1f1-459b-9848-9386cd9453c9"
/>

Extracted from https://github.com/go-gitea/gitea/pull/38404

Co-authored-by: silverwind <me@silverwind.io>
2026-10-21 04:03:35 +00:00
Giteabotandsilverwind 290568a07b fix: off-by-one errors in code comment patches (#39887) (#39904)
Backport #39887 by @silverwind

Code comments next to a diff hunk could show one line too high in the
conversation view vs. in the code view that showed them on the correct
line.

- Treat the hunk end in `CutDiffAroundLine` as exclusive, so the line
after a hunk falls back to the unchanged-line context
- Don't count `\ No newline at end of file` markers as lines when
cutting the hunk
- Share the unchanged-line fallback with migrated review comments

Co-authored-by: silverwind <me@silverwind.io>
2026-10-20 20:37:19 -07:00
bircniandGiteabot b758396619 chore(deps): update go toolchain directive to v1.27.3 (#39881) (#39896)
Backport of https://github.com/go-gitea/gitea/pull/39881.

Go 1.27.3 includes a crypto/x509 security fix that distrusts a set of
intermediate certificates (https://github.com/golang/go/issues/82345),
so release builds need the new toolchain.

Co-authored-by: Giteabot <teabot@gitea.io>
2026-10-20 14:04:10 -07:00
Giteabotandwxiaoguang 9bc4ce88ee fix: prevent system users from watching a repository (#39886) (#39895)
Backport #39886 by @wxiaoguang

fix #39884

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-20 20:27:42 +00:00
GiteabotandShivansh Garg 9bb9a51623 fix(api): skip label lookup in ListIssues when no labels filter given (#39890) (#39893)
Backport #39890 by @SHIVANSHGARG07

strings.Split("", ",") returns []string{""} (length 1), not an empty
slice, so the previous len(splitted) > 0 guard was always true and never
skipped the label ID lookup, even when the labels query param was
absent. Check the raw trimmed param for emptiness before splitting,
mirroring the pattern already used elsewhere in this file.

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

Co-authored-by: Shivansh Garg <shivanshgarg587@gmail.com>
2026-10-20 12:19:50 -07:00
wxiaoguang 5838cc8172 fix(pulls): tolerate invalid stored review commit_id (#39849) (#39857)
Simple backport for #39849 to fix #39848
2026-10-21 01:46:56 +08:00
829b49c1ae fix: add missing checks to several handlers and models (#39839) (#39847)
Backport https://github.com/go-gitea/gitea/pull/39839 by @silverwind

Several handlers and model helpers skipped checks that their siblings
already perform.

- Only match principal keys when looking up SSH certificate principals
- Return no attachments for an unsaved issue instead of every issue-less
one
- Reject issue and comment attachments when adding release attachments
- Share one permission check for editing, deleting and reacting to issue
content and for comment attachments between the web UI and the API,
refusing them on locked issues for users who can't write
- Reject issue edits, deletion and label changes in archived
repositories via the API
- Apply cross-origin protection to the repository topics endpoint
- Return 404 for the repositories tab of individual users and respect
profile visibility for the heatmap

* Fixes: https://github.com/go-gitea/gitea/issues/39226

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
2026-10-18 19:50:36 +02:00
58031cdc97 enhance: allow custom watches to bypass on-mention email restriction (#39652) (#39837)
Backport #39652 by @Ubayed-Bin-Sufian

Users with "Only Email on Mention" can now still receive email for the
events they picked in a custom repository watch, covering issues, pull
requests and releases. The setting is renamed to "Only Email on Mention
and Custom Watches" to make this explicit, matching GitHub's
"Participating, @mentions and custom".

"All activity" watches don't bypass the setting, because they are also
created automatically by `AUTO_WATCH_NEW_REPOS` for repository creators
and team members. To keep picking every event distinct from "All
activity", the custom watch dialog now stores a new `WatchModeCustom`.
Existing custom watches keep working without a migration.

Addresses: https://github.com/openSUSE/openSUSE-git/issues/183
Ref: https://github.com/go-gitea/gitea/issues/35492
Co-authored-by: Ubayed Bin Sufian <ubayedbinsufian@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
2026-10-18 12:45:52 +00:00
1eee73e9bd fix(mailer): reject incoming email tokens of users who cannot sign in (#39833) (#39841)
Backport #39833 by @basavaraj-sm05

Incoming email reply tokens stay valid for a year and are verified with
`user.Rands`, which is not rotated when an account is deactivated or
prohibited from signing in. A suspended user could therefore keep
commenting on public repos by replying to old notifications.

`DecodeToken` now rejects tokens of organizations, inactive users and
users prohibited from signing in, which covers every incoming mail
handler.

Co-authored-by: Basavaraj S m <basavaraj@digiscrypt.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-18 12:28:08 +00:00
Giteabotandwxiaoguang dc30660291 fix: use correct template variable on milestone page (#39834) (#39843)
Backport #39834 by @wxiaoguang

* fix #39828

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-18 11:03:40 +00:00
6bc0e8cd57 fix(pull): recover PRs whose recorded merge commit never reached the base repo (#39831) (#39840)
Backport #39831 by @lunny

When pushing the merge commit to the base repo fails (e.g.
non-fast-forward), the merge intent recorded by
https://github.com/go-gitea/gitea/pull/39442 is left behind.
`merged_commit_id` then points to a commit that does not exist in the
base repo, and `git branch --contains` fails with `no such commit`, so
the PR can never be checked or merged again.

- Clear the recorded merge intent if the push fails and the commit is
not on the base branch
- `IsCommitInBranch` now returns false for a missing commit, so
already-stuck PRs recover on the next check

Fixes: https://github.com/go-gitea/gitea/issues/39824
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-18 03:24:11 -07:00
wxiaoguang cfcbdf41e2 fix: prevent Gitea from crashing by notification panic (#39835) 2026-10-18 11:11:15 +02:00
GiteabotandShivansh Garg 6195cda58f fix(actions): bound request body size in artifacts v4 twirp handlers (#39818) (#39822)
Backport #39818 by @SHIVANSHGARG07

parseProtobufBody read the full Twirp request body via io.ReadAll with
no size limit, allowing an authenticated runner token holder to force
unbounded memory allocation. Cap it at 1 MiB via io.LimitReader,
matching the existing pattern in job_summary.go.

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

Co-authored-by: Shivansh Garg <shivanshgarg587@gmail.com>
2026-10-17 23:17:45 -07:00
silverwind 5251cd572f fix(actions): keep folded block scalars intact across serialization (#39787) (#39817)
Backport https://github.com/go-gitea/gitea/pull/39787 by @lunny,
including the go-yaml bump. The runner counterpart is
https://gitea.com/gitea/runner/pulls/1321.

Folded (`>`) scalars in workflows gained blank lines and had spaces
turned into newlines every time Gitea re-serialized a job.

- go-yaml main fixes the accumulating blank lines
(https://github.com/yaml/go-yaml/pull/366) and the block scalar
indentation indicators (https://github.com/yaml/go-yaml/pull/414), so
`BlockSafeString` goes away
- The job encoder no longer wraps lines, because go-yaml still wraps
more-indented lines of folded scalars and turns a space into a newline

Fixes https://github.com/go-gitea/gitea/issues/39775
2026-10-17 03:15:42 +00:00
Giteabotandsilverwind ce8912c8ff ci: update giteabot to v1.2.1 (#39815) (#39820)
Backport #39815 by @silverwind

Bump `giteabot` from `v1.1.0` to
[`v1.2.1`](https://github.com/go-gitea/giteabot/releases/tag/v1.2.1). It
now runs on the `node24` action runtime instead of setting up Deno
(https://github.com/go-gitea/giteabot/pull/23), and a reviewer who
requested changes can lift `lgtm/blocked` by re-requesting their own
review (https://github.com/go-gitea/giteabot/pull/24).

Co-authored-by: silverwind <me@silverwind.io>
2026-10-17 04:58:14 +02:00
Giteabotandbircni 0e2a188d8e enhance(oauth): pre-register a builtin OAuth2 application for gitea-vscode (#39785) (#39801)
Backport #39785 by @bircni

The [Gitea VS Code extension](https://gitea.com/gitea/gitea-vscode)
currently signs in through the builtin `tea` OAuth2 application. Gitea
keeps one grant per user and application, so anyone who has already
signed in with the tea CLI gets `a grant exists with different scope`
when the extension requests its own scopes, and sign-in fails
(gitea/gitea-vscode#24).

This adds a dedicated builtin application for the extension, following
the same mechanism as the existing CLI entries:

| | |
|---|---|
| client ID | `75a9ec3e-42b5-40cb-9144-9ea96ecd56a6` |
| config name | `gitea-vscode` |
| display name | `gitea-vscode` |
| redirect URIs | `http://127.0.0.1`, `https://127.0.0.1` (loopback, any
port) |

It is enabled by default via `[oauth2].DEFAULT_APPLICATIONS`. Instances
that set that option explicitly need to add `gitea-vscode` to use it.

The extension will use this client on Gitea >= 28.2.0 and keep falling
back to `tea` on older servers, so this should be backported to v28.

Co-authored-by: bircni <bircni@icloud.com>
2026-10-16 12:40:16 -07:00
95d6b572fe fix(pull): don't auto-detect empty pull requests as manually merged (#39807) (#39812)
Backport #39807 by @gomitrah

With "Autodetect manual merge" enabled, an empty pull request (one with
no commits of its own, for example opened to discuss a branch) was
closed as manually merged as soon as it was checked. Its head commit is
already an ancestor of the base branch, so the detection took the
"fast-forwarded" path and recorded the head itself as the merge commit,
even though nothing was merged.

Detection now skips a PR whose head commit is its merge base, the same
condition Gitea already uses to show a PR as "already included in the
target branch". Real manual merges are still detected, including when
the base branch and the PR branch are pushed together.

The merge base is refreshed on recheck, so a fast-forward manual merge
whose merge base was already refreshed to the head is not detected
either. This only misses a detection and never closes a PR wrongly;
telling the two apart would need a value stored at PR creation.

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

Co-authored-by: Mitrahsoft <bala.c@mitrahsoft.in>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-16 21:22:02 +02:00
c4e9d96162 fix(pull): fix code context of review comments on unchanged lines (#39799) (#39805)
Backport #39799

Fixes #39794

Co-authored-by: fixfon <12512757+fixfon@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-16 11:53:17 +00:00
96cb6cd16f fix(webauthn): accept unsolicited client extension outputs from WebKit clients (#39792) (#39806)
Backport #39792

Fixes #39790

Signed-off-by: choksi2212 <choksi2212@users.noreply.github.com>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Manas Choksi <162600900+choksi2212@users.noreply.github.com>
Co-authored-by: choksi2212 <choksi2212@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-16 09:28:24 +00:00
silverwindandwxiaoguang 02b18a0c8e fix: skip commit-graph write when fetching remote commits (#39798)
Since https://github.com/go-gitea/gitea/pull/39607, creating a PR
fetches the head commit into the base repo. Gitea's gitconfig sets
`fetch.writeCommitGraph=true`, so that fetch also writes a commit-graph
and fails with exit 128 when a stale `commit-graph-chain.lock` exists.
This adds `--no-write-commit-graph`, the only part of
https://github.com/go-gitea/gitea/pull/39583 that v28 needs. Its other
flags need git 2.29, while v28 supports git 2.25.

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

This PR was written with Claude.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-15 14:13:52 +00:00
GiteabotandZettat123 1ffe46d2a4 fix(actions): fix schedule cleanup and default branch switch (#39740) (#39786)
Backport #39740 by @Zettat123

Cancelling running schedule runs has not worked since
https://github.com/go-gitea/gitea/pull/35767: callers pass a branch name
like `main`, while schedule runs store the full ref like
`refs/heads/main`. Disabling Actions, archiving the repository or
changing the default branch left them running.

Fixing the ref alone would also cancel them on every push to the default
branch, which GitHub does not do, and whenever the Actions setting is
saved, so those paths now only replace the schedules.

Changing or renaming the default branch deleted the schedules without
registering the new branch's ones
(https://github.com/go-gitea/gitea/pull/28691), so they stopped until
the next push, and the API path kept the old ones. Both now switch the
schedules to the new default branch, since scheduled workflows only run
on the default branch.

Only the push case was verified on GitHub, as its scheduled workflows
have rarely been triggering lately.

Co-authored-by: Zettat123 <zettat123@gmail.com>
2026-10-14 22:47:59 +00:00
26f537dea6 fix(gitdiff): unquote C-quoted paths from git diff-tree --raw (#39781) (#39783)
Backport #39781 by @SudhanshuMatrix

## What

`parseGitDiffTreeLine` (`services/gitdiff/git_diff_tree.go`) read
`BasePath`/`HeadPath` straight from `git diff-tree --raw` output without
undoing git's C-quoting, unlike its sibling `parseLsTreeLine`
(`modules/git/parse.go`), which already does this with
`strconv.Unquote`.

This fix applies the same unquoting to both paths in the diff-tree
parser.

## Why

With the default `core.quotePath=true`, git C-quotes **any path
containing a non-ASCII byte** — not just tabs/newlines/backslashes. That
means any file with a CJK, Cyrillic, accented-Latin, or emoji name gets
octal-escaped, e.g.:

```
:100644 100644 b6fc4c6... 04fea06... M	"\346\227\245\346\234\254\350\252\236.txt"
```
(`日本語.txt`, confirmed against real `git diff-tree --raw` output)

`GetDiffTree` backs the file-tree sidebar on the commit, compare, and
pull request diff pages (`routers/web/repo/commit.go`, `compare.go`,
`pull.go`). Without unquoting, any such file shows its mangled, escaped
name instead of the real one, and the wrong path can throw off
jump-to-file and viewed-file-state behavior that key off the path.

Co-authored-by: Shudhanshu Singh <sudhanshuwriterblc@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-14 20:35:32 +00:00
GiteabotandYunus Ozen 74e7e813c3 fix(user): don't match a commit with an empty author email to a user (#39777) (#39780)
Backport #39777

Co-authored-by: Yunus Ozen <94426948+ozenyunus35@users.noreply.github.com>
2026-10-14 21:00:10 +02:00
Giteabotandsilverwind 3f0aa7a01e fix(markup): restore code copy newline strip and fix copying rendered blocks (#39755) (#39765)
Backport #39755 by @silverwind

1. Fix JS error when copying Mermaid and Math block sources, regression
from https://github.com/go-gitea/gitea/pull/39718.
1. Restore the newline trim which is consistent with the majority of
forges and markdown renderers as per
https://github.com/go-gitea/gitea/pull/39751#issuecomment-6129002625 and
my strong preference.

This is the minimal fixes to fix the bug and restore the existing
behaviour, more discussions and followups can be had in
https://github.com/go-gitea/gitea/pull/39751.

Co-authored-by: silverwind <me@silverwind.io>
2026-10-13 23:43:12 -07:00
7e1e32b42e fix(lfs): validate OID before building filesystem object path (#39762) (#39766)
Backport #39762

Co-authored-by: Basavaraj S m <basavaraj@digiscrypt.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-13 17:19:59 -07:00
63669003eb fix(markup): resolve and proxy srcset links of <source> elements (#39708) (#39756)
Backport #39708

Co-authored-by: Basavaraj S m <basavaraj@digiscrypt.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-13 15:46:51 +08:00
Giteabotandwxiaoguang f5fd936cbb fix: correct label deletion and release attachment load (#39743) (#39752)
Backport #39743 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-13 00:53:02 +00:00
wxiaoguang d87bbd3dd4 fix: validate "state" for OpenID (#39642) (#39744) 2026-10-13 08:26:37 +08:00
7a03ec3a4c fix(issues): allow selecting a tag in the issue Branch/Tag dropdown (#39747) (#39753)
Backport #39747 by @AliceLafox

* Fixes #39746
* Fixes #35505

The click handlers were bound only to the first `.reference-list-menu`
(branches), so clicking a tag did nothing. Bind them to the items of
both lists.

Co-authored-by: Alisa Lafoks <alice@lafox.net>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-12 18:29:43 +00:00
Giteabotandsilverwind 0132cb82c2 fix(packages/npm): restore former tarball URLs (#39681) (#39749)
Backport #39681 by @silverwind

Gitea changed the `dist.tarball` it advertises for already published
versions twice:

| Gitea | `dist.tarball` path of `@scope/name@1.0.0` |
|---|---|
| 1.27 | `%40scope%2Fname/-/1.0.0/name-1.0.0.tgz` |
| 28.0, https://github.com/go-gitea/gitea/pull/39061 |
`@scope%2Fname/-/1.0.0/name-1.0.0.tgz` |
| 28.1, https://github.com/go-gitea/gitea/pull/39434 |
`@scope/name/-/name-1.0.0.tgz` |

pnpm 11.5.2+ verifies the tarball URLs pinned in its lockfile against
the registry metadata, so frozen installs of lockfiles written against
1.27 fail with `ERR_PNPM_TARBALL_URL_MISMATCH`.

- Versions uploaded before this PR get their 1.27 URL back.
- New versions store their canonical tarball path at upload, so later
URL changes can't reach published versions. The host still follows the
request.

Lockfiles written against 28.0 for scoped packages, or by pnpm 12 with
`@scope:registry` against 28.1, need those pinned `tarball:` entries
removed, as 1.27 lockfiles of the same versions need the other URL.

Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-12 20:10:28 +02:00
wxiaoguang 33aaf0cfa3 fix(auth): prevent competing remember-me token rotations (#39686) (#39688)
Partially backport #39686
2026-10-12 09:48:37 +00:00
6064554b8d fix(packages): copy selected Debian installation values (#39718) (#39734)
Backport #39718

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

Make "copy code" button work with changed content.

Co-authored-by: Ehsan-hash <150884269+Ehsan-hash@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-11 11:33:37 +02:00
d29364a4f6 fix(actions): harden log cursor, activity type and actor filter handling (#39721) (#39731)
Backport #39721 by @bircni

Fixes a few Actions bugs where user input or nondeterminism caused
failures:

- The job log view indexed task steps with the client-supplied cursor
step without a bounds check, so an out-of-range value panicked and
returned a 500.
- Workflow activity type filters (`types:`) were compiled with
`glob.MustCompile`, so an invalid pattern in a workflow file panicked
during event detection. Invalid patterns now simply don't match.
- Listing workflow runs with an unknown `actor` returned a 500; it now
returns 404.
- The v4 artifact `ListArtifacts` response was built by ranging over a
map, so its order changed between calls. It now follows database order.
- The error from marshalling a workflow job when inserting run jobs was
silently ignored.

Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-10 20:23:39 +00:00
Giteabotandsilverwind 998a06627e fix(git): match git config values exactly when syncing (#39728) (#39729)
Backport #39728 by @silverwind

Since https://github.com/go-gitea/gitea/pull/39703, Gitea fails to start
when its gitconfig holds a `gc.reflogExpire` other than `90` (e.g.
`30.days`), because `git config --unset-all` exits 5 when no value
matches. Value patterns were also unanchored regexps, so `90` also
removed `90.days`.

- Match values exactly in `configUnsetAll` and `configAddNonExist`
- Treat "nothing to unset" as success
- Use `--replace-all` in `configSet` so a key with multiple values no
longer fails startup

Co-authored-by: silverwind <me@silverwind.io>
2026-10-10 20:06:48 +02:00
250d18d5f6 fix(actions): settle cancelled reusable workflow runs stuck in cancelling (#39706) (#39717)
Backport #39706 by @benv666

Fixes #39702

**Root Cause:**

Cancelling a reusable workflow caller wrote it `cancelled` right away,
even while a child was still `cancelling`. When the runner acknowledged,
the caller did not change, so the run was never refreshed and stayed
`cancelling`.

SQLite hid this because the child is cancelled a second time, which
re-aggregates the caller, while MySQL reports no affected rows for that
unchanged update.

**Changes:**

- A caller now takes its status only from its children: it stays
`cancelling` until its last child finishes, and its final status then
refreshes the run.
- Cancelling a run whose jobs are all done only settles the run, which
did not wake the runs waiting for its concurrency group. It now does, so
force-cancelling an already stuck run also frees them.


NOTE: Code was AI-generated, but reviewed and tested by me.

Signed-off-by: BenV <165034+benv666@users.noreply.github.com>
Co-authored-by: BenV <165034+benv666@users.noreply.github.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
2026-10-10 09:13:47 -06:00
Giteabotandsilverwind c26daf578f fix(git): leave reflog expiry to git's defaults (#39703) (#39711)
Backport #39703 by @silverwind

Gitea's default `gc.reflogExpire = 90` is parsed by git as
`1990-<month>-<day>`, currently `1990-10-09`, so reachable reflog
entries never expired.

Since git 2.54, auto maintenance runs its reflog-expire task in the
foreground of every push, and its trigger ignores reachability, so busy
repos rerun `git reflog expire --all` on every push without pruning
anything, stalling large repos for over a minute.

- Stop setting `gc.reflogExpire` so git's own defaults apply, and remove
the `90` written by earlier versions
- Treat the legacy `[git.reflog] EXPIRATION` as days, as documented

The next push to each repo prunes the accumulated entries once.

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

Co-authored-by: silverwind <me@silverwind.io>
2026-10-10 00:36:00 -07:00
silverwind 5371e788fe fix(deps): update module golang.org/x/net to v0.60.0 [security] (#39695) (#39701)
Backport https://github.com/go-gitea/gitea/pull/39695

`go.sum` conflicted on older x/net `go.mod` hashes that `release/v28`
still lists. `make tidy` resolves it to the same change as on `main`.
2026-10-08 23:31:32 -07:00
GiteabotandShivansh Garg 0001e58e6b fix: OIDC discovery advertises unsupported id_token response_type (#39498) (#39689)
Backport #39498 by @SHIVANSHGARG07

The OIDC discovery document at `/.well-known/openid-configuration`
advertised `id_token` in `response_types_supported`, but
`/login/oauth/authorize` only implements the authorization code flow and
rejects any other response_type with `unsupported_response_type`. This
mismatch caused OIDC client libraries that rely on discovery to attempt
the implicit flow and fail silently.

This removes `id_token` from `response_types_supported` so discovery
matches actual server behavior, and adds an integration test asserting
`response_type=id_token` is rejected consistently.

Manually verified: rebuilt Gitea, registered an OAuth2 app, confirmed
`/.well-known/openid-configuration` no longer lists `id_token`, and
confirmed `/login/oauth/authorize?...&response_type=id_token` still
correctly returns `error=unsupported_response_type`.

Fixes #39482.

<!--
Before submitting:
- Target the `main` branch; release branches are for backports only.
- Use a Conventional Commits title, e.g. `fix(repo): handle empty branch
names`.
- Read the contributing guidelines:
https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md
- Documentation changes go to https://gitea.com/gitea/docs

Describe your change below and link any issue it fixes.
-->

Co-authored-by: Shivansh Garg <shivanshgarg587@gmail.com>
2026-10-08 18:57:06 +00:00
Giteabot 79c4237cb2 chore(deps): update go toolchain directive to v1.27.2 (#39685) (#39687)
Backport #39685 by @GiteaBot

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [go](https://go.dev/)
([source](https://redirect.github.com/golang/go)) | toolchain | patch |
`1.27.1` → `1.27.2` |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

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

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

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

---

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

---

This PR has been generated by [Mend Renovate
CLI](https://redirect.github.com/renovatebot/renovate).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMjEuNCIsInVwZGF0ZWRJblZlciI6IjQ0LjEyMS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->
2026-10-08 11:25:28 -07:00
Giteabotandbircni 20f73b2230 ci: skip reverted commits in release notes, publish tagged snaps to stable (#39657) (#39678)
Backport #39657 by @bircni

Release notes: revert commits and the commits they revert are now left
out of the git-cliff changelog. A step before git-cliff finds commits
whose subject starts with "revert", reads the PR numbers on their revert
lines, and writes both SHAs to `.cliffignore`.

Snap: since the move from Launchpad to GitHub Actions, every build was
uploaded to `latest/edge`, including the stable-grade build that
`part-gitea-pull.sh` makes for an unreleased tag. Launchpad used to
release those builds to candidate and stable automatically. Without
that, v28.0.0 had to be promoted by hand and v28.1.0 stayed in edge.
Stable-grade snaps now go to `latest/stable` and `latest/candidate`;
main builds still go to edge. Candidate has to be updated too because
the pull script compares against it.

Signed-off-by: bircni <bircni@icloud.com>
Co-authored-by: bircni <bircni@icloud.com>
2026-10-08 15:57:37 +02:00
175bc89bf9 fix: correct "go get" URL ssh scheme, fix form binding (#39674, #39528) (#39676)
* Backport #39674
* Fix form binding errors (found by #39528)
* Fix #39680

---------

Co-authored-by: Roland Singer <10167163+r0l1@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-08 12:30:59 +00:00
GiteabotandKShot 793e77b073 fix(webhook): keep line breaks in Telegram rich messages (#39650) (#39677)
Backport #39650 by @Kshot3000

Since the Telegram webhook switched to Bot API rich messages
(https://github.com/go-gitea/gitea/pull/38298), a bare newline in
`rich_message.html` is treated as insignificant whitespace, so
multi-line messages — issue and PR bodies, comments, push commit lists —
arrive in Telegram as a single paragraph. This restores the old layout
by converting line breaks (`\n`, `\r\n`, `\r`) to `<br>` in
`createTelegramPayloadHTML`, after sanitizing, covering every Telegram
payload type at once.

Verified: the new `Line breaks are kept in rich messages` test plus the
updated Push/Issue/IssueComment/PullRequest/Review expectations fail on
unpatched code (literal `\n` in the payload) and pass with the fix; the
full `services/webhook` package passes, gofmt/vet clean.

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

---
Tips welcome: PayPal kyleblake0659@gmail.com · BTC
3GnR7TWBXAB3pPztBWpNF4LMNEX5yX8vZK

Co-authored-by: KShot <kshot9000@gmail.com>
2026-10-08 13:31:11 +02:00
wxiaoguang 5018ae1b29 fix: correct websocket notification for change password page (#39671) (#39670)
* Backport #39671
* Fix #39669

And also fix a UI bug
2026-10-07 19:26:34 +00:00
c60b13b2dd fix(markup): display MathML has no space below it (#39663) (#39668)
Backport #39663 by @nschloe

Since MathML is allowed in markup (#36352, #38034, #39337), a
display-style `<math display="block">` sits flush against the text below
it: it gets no margin of its own, and markup paragraphs have
`margin-top: 0`. Display math rendered by KaTeX gets 16px above and
below.

This adds `math[display="block"]` to the markup block elements that get
`margin-top: 0; margin-bottom: 16px`, like `p`, `pre` and `table`.

To test, view a Markdown file containing

```markdown
Text before.

<math display="block">
<mi>x</mi><mo>=</mo><mn>1</mn>
</math>

Text after.
```

Before, the equation has 16px above and 0px below; after, 16px on both
sides.

Before:

<img width="1544" height="238" alt="before"
src="https://github.com/user-attachments/assets/318238d9-018c-4945-974c-91bd1b282ff2"
/>

After:

<img width="1544" height="283" alt="after"
src="https://github.com/user-attachments/assets/9afde2be-b5ed-4624-a33f-4933e7fae79b"
/>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Nico Schlömer <nschloe@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-07 18:23:27 +00:00
Giteabotandwxiaoguang 27bd022b34 fix: various bugs (#39661) (#39667)
Backport #39661

1. fix #39660: relax email validation
2. fix #39658: use "int64" instead of time.Duration (for JSON v2)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-07 10:11:58 -07:00
Giteabot 4ebd5e319b fix: show "merge" form for empty PR (#39640) (#39645) 2026-10-06 17:49:17 +00:00
d6945fb0b5 fix(models): chunk commit status queries to avoid SQLite expression depth limit (#39611) (#39638)
Backport #39611 by @Kshot3000

* Fixes #39606
* Fixes #39329

`GetLatestCommitStatusForRepoCommitIDs` built one `OR` condition per
commit SHA. Rendering a commit list of more than ~1000 commits (for
example a large pull request, via `processGitCommits`) therefore failed
on SQLite with `SQL logic error: Expression tree is too large (maximum
depth 1000)`.

The max-index lookup now queries SHAs in chunks of 500 with `IN`, and
the follow-up status fetch runs in chunks of 100 `(index, sha)` pairs,
so the expression depth stays bounded regardless of the commit count.
Results are unchanged.

Verified with a new regression test that calls the function with 2001
SHAs: it fails with the reported error on `main` and passes with this
change. `go test ./models/git/` passes.

—
Fix offered freely; tips welcome: PayPal kyleblake0659@gmail.com · BTC
3GnR7TWBXAB3pPztBWpNF4LMNEX5yX8vZK

Co-authored-by: KShot <kshot9000@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-06 14:13:54 +00:00
69a2bfcfbd fix(pull): refresh commits behind when an AGit pull request is updated (#39613) (#39625)
Backport #39613 by @dziulatex

Fixes #39598

When the base branch has "Block merge if the pull request is outdated"
enabled, an AGit pull request that fell behind could never be unblocked:
merging the base branch into the topic and pushing it again to the same
`refs/for/<branch>/<topic>` left the PR "blocked because it's outdated"
forever.

The outdated block reads the stored `commits_behind`. Pushing a branch
refreshes it for GitHub-flow PRs (`AddTestPullRequestTask`), but the
AGit update path in `services/agit` moved `refs/pull/N/head` without
recomputing it, so the value only changed when the base branch was
pushed, which is the event that makes a PR behind again.

The AGit update path now calls `syncCommitDivergence` in
`UpdateRefForAgit`, the same as AGit PR creation (`NewPullRequest`)
already does.

Signed-off-by: dziulatex <paweldziurasoftware@yahoo.com>
Co-authored-by: dziulatex <38464243+dziulatex@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-06 11:27:16 +00:00
Giteabotandwxiaoguang 15a0ea83ca fix: correct RemoveWithRetry error handling (#39619) (#39626)
Backport #39619 by @wxiaoguang

* Fix #39618
* Follow up #38588
* Remove unrelated errors

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-06 09:36:06 +00:00
30cc41ff97 fix(web): normalize content for edit history diff and fix comment history dropdown (#39616) (#39627)
Backport #39616 by @SergioBenitez

Editing a comment as follows:

```diff
# Deployment checklist

- Timeout: 30 seconds.
+ Timeout: 60 seconds.
Restart the worker before deploying.
Check the logs after deployment.
```

Gitea would render the history this way, before, inserting spurious new
lines:

<img width="1280" height="720" alt="before"
src="https://github.com/user-attachments/assets/5391167b-ac04-4746-b544-add926230ad8"
/>

With this change, it now renders it like this:

<img width="1280" height="720" alt="after"
src="https://github.com/user-attachments/assets/369f1f19-0eaf-4da5-99e5-aeedbac6050c"
/>

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Sergio Benitez <SergioBenitez@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-06 08:57:59 +00:00
7f672160de ci(release): automate signed release tags and release notes (#39544) (#39622)
Backport #39544

Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: bircni <bircni@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-06 08:14:58 +00:00
e27f69bc88 fix(egress): expose more ranges as restricted rather than reserved (#39560) (#39624)
Backport #39560 by TheFox0x7

fixes: https://github.com/go-gitea/gitea/issues/39557

Signed-off-by: TheFox0x7 <thefox0x7@gmail.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-06 15:24:11 +08:00
a67171af37 fix: make "edit pr title & target branch" get correct branch (#39612) (#39621)
Backport #39612

* Fix #39610
* Regression of #39262

Also, the old code is very fragile: `#branch_target` is from translation
string, so refactored it together

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-06 06:04:30 +00:00
c7bd37e901 fix(actions): refresh reusable caller status when children are skipped (#39589) (#39609)
Backport #39589 by @bircni

When the job emitter itself finishes the last jobs of a called workflow
(e.g. skipped by `if:`), `UpdateRunJob` cascades the caller to done in
the database only. The pass kept the caller unfinished in memory and
nothing re-emitted the run, so jobs that `needs:` the caller stayed
`Pending` and the caller's commit status stayed "In progress".

The pass now reloads such callers so their commit statuses close, and
re-emits the run so their dependents are resolved in the next pass. They
are not resolved in the same pass because their `if:` and `concurrency:`
read the caller from the database, where it is not finished until the
pass writes the children.

* Fixes https://github.com/go-gitea/gitea/issues/39587
* Fixes https://github.com/go-gitea/gitea/issues/39586

Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-06 05:36:40 +00:00
wxiaoguang 0a4078558e fix: migrate broken team authorize access mode (#39579) (#39580)
backport #39579
2026-10-06 05:02:08 +00:00
silverwind e66e773a6f fix(pull): fetch PR head refs instead of pushing them (#39603) (#39607)
Backport https://github.com/go-gitea/gitea/pull/39603 by @silverwind

Uses `FetchRemoteCommit`, as v28 has no `FetchRemoteTempCommit`, so no
`--no-write-fetch-head` is involved.
2026-10-05 20:31:46 +02:00
silverwind 08c123299a revert: return 401 for unregistered runner (#39599)
Reverts the backport https://github.com/go-gitea/gitea/pull/39585 before
v28.0.1 ships. The change stays on main for v29.

gitea-runner v4.1.0 recognizes a rejected registration only by the old
`Unknown`-coded `rpc error: code = Unauthenticated desc = unregistered
runner` error. With the new `Unauthenticated` reply, an ephemeral runner
keeps its spent registration file and fails on every restart instead of
registering again. The runner fix is in
https://gitea.com/gitea/runner/pulls/1287, so a patch release shouldn't
change runner behavior before that fix has shipped.

Written by Claude Code.
2026-10-04 22:04:11 -07:00
GiteabotandKBS 219b0e7c17 fix(git): return no submodule web link when the URL cannot be parsed (#39274) (#39592)
Backport #39274

Co-authored-by: KBS <youdie006@naver.com>
2026-10-04 14:00:15 +00:00
Giteabotandbreken 02fd18970b fix(httpcache): raw files return 304 after a change when the new commit is older (#39435) (#39591)
Backport #39435

Co-authored-by: breken <support@brekfuz.com>
2026-10-04 13:39:06 +00:00
efee6d7ce4 fix(markup): link team mentions that use a different org name case (#39436) (#39588)
Backport #39436 by @breken-ai

The `org` render meta is lower-cased (`Repository.composeCommonMetas`
sets `metas["org"] = strings.ToLower(repo.OwnerName)`), but
`mentionProcessor` compares it with the org part of the mention exactly
as typed, while the team part is lower-cased before its lookup. So in a
repository of the org `MyOrg`, `@MyOrg/developers` stays plain text,
although `ResolveIssueMentionsByVisibility` lower-cases the name and
still notifies that team.

This compares the org part case-insensitively and adds a render test for
team mentions.

The code is the same on `release/v1.27`, so it could be backported.

### Verification
- `go test ./modules/markup/ -run TestRender_TeamMention -count=1`: the
`@Org1/Developers` case renders as plain text on current main and as the
team link with this change; `go test ./modules/markup/...` passes.
- `golangci-lint run ./modules/markup/` (v2.13.2): 0 issues.

### AI assistance
This contribution was prepared with AI assistance. The behavior, the
metas and mention-resolution code paths, the patch and the test were
reviewed manually.

> Built by breken, your AI support engineer - breken.ai - this one's on
us.

Co-authored-by: breken <support@brekfuz.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-04 13:10:58 +00:00
0ca36eeb3c fix(actions): return 401 for unregistered runner (#39578) (#39585)
Backport #39578

Signed-off-by: Alex Mitre <mitre88@users.noreply.github.com>
Co-authored-by: Dr Alex Mitre <bedr10_capacitacion@hotmail.com>
Co-authored-by: Alex Mitre <mitre88@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-04 04:58:32 -07:00
GiteabotandZettat123 3ee1924069 fix(user): restore organizations tab on user profile (#39577) (#39581)
Backport #39577

Fixes #39572

The shared user cards template calls the User-only `IsTypeBot` method.
The profile organizations tab passed `*organization.Organization` values
to that template, so `/{username}?tab=organizations` returned a 500
error.

Organizations are now converted to Users before rendering.

Co-authored-by: Zettat123 <zettat123@gmail.com>
2026-10-04 15:51:14 +08:00
99ac787400 fix(api): allow bots with pending password changes (#39551) (#39566)
Backport #39551 by @bircni

Allow bot accounts to use the API when a legacy password-change flag is
set, since bots cannot complete the interactive password-change flow.
Preserve password-change enforcement for human accounts and restrictions
for inactive or prohibited accounts.

Fix https://github.com/go-gitea/gitea/issues/39542

Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-03 19:19:44 +00:00
1856fef7a8 fix(actions): restore pushes to protected branches (#39564) (#39567)
Backport #39564 by @bircni

Use the Actions token's loaded write permission when checking
protected-branch pushes. Preserve push and force-push allowlists and add
regression coverage.

Fixes: https://github.com/go-gitea/gitea/issues/39563
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-03 18:12:17 +00:00
wxiaoguang 7440f1e452 fix: make navbar stopwatch button can show popup (#39562)
regression:
https://github.com/go-gitea/gitea/pull/36965#pullrequestreview-5400326165

partially backport the "href" fix from #39554
2026-10-03 16:58:45 +02:00
Giteabotandsilverwind 638c75a4d3 ci: relay fork PR reviews to giteabot through workflow_run (#39546) (#39561)
Backport #39546 by @silverwind

`pull_request_review` runs on fork PRs, which includes all backport PRs,
get a read-only token and no secrets, so giteabot cannot write lgtm
labels and statuses there. A no-op `giteabot-review` workflow now
triggers giteabot through `workflow_run`, which gets both. This allows
retiring the legacy fly.io webhook bot.

Part of https://github.com/go-gitea/giteabot/issues/15

---
This PR was written by Claude.

Co-authored-by: silverwind <me@silverwind.io>
2026-10-03 09:49:44 +00:00
575754cbeb fix: use READ_COMMITTED_SNAPSHOT on MSSQL (#39512) (#39558)
Backport #39512

MSSQL's default READ COMMITTED makes reads wait on writers, so the
runner pickup deadlocks with concurrent claims, flaking
`TestCreateTaskForRunnerConcurrentClaim`.

- Enable `READ_COMMITTED_SNAPSHOT` on MSSQL so it reads like PostgreSQL
and MySQL
- Read the pickup cursor before claiming, a lost claim could skip
waiting jobs
- Add tests that fail without consistent READ COMMITTED

Performance: Writes on MSSQL now also store the previous row version in
tempdb, the same versioning cost PostgreSQL and MySQL always pay, and
Azure SQL enables it by default. Reads no longer block on writers, and a
32-runner pickup stress test ran 2.5x faster with it.

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-03 08:21:46 +00:00
wxiaoguang f24f3e1eda fix: make image captcha work with csp (#39555)
fix #39553
2026-10-03 07:40:13 +00:00
Giteabotandsilverwind 1f615a406f fix(git): tolerate concurrent repacks in go-git storage (#39536) (#39556)
Backport #39536 by @silverwind

Since `transfer.fsckObjects` makes fetches keep a pack, git 2.54+
background maintenance repacks a mirror right after its sync fetch, and
go-git then misses objects mid-repack. Fixes these flakes:

-
https://github.com/go-gitea/gitea/actions/runs/36875305717/job/110419770196
-
https://github.com/go-gitea/gitea/actions/runs/36900439473/job/110498845882

Changes:

- Keep reindexing while the pack set changes instead of retrying once
- List packs only once their `.idx` exists and don't fail the listing on
files removed mid-repack
- Look up large objects again when their file is gone before reading

Co-authored-by: silverwind <me@silverwind.io>
2026-10-03 02:44:06 +00:00
9c77a87908 fix(markup): use installed math fonts for MathML in Chromium (#39491) (#39547)
Backport #39491 by @Copilot

On Linux and ChromeOS, Chromium resolves the `math` font family to Latin
Modern Math, which neither installs, so MathML renders with a text font
and brackets and large operators don't stretch, see
https://issues.chromium.org/issues/40069293. The new `--fonts-math`
variable keeps `math` first, so browsers that always resolve it keep
their font. Only Chromium falls through to the math fonts Linux and
ChromeOS install by default:

- `STIX Two Math`: Fedora, and `fonts-stix` on Ubuntu 26.04
- `DejaVu Math TeX Gyre`: Debian 13 and openSUSE
- `Noto Sans Math`: Fedora and ChromeOS, last because Debian and Ubuntu
ship an older version without a `MATH` table

Math fonts also have smaller x-heights than UI fonts, so MathML rendered
smaller than KaTeX and the surrounding text in every browser, see
https://github.com/w3c/mathml-core/issues/41. `font-size-adjust:
ex-height 0.52` scales whichever math font is used to KaTeX's x-height.
KaTeX output is unchanged.

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

![before and
after](https://github.com/user-attachments/assets/8319c9a9-363f-4d14-ad3d-3d2282bd0df9)

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: bircni <75789103+bircni@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-03 04:34:26 +08:00
Giteabotandsilverwind 4c3df3ab3c fix: use READ COMMITTED transactions on MySQL and MariaDB (#39506) (#39537)
Backport #39506 by @silverwind

MariaDB 11.6.2+ defaults `innodb_snapshot_isolation` to `ON`, which
fails REPEATABLE READ transactions with error 1020 when a row they write
changed after their first read. Gitea's background work like push
processing writes the same rows, so merges, issue closes and workflow
runs fail sporadically.

- Use READ COMMITTED on MySQL and MariaDB, like PostgreSQL and MSSQL
- Update xorm to v1.4.3

Replaces: https://github.com/go-gitea/gitea/pull/39494
Fixes: https://github.com/go-gitea/gitea/issues/39492

Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-01 14:19:32 -07:00
565957503c fix(git): avoid unnecessary timers during language stats (#39531) (#39535)
Backport #39531 by @CalvinTjoaquinn

## What

`BatchChecker.CheckPath` uses `time.After` inside its read loop. This
replaces it with a `time.Timer` that is stopped once the attribute
arrives.

## Why

```go
for i := 0; i < c.attributesNum; i++ {
	select {
	case <-time.After(5 * time.Second):
		// there is no "hang" problem now. This code is just used to catch other potential problems.
		return nil, reportTimeout()
	case attr, ok := <-c.stdOut.ReadAttribute():
```

`time.After` has no way to be cancelled, so the timer it allocates stays
in the runtime timer heap for the full five seconds whichever case the
`select` picks. On the normal path the attribute arrives immediately and
the timer is abandoned while still pending.

The multiplier is what makes it worth changing rather than leaving as
noise. The loop runs `len(LinguistAttributes)` times, which is six, and
`CheckPath` is called once per file:

```go
// modules/git/languagestats/language_stats_get.go:95, in the loop over repository files
attrs, err := checker.CheckPath(f.Name())
// services/gitdiff/gitdiff.go:1408, in the loop over diff files
attrs, err := checker.CheckPath(diffFile.Name)
```

So a language-stats pass over a repository of N files holds up to 6N
pending five-second timers, and a large diff does the same per file. By
the comment's own account that timeout path does not fire in practice,
so every one of those timers is allocated and held for nothing.

## The change

`time.NewTimer` plus `Stop` on the paths that win, which keeps the
behaviour identical: each iteration still gets its own five-second
budget, and the timer is released as soon as the attribute or the
context arrives rather than five seconds later.

If you would rather have a single budget for the whole read, one timer
hoisted above the loop with `defer timeout.Stop()` is simpler and
stricter, since six attributes from an already running `git check-attr`
should arrive together. That changes the semantics from per-attribute to
per-call, so I left it alone and am happy to switch if you prefer it.

## Verification

```
go build ./modules/git/...
go vet ./modules/git/attribute/
go test -count=1 ./modules/git/attribute/      # 11 tests, 0 failures
golangci-lint run ./modules/git/attribute/...
gofmt -l modules/git/attribute/                # no output
```

The package's own `CheckPath` tests cover the success path, the
closed-stdout path and the context-cancelled path, which are the three
`select` arms touched here.

Found with a small AST pass over the tree looking for `time.After`
inside loop bodies. `staticcheck`'s SA1015 covers `time.Tick` and says
nothing about this shape, so no linter in the current set reports it. Of
the nine other hits in the tree the rest look deliberate or harmless,
and `modules/queue/workergroup.go` already guards against exactly this
by only creating a debounce timer when none is pending, so I only
changed this one.

<sub>Disclosure per the AI Contribution Policy: I used an AI tool to
help find this and to draft the description. The counts above are
`len(LinguistAttributes)` and the two call sites cited, so they can be
checked directly.</sub>

Signed-off-by: Calvin Tjoaquinn <calvintjoa23@gmail.com>
Co-authored-by: Calvin Tjoaquinn <66313400+CalvinTjoaquinn@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-01 19:44:48 +00:00
e48591ba64 fix(api): add index tiebreaker to commit status ordering (#39508) (#39525)
Backport #39508 by @perfectra1n

Commit status list orders only by `created_unix`/`updated_unix`, which
have 1-second resolution while CI often posts many statuses per second.
With LIMIT/OFFSET paging, databases (e.g. PostgreSQL using a Sort plan)
may order tied rows differently per page, so `GET
/repos/{owner}/{repo}/commits/{ref}/statuses` returns some statuses
twice and never returns others.

This became visible after https://github.com/go-gitea/gitea/pull/36521
made requests without `page` paginated. Clients like Renovate that page
until `X-Total-Count` can miss a context's newest status and see a stale
`pending`, blocking automerge.

Fix: add `index` (unique per commit) as a tiebreaker to the
timestamp-based orders.

Co-authored-by: Jon Fuller <jonfuller2012@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-01 18:03:00 +00:00
f2a08e0261 fix(oauth2): allow users to approve scope changes (#38942) (#39521)
Backport #38942 by @Harsh-128

Lets users approve an OAuth2 scope change on an existing grant instead
of failing with `a grant exists with different scope`. Fixes
https://github.com/go-gitea/gitea/issues/38940.

- Approving a different scope updates the existing grant. Issued tokens
follow immediately, since their scope is read from the grant.
- Confidential and trusted apps show the consent page when the scope set
changes, instead of silently reusing the old grant.
- An omitted `scope` reuses the existing grant's scope, like GitHub.
- The consent page lists newly added scopes.

<img width="500" alt="consent page with new scopes"
src="https://github.com/user-attachments/assets/40282353-32fe-4d87-9929-08aabbab32f1"
/>

Co-authored-by: Harsh Sharma <harshee2000@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-01 17:35:17 +00:00
Giteabotandsilverwind 532fb8f4f4 fix(git): reindex go-git storage when a concurrent repack removes packs (#39510) (#39534)
Backport #39510 by @silverwind

Improve the go-git workaround to fix these flakes:

- https://github.com/go-gitea/gitea/actions/runs/36721877142/job/109908823684
- https://github.com/go-gitea/gitea/actions/runs/36799665163/job/110170983591

Co-authored-by: silverwind <me@silverwind.io>
2026-10-01 16:12:04 +00:00
Giteabotandwxiaoguang 25416e9be7 fix: trace git command correctly (#39520) (#39524)
Backport #39520 by @wxiaoguang

Help  #39410

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-01 10:19:33 +00:00
1c4fff096f enhance(packages/npm): improve npm client compatibility (#39434) (#39522)
Backport #39434 by @silverwind

Aligns the npm registry with what npm, pnpm and yarn expect:

1. Raise the publish body cap from
https://github.com/go-gitea/gitea/pull/37890 to 256 MiB like npmjs,
larger bodies get 413
2. Pick the tarball attachment by name, `npm publish --provenance`
failed at random
3. Store and serve `libc`, so mismatched glibc/musl optional binaries
are skipped
4. Treat root `*.gyp` files as an install script, like npm does
5. Always serve a `latest` dist-tag, yarn and pnpm fail without it
6. Take top-level metadata from `latest` and drop the per-version readme
7. Serve tarballs at the npmjs path `/<name>/-/<file>`, former URLs keep
working
8. Add ETag revalidation for metadata, `npm ping` and `npm whoami`

Tested with npm 12.1, pnpm 12.4, yarn 1.22 and yarn 4.18.

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-01 08:09:21 +00:00
d16daed041 fix: handle git branch name with special chars correctly (#39483) (#39515)
Backport #39483 

Fix the bugs:
* Commit graph page doesn't show
* PR command line instructions are wrong

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-10-01 14:07:00 +08:00
2d58c8c3df fix: add missing checks to several API and web handlers (#39501) (#39507)
Backport #39501 by @silverwind

Several handlers skipped checks that their sibling routes or settings
already enforce. This brings them in line.

- Push mirror API honors `DISABLE_NEW_PUSH` and checks the caller's
permission
- Media API serves small files with the usual content headers
- Issue attachment API ignores comment attachments
- Push-to-create respects `FORCE_PRIVATE`
- Profile feeds and follow actions respect `ENABLE_FEED` and owner
visibility
- Tag delete route refuses release tags
- Refresh token grant only accepts refresh tokens
- Gitea migrations bound the source's page size

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: bircni <bircni@icloud.com>
2026-09-30 18:46:59 +00:00
1e28bb1bd7 fix(markup): don't escape ambiguous characters in MathML (#39493) (#39505)
Co-authored-by: Nico Schlömer <nschloe@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-09-30 16:56:23 +00:00
f0e8c3c3d0 fix(markup): skip post-processing inside MathML (#39497) (#39502)
Co-authored-by: Nico Schlömer <nschloe@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-09-30 08:06:27 -07:00
7c58b73243 fix: copy new access token to clipboard (#39496) (#39499)
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-09-30 13:45:37 +00:00
Giteabotandwxiaoguang 6546382f4e fix: npm route (#39488) (#39490)
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-09-30 12:08:14 +00:00
0930bd71fe fix(actions): keep runs order after auto refresh (#39479) (#39481)
Co-authored-by: JerryLien <jerrylien0801@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-09-30 18:57:19 +08:00
Giteabot 15b8a5805a ci: Also release for other versions than 1 majors (#39475) (#39476) 2026-09-29 20:01:48 +00:00
1592 changed files with 55295 additions and 57329 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Gitea DevContainer",
"image": "mcr.microsoft.com/devcontainers/go:1.27-trixie",
"image": "mcr.microsoft.com/devcontainers/go:1.26-trixie",
"containerEnv": {
// override "local" from packaged version
"GOTOOLCHAIN": "auto"
+1
View File
@@ -63,6 +63,7 @@ cpu.out
/public/assets/fonts
/public/assets/img/avatar
/vendor
/VERSION
/.air
/.go-licenses
/Dockerfile
-2
View File
@@ -6,8 +6,6 @@
/public/assets/img/svg/*.svg linguist-generated
/templates/swagger/*.generated.json linguist-generated
/options/fileicon/** linguist-generated
/services/migrations/_mock_data/** linguist-generated
/tests/integration/_mock_data/** linguist-generated
/vendor/** -text -eol linguist-vendored
Dockerfile.* linguist-language=Dockerfile
Makefile.* linguist-language=Makefile
+1 -1
View File
@@ -6,7 +6,7 @@ body:
attributes:
value: |
Follow the [release management guide](https://github.com/go-gitea/gitea/blob/main/docs/release-management.md).
Replace the examples below and mark inapplicable tasks as such.
Set the issue title and milestone to the version being released. Replace the examples below and mark inapplicable tasks as such.
CI signs the tag, generates release notes, and publishes binaries and containers. Track verification here; no manual changelog PR or release upload is needed.
- type: input
id: version
+1 -1
View File
@@ -10,7 +10,7 @@ runs:
using: composite
steps:
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 26
- if: ${{ inputs.cache == 'true' }}
+1 -1
View File
@@ -34,7 +34,7 @@ runs:
env:
# pgsql is chosen to be the unlucky one to run with the slow "race detector", it is about 60% slower.
GOTEST_FLAGS: -race -timeout=40m
TAGS: bindata
TAGS: bindata gogit
TEST_LDAP: 1
TEST_SHARD: ${{ inputs.shard }}
TEST_TOTAL_SHARDS: ${{ inputs.total-shards }}
+2 -2
View File
@@ -4,7 +4,7 @@ description: Set up uv and python and restore the uv cache
runs:
using: composite
steps:
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
python-version: 3.15 # renovate: datasource=python-version depName=python versioning=python
python-version: 3.14
save-cache: ${{ github.workflow == 'cache-seeder' }}
+8 -1
View File
@@ -37,15 +37,22 @@ jobs:
- uses: ./.github/actions/go-setup
- run: make deps-backend deps-tools
- run: TAGS="bindata" make backend
- run: TAGS="bindata gogit" make backend
- name: warm test compile cache (bindata)
env:
TAGS: bindata
GOTEST_FLAGS: -race -list=^$$ -count=1
run: make test-backend
- name: warm test compile cache (bindata gogit)
env:
TAGS: bindata gogit
GOTEST_FLAGS: -race -list=^$$ -count=1
run: make test-backend
- name: warm integration compile cache
run: |
TAGS="bindata" make test-integration-compile
TAGS="bindata" GOTEST_FLAGS="-race" make test-integration-compile
TAGS="bindata gogit" make test-integration-compile
TAGS="bindata gogit" GOTEST_FLAGS="-race" make test-integration-compile
lint:
runs-on: ubuntu-latest
+2 -3
View File
@@ -9,7 +9,7 @@ concurrency:
group: cron-renovate
env:
RENOVATE_VERSION: 44.172.2 # renovate: datasource=npm depName=renovate
RENOVATE_VERSION: 44.109.1 # renovate: datasource=npm depName=renovate
permissions:
contents: read
@@ -21,7 +21,7 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: renovatebot/github-action@a63d9a7bc606f7336fd20b47f2f32259a35e6213 # v46.3.9
- uses: renovatebot/github-action@9fea9f0fbf80401026d11d03911d62b1f70fef1f # v46.3.3
with:
renovate-version: ${{ env.RENOVATE_VERSION }}
configurationFile: renovate.json5
@@ -30,4 +30,3 @@ jobs:
RENOVATE_BINARY_SOURCE: install # auto-install go/node toolchains needed by post-upgrade tasks.
RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^make (tidy|svg|generate-codemirror-languages)$"]'
RENOVATE_REPOSITORIES: '["go-gitea/gitea"]'
RENOVATE_DOCKER_MAX_PAGES: 10 # https://github.com/renovatebot/renovate/issues/46925
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: crowdin/github-action@72955432d4aa4f6dd64cf17eeb88dd34d6ba064c # v3.4.0
- uses: crowdin/github-action@df474cdfb9f41d6ae777118749477c2cdf7cacc8 # v3.2.0
with:
upload_sources: true
upload_translations: false
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: go-gitea/giteabot@b830f7aa810e43afc1b2f8e8968a30754e6ff5d3 # v1.4.1
- uses: go-gitea/giteabot@36ea99f821fefa3e8fb26aeac6211735664cd9c0 # v1.3.1
with:
github_token: ${{ secrets.GITEABOT_TOKEN }}
gitea_fork: giteabot/gitea
+12 -6
View File
@@ -1,11 +1,16 @@
name: giteabot
on:
# When main advances, rerun merge queue maintenance so the oldest
# reviewed/wait-merge PR can be updated against the new base promptly.
push:
branches:
- main
# pull_request_target gives this workflow access to GITEABOT_TOKEN on PRs from
# forks, which the bot needs to write labels, statuses and comments. Safe here
# because the job only runs a pinned action and never checks out PR HEAD.
# These PR lifecycle events drive label maintenance, lgtm, and explicit bot
# actions triggered by relevant label changes.
# These PR lifecycle events drive label maintenance, queue maintenance, and
# explicit bot actions triggered by relevant label changes.
pull_request_target: # zizmor: ignore[dangerous-triggers]
types:
- opened
@@ -21,7 +26,8 @@ on:
- giteabot-review
types:
- requested
# Daily housekeeping like locks, stale feedback issues, last calls and milestones
# Periodic maintenance is still useful as a backstop for queue cleanup and
# other housekeeping, even though main pushes now trigger it promptly.
schedule:
- cron: "15 3 * * *"
# Allow maintainers to rerun selected checks manually when debugging bot
@@ -31,7 +37,7 @@ on:
checks:
description: Comma-separated list of non-backport checks to run
required: false
default: backport_branches,labels,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions
default: backport_branches,labels,merge_queue,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions
permissions: {}
@@ -50,7 +56,7 @@ jobs:
pull-requests: write
statuses: write
steps:
- uses: go-gitea/giteabot@b830f7aa810e43afc1b2f8e8968a30754e6ff5d3 # v1.4.1
- uses: go-gitea/giteabot@36ea99f821fefa3e8fb26aeac6211735664cd9c0 # v1.3.1
with:
github_token: ${{ secrets.GITEABOT_TOKEN }}
checks: ${{ github.event.inputs.checks || 'backport_branches,labels,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions' }}
checks: ${{ github.event.inputs.checks || 'backport_branches,labels,merge_queue,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions' }}
-16
View File
@@ -1,16 +0,0 @@
name: merge-group
# Reports the required giteabot/lgtm check on merge queue commits. giteabot only sets it
# on PR heads, which already passed it before entering the queue.
on:
merge_group:
permissions: {}
jobs:
lgtm:
name: giteabot/lgtm
runs-on: ubuntu-latest
steps:
- run: "true"
+4 -4
View File
@@ -2,7 +2,6 @@ name: compliance
on:
pull_request:
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -28,7 +27,6 @@ jobs:
- run: TAGS="bindata" make generate-go lint-backend # lint-go can lint with "bindata" tags
lint-on-demand:
if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.swagger == 'true' || needs.files-changed.outputs.json == 'true' || needs.files-changed.outputs.shell == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
@@ -40,6 +38,8 @@ jobs:
with:
cache: "false"
- run: make lint-spell
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true' || needs.files-changed.outputs.actions == 'true'
uses: ./.github/actions/python-setup
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true'
@@ -93,13 +93,13 @@ jobs:
env:
GOOS: linux
GOARCH: arm64
TAGS: bindata
TAGS: bindata gogit
- name: build-backend-windows
run: go build -ldflags '-s -w' -o gitea-windows
env:
GOOS: windows
GOARCH: amd64
TAGS: bindata
TAGS: bindata gogit
- name: build-backend-386
run: go build -ldflags '-s -w' -o gitea-linux-386
env:
+14 -11
View File
@@ -2,7 +2,6 @@ name: db-tests
on:
pull_request:
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -26,8 +25,6 @@ jobs:
env:
POSTGRES_DB: test
POSTGRES_PASSWORD: postgres
options: >-
--mount type=tmpfs,destination=/var/lib/postgresql/data
ports:
- "5432:5432"
ldap:
@@ -64,8 +61,6 @@ jobs:
env:
POSTGRES_DB: test
POSTGRES_PASSWORD: postgres
options: >-
--mount type=tmpfs,destination=/var/lib/postgresql/data
ports:
- "5432:5432"
ldap:
@@ -100,17 +95,17 @@ jobs:
- run: make deps-backend
- run: make backend
env:
TAGS: bindata
TAGS: bindata gogit
- run: GITEA_TEST_DATABASE=sqlite make test-migration
env:
TAGS: bindata
TAGS: bindata gogit
- name: run tests
run: GITEA_TEST_DATABASE=sqlite make test-integration
timeout-minutes: 50
env:
# sqlite driver can contain large amount of Golang code, so don't use race detector for it, otherwise, extremely slow
GOTEST_FLAGS: -timeout=40m
TAGS: bindata
TAGS: bindata gogit
test-unit:
if: needs.files-changed.outputs.backend == 'true'
@@ -118,7 +113,7 @@ jobs:
runs-on: ubuntu-latest
services:
elasticsearch:
image: elasticsearch:8.19.23@sha256:3f4d63c59ca5a05b535179dfcced47a514119e351fa81653f69902beacae90c9
image: elasticsearch:8.19.21@sha256:ae1f398ec8734a63eb7f719cecc2ea33e2bb46e23bdf24641dec86fd256bf594
env:
discovery.type: single-node
xpack.security.enabled: false
@@ -168,6 +163,13 @@ jobs:
GOTEST_FLAGS: -race -timeout=20m
TAGS: bindata
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
- name: unit-tests-gogit
run: make test-backend
env:
GOTEST_FLAGS: -race -timeout=20m
TAGS: bindata gogit
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
GITEA_TEST_CI_SKIP_EXTERNAL: true
- run: make test-check
test-mysql:
@@ -186,7 +188,7 @@ jobs:
options: >-
--mount type=tmpfs,destination=/bitnami/mysql/data
elasticsearch:
image: elasticsearch:8.19.23@sha256:3f4d63c59ca5a05b535179dfcced47a514119e351fa81653f69902beacae90c9
image: elasticsearch:8.19.21@sha256:ae1f398ec8734a63eb7f719cecc2ea33e2bb46e23bdf24641dec86fd256bf594
env:
discovery.type: single-node
xpack.security.enabled: false
@@ -216,6 +218,7 @@ jobs:
run: GITEA_TEST_DATABASE=mysql make test-integration
env:
TAGS: bindata
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
test-mssql:
if: needs.files-changed.outputs.backend == 'true'
@@ -223,7 +226,7 @@ jobs:
runs-on: ubuntu-latest
services:
mssql:
image: mcr.microsoft.com/mssql/server:2019-latest@sha256:07d7dedecdbfcf6776035dc5eefb5bfd9aa1804b2f6b99f7dedb397d8a599069
image: mcr.microsoft.com/mssql/server:2019-latest@sha256:ef0b8db33970ecd01bed49c3a84a1d083c435a9891718df619298b67b352e74a
env:
ACCEPT_EULA: Y
MSSQL_PID: Standard
-1
View File
@@ -2,7 +2,6 @@ name: e2e-tests
on:
pull_request:
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.event.pull_request.base.sha }}
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 26
# Labels are only synced after the title lints, so an invalid title never reaches the label diff.
@@ -0,0 +1,55 @@
name: release-nightly-snapcraft
on:
push:
branches:
- main
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
build-and-publish:
strategy:
fail-fast: false
matrix:
runner: [ubuntu-24.04, ubuntu-24.04-arm]
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- name: Build snap
id: build
env:
SNAPCRAFT_IMAGE_INFO: '{"build_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}'
run: |
# snapcraft installs build-snaps and apt packages system-wide, which needs root
sudo snap install --classic snapcraft
sudo SNAPCRAFT_BUILD_INFO=1 SNAPCRAFT_IMAGE_INFO="$SNAPCRAFT_IMAGE_INFO" snapcraft pack --destructive-mode
echo "snap=$(ls ./*.snap)" >> "$GITHUB_OUTPUT"
# retry snapcraft uploads which can be flaky
- name: Publish snap
run: |
# part-gitea-pull.sh decides what is built, see the comments there:
# * grade devel (main or a prerelease tag) goes to latest/edge
# * grade stable (a new release tag) goes to latest/stable, and to latest/candidate which marks it as released
channel=latest/edge
if unsquashfs -cat "$SNAP" meta/snap.yaml | grep -qx 'grade: stable'; then
channel=latest/stable,latest/candidate
fi
for attempt in 1 2 3 4 5; do
snapcraft upload "$SNAP" --release "$channel" && exit 0
echo "::warning::snap upload attempt $attempt failed, retrying in 15s"
sleep 15
done
exit 1
env:
SNAP: ${{ steps.build.outputs.snap }}
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
+5 -90
View File
@@ -1,9 +1,8 @@
name: release-nightly
on:
schedule:
- cron: "17 */12 * * *"
workflow_dispatch:
push:
branches: [main, release/v*]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -12,42 +11,13 @@ concurrency:
permissions: {}
jobs:
branches:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
outputs:
branches: ${{ steps.changed.outputs.branches }}
steps:
- id: changed
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
since=$(gh run list --workflow release-nightly.yml --status success --limit 1 --json createdAt -q '.[0].createdAt')
branches=$(gh api --paginate "repos/$GH_REPO/branches" -q '.[].name | select(. == "main" or startswith("release/v"))' |
while read -r branch; do
if [ -z "$since" ] || [ -n "$(gh api "repos/$GH_REPO/commits?sha=$branch&since=$since&per_page=1" -q '.[].sha')" ]; then
echo "$branch"
fi
done | jq -Rnc '[inputs]')
echo "branches=$branches" >> "$GITHUB_OUTPUT"
nightly-binary:
needs: branches
if: needs.branches.outputs.branches != '[]'
strategy:
matrix:
branch: ${{ fromJSON(needs.branches.outputs.branches) }}
runs-on: namespace-profile-gitea-release-binary
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ matrix.branch }}
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
@@ -58,9 +28,7 @@ jobs:
cache: false
- uses: ./.github/actions/node-setup
- run: make deps-frontend deps-backend
- run: make release GITHUB_REF_NAME="$BRANCH"
env:
BRANCH: ${{ matrix.branch }}
- run: make release
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: import gpg key
@@ -82,7 +50,7 @@ jobs:
- name: Get cleaned branch name
id: clean_name
env:
REF: ${{ matrix.branch }}
REF: ${{ github.ref }}
run: |
REF_NAME=$(echo "$REF" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "Cleaned name is ${REF_NAME}"
@@ -99,19 +67,12 @@ jobs:
aws s3 sync dist/release "s3://$CLOUDFLARE_R2_BUCKET/gitea/$BRANCH" --endpoint-url "https://$CLOUDFLARE_R2_ACCOUNT_ID.r2.cloudflarestorage.com" --no-progress
nightly-container:
needs: branches
if: needs.branches.outputs.branches != '[]'
strategy:
matrix:
branch: ${{ fromJSON(needs.branches.outputs.branches) }}
runs-on: namespace-profile-gitea-release-docker
permissions:
contents: read
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ matrix.branch }}
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
@@ -122,14 +83,13 @@ jobs:
- name: Get cleaned branch name
id: clean_name
env:
REF: ${{ matrix.branch }}
REF: ${{ github.ref }}
run: |
REF_NAME=$(echo "$REF" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
- uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
id: meta
with:
context: git
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
@@ -140,7 +100,6 @@ jobs:
- uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
id: meta_rootless
with:
context: git
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
@@ -183,47 +142,3 @@ jobs:
annotations: ${{ steps.meta_rootless.outputs.annotations }}
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless
cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless,mode=max
nightly-snap:
needs: branches
if: contains(fromJSON(needs.branches.outputs.branches), 'main')
strategy:
fail-fast: false
matrix:
runner: [ubuntu-24.04, ubuntu-24.04-arm]
runs-on: ${{ matrix.runner }}
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: main
fetch-depth: 0
- name: Build snap
id: build
env:
SNAPCRAFT_IMAGE_INFO: '{"build_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}'
run: |
# snapcraft installs build-snaps and apt packages system-wide, which needs root
sudo snap install --classic snapcraft
sudo SNAPCRAFT_BUILD_INFO=1 SNAPCRAFT_IMAGE_INFO="$SNAPCRAFT_IMAGE_INFO" snapcraft pack --destructive-mode
echo "snap=$(ls ./*.snap)" >> "$GITHUB_OUTPUT"
# retry snapcraft uploads which can be flaky
- name: Publish snap
run: |
# part-gitea-pull.sh decides what is built, see the comments there:
# * grade devel (main or a prerelease tag) goes to latest/edge
# * grade stable (a new release tag) goes to latest/stable, and to latest/candidate which marks it as released
channel=latest/edge
if unsquashfs -cat "$SNAP" meta/snap.yaml | grep -qx 'grade: stable'; then
channel=latest/stable,latest/candidate
fi
for attempt in 1 2 3 4 5; do
snapcraft upload "$SNAP" --release "$channel" && exit 0
echo "::warning::snap upload attempt $attempt failed, retrying in 15s"
sleep 15
done
exit 1
env:
SNAP: ${{ steps.build.outputs.snap }}
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
+5 -3
View File
@@ -68,6 +68,10 @@ jobs:
BRANCH: ${{ steps.clean_name.outputs.branch }}
run: |
aws s3 sync dist/release "s3://$CLOUDFLARE_R2_BUCKET/gitea/$BRANCH" --endpoint-url "https://$CLOUDFLARE_R2_ACCOUNT_ID.r2.cloudflarestorage.com" --no-progress
- name: Install GH CLI
uses: dev-hanz-ops/install-gh-cli-action@6089bdde54118ad7ca3d22053eb2d69387fd2779 # v0.3.0
with:
gh-cli-version: 2.39.1
- id: range
run: |
previous=$(git tag --list --sort=-v:refname | grep -xE 'v[0-9]+\.[0-9]+\.[0-9]+' | grep -A1 -xF "$GITHUB_REF_NAME" | tail -1) # highest stable version below this one
@@ -81,12 +85,10 @@ jobs:
args: --tag ${{ github.ref_name }} ${{ steps.range.outputs.range }}
- name: create github release
env:
GH_CLI_VERSION: 2.103.2 # renovate: datasource=github-releases depName=cli/cli extractVersion=^v(?<version>.+)$
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
TAG: ${{ github.ref_name }}
run: |
curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_CLI_VERSION}/gh_${GH_CLI_VERSION}_linux_amd64.tar.gz" | tar xz --strip-components=2 "gh_${GH_CLI_VERSION}_linux_amd64/bin/gh"
./gh release create "$TAG" --title "$TAG" --notes-file git-cliff/CHANGELOG.md dist/release/*
gh release create "$TAG" --title "$TAG" --notes-file git-cliff/CHANGELOG.md dist/release/*
container:
runs-on: namespace-profile-gitea-release-docker
+1 -2
View File
@@ -1,6 +1,6 @@
- Never assume, verify before claiming
- List development targets with `make help`
- Before working on code, read the `docs` guides for every area touched: `guidelines-backend.md` for Go, `guidelines-frontend.md` for TS, CSS and Vue, both for templates, `testing.md` for tests, `development.md` for config options and migrations, `guidelines-refactoring.md` for refactors
- Read relevant developer documentation in the `docs` folder
- PR descriptions: minimal, only what and why, no task or file listings. Include screenshots for UI changes, before and after when modifying existing UI. Aim for less than 1000 characters
- Reference issues and PRs by full URL, not by number
- Use Conventional Commits for commit messages and PR titles, plus Gitea's `enhance` type for user-facing enhancements
@@ -12,7 +12,6 @@
- In `options/locale`, only edit `locale_en-US.json`, other locales are synced automatically
- In TS, use `!` instead of `?.`/`??` when a value always exists
- In Go, prefer to use modern language features wherever possible
- In Go, function-name prefixes in errors like `fmt.Errorf("Foo: %w", err)` must always name the function they are in
- Write sizes as multiplications like `64 * 1024`, not bit shifts like `64 << 10`
- Prefer `tw-*` utilities over inline `style` and `flex-*` helpers over per-child `tw-ml-*`/`tw-mr-*` margins, falling back to `tw-*` where specificity requires `!important`
- Run `make fmt` after `.go` edits, `make tidy` after `go.mod` edits, `make generate-swagger` after API changes, and lint what changed with `make lint-go`, `lint-js`, `lint-css` or `lint-templates`
+2 -3
View File
@@ -58,8 +58,7 @@ Contributions made with the assistance of AI tools are welcome, but contributors
2. Manually test the changes and add appropriate automated tests where feasible.
3. Only use AI to assist in contributions that you understand well enough to explain, defend, and revise yourself during review.
4. Disclose AI-assisted content clearly.
5. Do not rely solely on AI to generate replies or engage in discussions.
Ensure your responses are concise, accurate, and thoughtful, avoiding generic or low-quality AI-generated content.
5. Do not use AI to reply to questions about your issue or pull request. The questions are for you, not an AI model.
6. AI may be used to help draft issues and pull requests, but contributors remain responsible for the accuracy, completeness, and intent of what they submit.
Maintainers reserve the right to close pull requests and issues that do not disclose AI assistance, that appear to be low-quality AI-generated content, or where the contributor cannot explain or defend the proposed changes themselves.
@@ -176,7 +175,7 @@ PR titles must follow the [Conventional Commits](https://www.conventionalcommits
type(scope)!: subject
```
The scope in parentheses is optional. A `!` immediately before the colon marks a [breaking change](https://www.conventionalcommits.org/en/v1.0.0/#summary): either `type!:` or `type(scope)!:` (not `type!(scope):`). Do not mark `build`, `chore`, `ci`, `docs`, `style`, or `test` as breaking.
The scope in parentheses is optional. A `!` immediately before the colon marks a [breaking change](https://www.conventionalcommits.org/en/v1.0.0/#summary): either `type!:` or `type(scope)!:` (not `type!(scope):`).
Use one of these types:
+1 -3
View File
@@ -64,6 +64,4 @@ Christopher Homberger <christopher.homberger@web.de> (@ChristopherHX)
Tobias Balle-Petersen <tobiasbp@gmail.com> (@tobiasbp)
TheFox <thefox0x7@gmail.com> (@TheFox0x7)
Nicolas <bircni@icloud.com> (@bircni)
Minjie Fang <wingsallen@gmail.com> (@wingsallen)
Shudhanshu Singh <sudhanshuwriterblc@gmail.com> (@SudhanshuMatrix)
Shivansh Garg <shivanshgarg587@gmail.com> (@SHIVANSHGARG07)
Minjie Fang <wingsallen@gmail.com> (@wingsallen)
+33 -17
View File
@@ -6,16 +6,17 @@ SHASUM ?= shasum -a 256
AIR_PACKAGE ?= github.com/air-verse/air@v1.67.4 # renovate: datasource=go
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v4/cmd/editorconfig-checker@v4.0.2 # renovate: datasource=go
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.15.0 # renovate: datasource=go
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.14.0 # renovate: datasource=go
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.17 # renovate: datasource=go
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 # renovate: datasource=go
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.36.6 # renovate: datasource=go
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.9.0 # renovate: datasource=go
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.8.0 # renovate: datasource=go
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 # renovate: datasource=go
SHELLCHECK_IMAGE ?= docker.io/koalaman/shellcheck:v0.11.0@sha256:61862eba1fcf09a484ebcc6feea46f1782532571a34ed51fedf90dd25f925a8d # renovate: datasource=docker
CONTAINER_RUNTIME ?= $(shell hash docker >/dev/null 2>&1 && echo docker || echo podman)
PLAYWRIGHT_BROWSERS ?= chromium firefox webkit
PLAYWRIGHT_BROWSERS ?= chromium firefox
PLAYWRIGHT_FLAGS ?=
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
@@ -68,15 +69,14 @@ endif
# GOTEST_FLAGS is for unit test and integration test
GOTEST_FLAGS ?= -timeout 40m
# disable race detection for the packages that slow down the tests
GOTEST_FLAGS := -gcflags=modernc.org/sqlite/lib=-race=false $(GOTEST_FLAGS)
STORED_VERSION_FILE := VERSION
GITHUB_REF_TYPE ?= branch
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
# VERSION: the name for the build and filenames, e.g.: "1.2.3" for tag "v1.2.3", "main-nightly" for branch "main"
# VERSION: the branch name for the build and filenames, e.g.: "feature/foo-bar", "main"
# branch name "release/v1.27.2" is stripped to "1.27.2".
# GITEA_VERSION: the Gitea's internal version for display, e.g. "1.28.0+dev-356-ge47d0b66ea"
ifeq ($(GITHUB_REF_TYPE),tag)
# convert tag "v1.2.3" to "1.2.3"
@@ -101,6 +101,11 @@ else
$(error unsupported ref type $(GITHUB_REF_TYPE))
endif
# if version == "main" then add "-nightly" to the version for nightly builds: "main-nightly"
ifeq ($(VERSION),main)
VERSION := main-nightly
endif
LDFLAGS := $(LDFLAGS) -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
RELEASE_ENV = GO="$(GO)" TAGS="$(TAGS)" LDFLAGS="$(LDFLAGS)" DIST="$(DIST)" VERSION="$(VERSION)"
@@ -127,9 +132,11 @@ GO_LICENSE_FILE := assets/go-licenses.json
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR)
GO_DIRS := build cmd modelmigration models modules routers services tests tools
WEB_DIRS := web_src/js web_src/css
ESLINT_FILES := web_src/js tools *.ts tests/e2e
STYLELINT_FILES := web_src/css web_src/js/components/*.vue
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.json .github $(wildcard *.go *.md *.yml *.yaml *.toml)
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.json
GO_SOURCES := $(wildcard *.go)
@@ -266,10 +273,10 @@ checks-frontend: lockfile-check svg-check ## check frontend files
checks-backend: tidy-check swagger-check openapi3-check fmt-check swagger-validate security-check ## check backend files
.PHONY: lint
lint: lint-frontend lint-backend lint-templates lint-swagger lint-md lint-actions lint-json lint-yaml lint-shell ## lint everything
lint: lint-frontend lint-backend lint-templates lint-swagger lint-spell lint-md lint-actions lint-json lint-yaml lint-shell ## lint everything
.PHONY: lint-fix
lint-fix: lint-frontend-fix lint-backend-fix ## lint everything and fix issues
lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix ## lint everything and fix issues
.PHONY: lint-frontend
lint-frontend: lint-js lint-css ## lint frontend files
@@ -313,6 +320,14 @@ lint-md: node_modules ## lint markdown files
lint-md-fix: node_modules ## lint markdown files and fix issues
pnpm exec markdownlint --fix *.md
.PHONY: lint-spell
lint-spell: ## lint spelling
@git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -error
.PHONY: lint-spell-fix
lint-spell-fix: ## lint spelling and fix issues
@git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -w
.PHONY: lint-go
lint-go: ## lint go files
GO=$(GO) GOLANGCI_LINT_PACKAGE=$(GOLANGCI_LINT_PACKAGE) $(GO) run ./tools/lint-go-all.go
@@ -541,7 +556,7 @@ release-compress: | $(DIST_DIRS)
.PHONY: release-sources
release-sources: | $(DIST_DIRS)
echo $(GITEA_VERSION) > $(STORED_VERSION_FILE)
echo $(VERSION) > $(STORED_VERSION_FILE)
# bsdtar needs a ^ to prevent matching subdirectories
$(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./)
# use transform to a add a release-folder prefix; in bsdtar the transform parameter equivalent is -s
@@ -563,14 +578,15 @@ deps-backend: ## install backend dependencies
$(GO) mod download
.PHONY: deps-tools
deps-tools: ## fetch and compile tool dependencies
$(GO) run -exec true $(AIR_PACKAGE) & \
$(GO) run -exec true $(EDITORCONFIG_CHECKER_PACKAGE) & \
$(GO) run -exec true $(GOLANGCI_LINT_PACKAGE) & \
$(GO) run -exec true $(GXZ_PACKAGE) & \
$(GO) run -exec true $(SWAGGER_PACKAGE) & \
$(GO) run -exec true $(GOVULNCHECK_PACKAGE) & \
$(GO) run -exec true $(ACTIONLINT_PACKAGE) & \
deps-tools: ## install tool dependencies
$(GO) install $(AIR_PACKAGE) & \
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) & \
$(GO) install $(GOLANGCI_LINT_PACKAGE) & \
$(GO) install $(GXZ_PACKAGE) & \
$(GO) install $(MISSPELL_PACKAGE) & \
$(GO) install $(SWAGGER_PACKAGE) & \
$(GO) install $(GOVULNCHECK_PACKAGE) & \
$(GO) install $(ACTIONLINT_PACKAGE) & \
wait
node_modules: pnpm-lock.yaml
+1 -1
View File
@@ -68,7 +68,7 @@ Get more information from [the translation section of our contributing guide](CO
## Official and Third-Party Projects
We provide an official [go-sdk](https://gitea.com/gitea/go-sdk), a CLI tool called [gt](https://gitea.com/gitea/cli) and an [action runner](https://gitea.com/gitea/runner) for Gitea Action.
We provide an official [go-sdk](https://gitea.com/gitea/go-sdk), a CLI tool called [tea](https://gitea.com/gitea/tea) and an [action runner](https://gitea.com/gitea/runner) for Gitea Action.
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea), where you can discover more third-party projects, including SDKs, plugins, themes, and more.
+1 -1
View File
@@ -76,7 +76,7 @@
## 官方和第三方项目
我们提供了一个官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一个名为 [gt](https://gitea.com/gitea/cli) 的 CLI 工具和一个 Gitea Action 的 [action runner](https://gitea.com/gitea/runner)。
我们提供了一个官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一个名为 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一个 Gitea Action 的 [action runner](https://gitea.com/gitea/runner)。
我们在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 维护了一个 Gitea 相关项目的列表,您可以在那里发现更多的第三方项目,包括 SDK、插件、主题等。
+1 -1
View File
@@ -76,7 +76,7 @@
## 官方和第三方項目
我們提供了一個官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一個名為 [gt](https://gitea.com/gitea/cli) 的 CLI 工具和一個 Gitea Action 的 [action runner](https://gitea.com/gitea/runner)。
我們提供了一個官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一個名為 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一個 Gitea Action 的 [action runner](https://gitea.com/gitea/runner)。
我們在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 維護了一個 Gitea 相關項目的列表,您可以在那裡發現更多的第三方項目,包括 SDK、插件、主題等。
+74 -4
View File
@@ -5,8 +5,8 @@
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
},
{
"name": "connectrpc.com/connect/v2",
"path": "connectrpc.com/connect/v2/LICENSE",
"name": "connectrpc.com/connect",
"path": "connectrpc.com/connect/LICENSE",
"licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under thiLine truncated
},
{
@@ -14,6 +14,26 @@
"path": "filippo.io/edwards25519/LICENSE",
"licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
},
{
"name": "gitea.com/go-chi/binding",
"path": "gitea.com/go-chi/binding/LICENSE",
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
},
{
"name": "gitea.com/go-chi/cache",
"path": "gitea.com/go-chi/cache/LICENSE",
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
},
{
"name": "gitea.com/go-chi/captcha",
"path": "gitea.com/go-chi/captcha/LICENSE",
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
},
{
"name": "gitea.com/go-chi/session",
"path": "gitea.com/go-chi/session/LICENSE",
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
},
{
"name": "gitea.com/lunny/dingtalk_webhook",
"path": "gitea.com/lunny/dingtalk_webhook/LICENSE",
@@ -54,6 +74,11 @@
"path": "github.com/Azure/go-ntlmssp/LICENSE",
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2016 Microsoft\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
},
{
"name": "github.com/DataDog/zstd",
"path": "github.com/DataDog/zstd/LICENSE",
"licenseText": "Simplified BSD License\n\nCopyright (c) 2016, Datadog \u003cinfo@datadoghq.com\u003e\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n * Redistributions of source code must retain the above copyright notice,\n this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n * Neither the name of the copyright holder nor the names of its contributors\n may be used to endorse or promote products derived from this software\n without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
},
{
"name": "github.com/Necoro/html2text",
"path": "github.com/Necoro/html2text/LICENSE",
@@ -284,6 +309,11 @@
"path": "github.com/charmbracelet/git-lfs-transfer/LICENSE",
"licenseText": "MIT License\n\nCopyright (c) 2022-2023 Charmbracelet, Inc\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
},
{
"name": "github.com/chi-middleware/proxy",
"path": "github.com/chi-middleware/proxy/LICENSE",
"licenseText": "Copyright (c) 2020 Lauris BH\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
},
{
"name": "github.com/clipperhouse/displaywidth",
"path": "github.com/clipperhouse/displaywidth/LICENSE",
@@ -304,6 +334,21 @@
"path": "github.com/coder/websocket/LICENSE.txt",
"licenseText": "Copyright (c) 2025 Coder\n\nPermission to use, copy, modify, and distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
},
{
"name": "github.com/couchbase/go-couchbase",
"path": "github.com/couchbase/go-couchbase/LICENSE",
"licenseText": "Copyright (c) 2013 Couchbase, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
},
{
"name": "github.com/couchbase/gomemcached",
"path": "github.com/couchbase/gomemcached/LICENSE",
"licenseText": "Copyright (c) 2013 Dustin Sallings\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
},
{
"name": "github.com/couchbase/goutils",
"path": "github.com/couchbase/goutils/LICENSE.txt",
"licenseText": "Source code in this repository is licensed under various licenses. The\nBusiness Source License 1.1 (BSL) is one such license. Each file indicates in\na section at the beginning of the file the name of the license that applies to\nit. All licenses used in this repository can be found in the top-level\nlicenses directory.\n"
},
{
"name": "github.com/cpuguy83/go-md2man/v2",
"path": "github.com/cpuguy83/go-md2man/v2/LICENSE.md",
@@ -485,8 +530,13 @@
"licenseText": "Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
},
{
"name": "github.com/google/go-github/v93",
"path": "github.com/google/go-github/v93/LICENSE",
"name": "github.com/google/flatbuffers",
"path": "github.com/google/flatbuffers/LICENSE",
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
},
{
"name": "github.com/google/go-github/v92",
"path": "github.com/google/go-github/v92/LICENSE",
"licenseText": "Copyright (c) 2013 The go-github AUTHORS. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
},
{
@@ -874,6 +924,11 @@
"path": "github.com/santhosh-tekuri/jsonschema/v6/LICENSE",
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
},
{
"name": "github.com/sassoftware/go-rpmutils",
"path": "github.com/sassoftware/go-rpmutils/LICENSE",
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
},
{
"name": "github.com/sergi/go-diff",
"path": "github.com/sergi/go-diff/LICENSE",
@@ -884,6 +939,11 @@
"path": "github.com/shopspring/decimal/LICENSE",
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Spring, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n- Based on https://github.com/oguzbilgic/fpd, which has the following license:\n\"\"\"\nThe MIT License (MIT)\n\nCopyright (c) 2013 Oguz Bilgic\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\n"
},
{
"name": "github.com/sirupsen/logrus",
"path": "github.com/sirupsen/logrus/LICENSE",
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014 Simon Eskildsen\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
},
{
"name": "github.com/sorairolake/lzip-go",
"path": "github.com/sorairolake/lzip-go/LICENSE-APACHE",
@@ -934,6 +994,11 @@
"path": "github.com/ulikunitz/xz/LICENSE",
"licenseText": "Copyright (c) 2014-2022 Ulrich Kunitz\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* My name, Ulrich Kunitz, may not be used to endorse or promote products\n derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
},
{
"name": "github.com/unknwon/com",
"path": "github.com/unknwon/com/LICENSE",
"licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that YLine truncated
},
{
"name": "github.com/urfave/cli-docs/v3",
"path": "github.com/urfave/cli-docs/v3/LICENSE",
@@ -959,6 +1024,11 @@
"path": "github.com/x448/float16/LICENSE",
"licenseText": "MIT License\n\nCopyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
},
{
"name": "github.com/xi2/xz",
"path": "github.com/xi2/xz/LICENSE",
"licenseText": "Licensing of github.com/xi2/xz\n==============================\n\n This Go package is a modified version of\n\n XZ Embedded \u003chttp://tukaani.org/xz/embedded.html\u003e\n\n The contents of the testdata directory are modified versions of\n the test files from\n\n XZ Utils \u003chttp://tukaani.org/xz/\u003e\n\n All the files in this package have been written by Michael Cross,\n Lasse Collin and/or Igor PavLov. All these files have been put\n into the public domain. You can do whatever you want with these\n files.\n\n This software is provided \"as is\", without any warranty.\n"
},
{
"name": "github.com/yohcop/openid-go",
"path": "github.com/yohcop/openid-go/LICENSE",
+21
View File
@@ -0,0 +1,21 @@
acounts,accounts
canidate,candidate
comfirm,confirm
converage,coverage
currrently,currently
delimeter,delimiter
differrent,different
exclusing,excluding
finshed,finished
formated,formatted
inderect,indirect
insuficient,insufficient
likly,likely
mergable,mergeable
overrided,overridden
priortized,prioritized
registeration,registration
reuqest,request
reviwer,reviewer
superceded,superseded
underlaying,underlying
1 acounts accounts
2 canidate candidate
3 comfirm confirm
4 converage coverage
5 currrently currently
6 delimeter delimiter
7 differrent different
8 exclusing excluding
9 finshed finished
10 formated formatted
11 inderect indirect
12 insuficient insufficient
13 likly likely
14 mergable mergeable
15 overrided overridden
16 priortized prioritized
17 registeration registration
18 reuqest request
19 reviwer reviewer
20 superceded superseded
21 underlaying underlying
+3 -2
View File
@@ -23,8 +23,8 @@ import (
var rxDeprecated = regexp.MustCompile(`(?i)(?:^|[\n.;])\s*deprecated\b`)
// Convert parses a Swagger 2.0 spec and returns an OAS3 spec, applying
// Gitea-specific post-processing: file-schema fixups, URI formats,
// deprecated flags, and shared-enum extraction.
// Gitea-specific post-processing: server URL, file-schema fixups, URI
// formats, deprecated flags, and shared-enum extraction.
//
// astEnumMap is a value-set-key → Go-type-name(s) map (built by
// ScanSwaggerEnumTypes). When a value set is shared by multiple Go types,
@@ -42,6 +42,7 @@ func Convert(swaggerJSON []byte, astEnumMap map[string][]string) (*openapi3.T, e
return nil, fmt.Errorf("converting to openapi 3.0: %w", err)
}
oas3.Servers = openapi3.Servers{{URL: swagger2.BasePath}}
fixFileSchemas(oas3)
addURIFormats(oas3)
addDeprecatedFlags(oas3)
+38 -22
View File
@@ -42,19 +42,27 @@ func TestDeriveEnumName_miss(t *testing.T) {
func TestExtractSharedEnums_usesASTMap(t *testing.T) {
doc := &openapi3.T{
Components: &openapi3.Components{
Schemas: openapi3.Schemas{},
},
}
for _, name := range []string{"A", "B"} {
doc.Components.Schemas[name] = &openapi3.SchemaRef{Value: &openapi3.Schema{
Type: &openapi3.Types{"object"},
Properties: openapi3.Schemas{
"color": {Value: &openapi3.Schema{
Type: &openapi3.Types{"string"},
Enum: []any{"red", "green", "blue"},
Schemas: openapi3.Schemas{
"A": {Value: &openapi3.Schema{
Type: &openapi3.Types{"object"},
Properties: openapi3.Schemas{
"color": {Value: &openapi3.Schema{
Type: &openapi3.Types{"string"},
Enum: []any{"red", "green", "blue"},
}},
},
}},
"B": {Value: &openapi3.Schema{
Type: &openapi3.Types{"object"},
Properties: openapi3.Schemas{
"color": {Value: &openapi3.Schema{
Type: &openapi3.Types{"string"},
Enum: []any{"red", "green", "blue"},
}},
},
}},
},
}}
},
}
astMap := map[string][]string{EnumKey([]any{"red", "green", "blue"}): {"Color"}}
if err := extractSharedEnums(doc, astMap); err != nil {
@@ -182,19 +190,27 @@ func TestExtractEnumTypeName_rejectsIncidentalPrefix(t *testing.T) {
func TestExtractSharedEnums_missReturnsError(t *testing.T) {
doc := &openapi3.T{
Components: &openapi3.Components{
Schemas: openapi3.Schemas{},
},
}
for _, name := range []string{"A", "B"} {
doc.Components.Schemas[name] = &openapi3.SchemaRef{Value: &openapi3.Schema{
Type: &openapi3.Types{"object"},
Properties: openapi3.Schemas{
"color": {Value: &openapi3.Schema{
Type: &openapi3.Types{"string"},
Enum: []any{"red", "green"},
Schemas: openapi3.Schemas{
"A": {Value: &openapi3.Schema{
Type: &openapi3.Types{"object"},
Properties: openapi3.Schemas{
"color": {Value: &openapi3.Schema{
Type: &openapi3.Types{"string"},
Enum: []any{"red", "green"},
}},
},
}},
"B": {Value: &openapi3.Schema{
Type: &openapi3.Types{"object"},
Properties: openapi3.Schemas{
"color": {Value: &openapi3.Schema{
Type: &openapi3.Types{"string"},
Enum: []any{"red", "green"},
}},
},
}},
},
}}
},
}
if err := extractSharedEnums(doc, map[string][]string{}); err == nil {
t.Fatal("expected miss error")
+141 -110
View File
@@ -33,17 +33,9 @@ func TestEnumKey_handlesNonStringValues(t *testing.T) {
}
}
func TestScanSwaggerEnumTypes(t *testing.T) {
scan := func(t *testing.T, filename, src string) (map[string][]string, error) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, filename), []byte(src), 0o644); err != nil {
t.Fatal(err)
}
return ScanSwaggerEnumTypes([]string{dir})
}
t.Run("basic", func(t *testing.T) {
got, err := scan(t, "color.go", `package fixture
func TestScanSwaggerEnumTypes_basic(t *testing.T) {
dir := t.TempDir()
src := `package fixture
// Color is a primary color.
// swagger:enum Color
@@ -54,18 +46,24 @@ const (
ColorGreen Color = "green"
ColorBlue Color = "blue"
)
`)
if err != nil {
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
}
wantKey := EnumKey([]any{"red", "green", "blue"})
if single(got, wantKey) != "Color" {
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Color")
}
})
`
if err := os.WriteFile(filepath.Join(dir, "color.go"), []byte(src), 0o644); err != nil {
t.Fatal(err)
}
t.Run("orphanAnnotation", func(t *testing.T) {
_, err := scan(t, "typo.go", `package fixture
got, err := ScanSwaggerEnumTypes([]string{dir})
if err != nil {
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
}
wantKey := EnumKey([]any{"red", "green", "blue"})
if single(got, wantKey) != "Color" {
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Color")
}
}
func TestScanSwaggerEnumTypes_orphanAnnotation(t *testing.T) {
dir := t.TempDir()
src := `package fixture
// swagger:enum Sttype
type StateType string
@@ -73,17 +71,23 @@ type StateType string
const (
StateOpen StateType = "open"
)
`)
if err == nil {
t.Fatal("expected error for annotation referencing a non-matching type name")
}
if !strings.Contains(err.Error(), "Sttype") {
t.Fatalf("error %q should mention the typo'd name Sttype", err.Error())
}
})
`
if err := os.WriteFile(filepath.Join(dir, "typo.go"), []byte(src), 0o644); err != nil {
t.Fatal(err)
}
t.Run("collision", func(t *testing.T) {
got, err := scan(t, "dup.go", `package fixture
_, err := ScanSwaggerEnumTypes([]string{dir})
if err == nil {
t.Fatal("expected error for annotation referencing a non-matching type name")
}
if !strings.Contains(err.Error(), "Sttype") {
t.Fatalf("error %q should mention the typo'd name Sttype", err.Error())
}
}
func TestScanSwaggerEnumTypes_collision(t *testing.T) {
dir := t.TempDir()
src := `package fixture
// swagger:enum Alpha
type Alpha string
@@ -98,90 +102,52 @@ const (
BetaX Beta = "x"
BetaY Beta = "y"
)
`)
if err != nil {
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
}
key := EnumKey([]any{"x", "y"})
names := got[key]
if !slices.Equal(names, []string{"Alpha", "Beta"}) {
t.Fatalf("map[%q] = %v, want [Alpha Beta]", key, names)
}
})
`
if err := os.WriteFile(filepath.Join(dir, "dup.go"), []byte(src), 0o644); err != nil {
t.Fatal(err)
}
t.Run("parseFailure", func(t *testing.T) {
_, err := scan(t, "bad.go", "package fixture\nfunc Foo() {")
if err == nil {
t.Fatal("expected parse error, got nil")
}
})
got, err := ScanSwaggerEnumTypes([]string{dir})
if err != nil {
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
}
key := EnumKey([]any{"x", "y"})
names := got[key]
if !slices.Equal(names, []string{"Alpha", "Beta"}) {
t.Fatalf("map[%q] = %v, want [Alpha Beta]", key, names)
}
}
t.Run("annotationWithoutConsts", func(t *testing.T) {
_, err := scan(t, "lonely.go", `package fixture
func TestScanSwaggerEnumTypes_parseFailure(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "bad.go"), []byte("package fixture\nfunc Foo() {"), 0o644); err != nil {
t.Fatal(err)
}
_, err := ScanSwaggerEnumTypes([]string{dir})
if err == nil {
t.Fatal("expected parse error, got nil")
}
}
func TestScanSwaggerEnumTypes_annotationWithoutConsts(t *testing.T) {
dir := t.TempDir()
src := `package fixture
// swagger:enum Lonely
type Lonely string
`)
if err == nil {
t.Fatal("expected error for annotation without consts")
}
if !strings.Contains(err.Error(), "Lonely") {
t.Fatalf("error %q should mention Lonely", err.Error())
}
})
`
if err := os.WriteFile(filepath.Join(dir, "lonely.go"), []byte(src), 0o644); err != nil {
t.Fatal(err)
}
t.Run("constsBeforeType", func(t *testing.T) {
got, err := scan(t, "shade.go", `package fixture
const (
ShadeDark Shade = "dark"
ShadeLight Shade = "light"
)
// swagger:enum Shade
type Shade string
`)
if err != nil {
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
}
wantKey := EnumKey([]any{"dark", "light"})
if single(got, wantKey) != "Shade" {
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Shade")
}
})
t.Run("groupedTypeDecl", func(t *testing.T) {
got, err := scan(t, "grouped.go", `package fixture
type (
// swagger:enum Color
Color string
// swagger:enum Shade
Shade string
)
const (
ColorRed Color = "red"
ColorBlue Color = "blue"
)
const (
ShadeDark Shade = "dark"
ShadeLight Shade = "light"
)
`)
if err != nil {
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
}
colorKey := EnumKey([]any{"red", "blue"})
shadeKey := EnumKey([]any{"dark", "light"})
if single(got, colorKey) != "Color" {
t.Fatalf("Color: map[%q] = %q, want %q", colorKey, got[colorKey], "Color")
}
if single(got, shadeKey) != "Shade" {
t.Fatalf("Shade: map[%q] = %q, want %q", shadeKey, got[shadeKey], "Shade")
}
})
_, err := ScanSwaggerEnumTypes([]string{dir})
if err == nil {
t.Fatal("expected error for annotation without consts")
}
if !strings.Contains(err.Error(), "Lonely") {
t.Fatalf("error %q should mention Lonely", err.Error())
}
}
func TestScanSwaggerEnumTypes_constsAndTypeInDifferentFiles(t *testing.T) {
@@ -216,3 +182,68 @@ type Hue string
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Hue")
}
}
func TestScanSwaggerEnumTypes_constsBeforeType(t *testing.T) {
dir := t.TempDir()
src := `package fixture
const (
ShadeDark Shade = "dark"
ShadeLight Shade = "light"
)
// swagger:enum Shade
type Shade string
`
if err := os.WriteFile(filepath.Join(dir, "shade.go"), []byte(src), 0o644); err != nil {
t.Fatal(err)
}
got, err := ScanSwaggerEnumTypes([]string{dir})
if err != nil {
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
}
wantKey := EnumKey([]any{"dark", "light"})
if single(got, wantKey) != "Shade" {
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Shade")
}
}
func TestScanSwaggerEnumTypes_groupedTypeDecl(t *testing.T) {
dir := t.TempDir()
src := `package fixture
type (
// swagger:enum Color
Color string
// swagger:enum Shade
Shade string
)
const (
ColorRed Color = "red"
ColorBlue Color = "blue"
)
const (
ShadeDark Shade = "dark"
ShadeLight Shade = "light"
)
`
if err := os.WriteFile(filepath.Join(dir, "grouped.go"), []byte(src), 0o644); err != nil {
t.Fatal(err)
}
got, err := ScanSwaggerEnumTypes([]string{dir})
if err != nil {
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
}
colorKey := EnumKey([]any{"red", "blue"})
shadeKey := EnumKey([]any{"dark", "light"})
if single(got, colorKey) != "Color" {
t.Fatalf("Color: map[%q] = %q, want %q", colorKey, got[colorKey], "Color")
}
if single(got, shadeKey) != "Shade" {
t.Fatalf("Shade: map[%q] = %q, want %q", shadeKey, got[shadeKey], "Shade")
}
}
+401 -70
View File
@@ -489,20 +489,13 @@ func TestUpdateLdapBindDn(t *testing.T) {
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
// Test cases
type testCase struct {
cases := []struct {
args []string
id int64
existingAuthSource *auth.Source
authSource *auth.Source
errMsg string
}
withConfig := func(config *ldap.Source, args ...string) testCase {
return testCase{
args: append([]string{"ldap-test", "--id", "1"}, args...),
authSource: &auth.Source{Type: auth.LDAP, Cfg: config},
}
}
cases := []testCase{
}{
// case 0
{
args: []string{
@@ -581,7 +574,16 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
},
// case 1
withConfig(&ldap.Source{}),
{
args: []string{
"ldap-test",
"--id", "1",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{},
},
},
// case 2
{
args: []string{
@@ -616,44 +618,215 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
},
// case 4
withConfig(&ldap.Source{SecurityProtocol: ldap.SecurityProtocol(1)}, "--security-protocol", "LDAPS"),
{
args: []string{
"ldap-test",
"--id", "1",
"--security-protocol", "LDAPS",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
SecurityProtocol: ldap.SecurityProtocol(1),
},
},
},
// case 5
withConfig(&ldap.Source{SkipVerify: true}, "--skip-tls-verify"),
{
args: []string{
"ldap-test",
"--id", "1",
"--skip-tls-verify",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
SkipVerify: true,
},
},
},
// case 6
withConfig(&ldap.Source{Host: "ldap-server"}, "--host", "ldap-server"),
{
args: []string{
"ldap-test",
"--id", "1",
"--host", "ldap-server",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
Host: "ldap-server",
},
},
},
// case 7
withConfig(&ldap.Source{Port: 389}, "--port", "389"),
{
args: []string{
"ldap-test",
"--id", "1",
"--port", "389",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
Port: 389,
},
},
},
// case 8
withConfig(
&ldap.Source{UserBase: "ou=Users,dc=domain,dc=org"},
"--user-search-base", "ou=Users,dc=domain,dc=org",
),
{
args: []string{
"ldap-test",
"--id", "1",
"--user-search-base", "ou=Users,dc=domain,dc=org",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
UserBase: "ou=Users,dc=domain,dc=org",
},
},
},
// case 9
withConfig(
&ldap.Source{Filter: "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)"},
"--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
),
{
args: []string{
"ldap-test",
"--id", "1",
"--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
Filter: "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
},
},
},
// case 10
withConfig(
&ldap.Source{AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)"},
"--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
),
{
args: []string{
"ldap-test",
"--id", "1",
"--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
},
},
},
// case 11
withConfig(&ldap.Source{AttributeUsername: "uid"}, "--username-attribute", "uid"),
{
args: []string{
"ldap-test",
"--id", "1",
"--username-attribute", "uid",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
AttributeUsername: "uid",
},
},
},
// case 12
withConfig(&ldap.Source{AttributeName: "givenName"}, "--firstname-attribute", "givenName"),
{
args: []string{
"ldap-test",
"--id", "1",
"--firstname-attribute", "givenName",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
AttributeName: "givenName",
},
},
},
// case 13
withConfig(&ldap.Source{AttributeSurname: "sn"}, "--surname-attribute", "sn"),
{
args: []string{
"ldap-test",
"--id", "1",
"--surname-attribute", "sn",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
AttributeSurname: "sn",
},
},
},
// case 14
withConfig(&ldap.Source{AttributeMail: "mail"}, "--email-attribute", "mail"),
{
args: []string{
"ldap-test",
"--id", "1",
"--email-attribute", "mail",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
AttributeMail: "mail",
},
},
},
// case 15
withConfig(&ldap.Source{AttributesInBind: true}, "--attributes-in-bind"),
{
args: []string{
"ldap-test",
"--id", "1",
"--attributes-in-bind",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
AttributesInBind: true,
},
},
},
// case 16
withConfig(&ldap.Source{AttributeSSHPublicKey: "publickey"}, "--public-ssh-key-attribute", "publickey"),
{
args: []string{
"ldap-test",
"--id", "1",
"--public-ssh-key-attribute", "publickey",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
AttributeSSHPublicKey: "publickey",
},
},
},
// case 17
withConfig(&ldap.Source{BindDN: "cn=readonly,dc=domain,dc=org"}, "--bind-dn", "cn=readonly,dc=domain,dc=org"),
{
args: []string{
"ldap-test",
"--id", "1",
"--bind-dn", "cn=readonly,dc=domain,dc=org",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
BindDN: "cn=readonly,dc=domain,dc=org",
},
},
},
// case 18
withConfig(&ldap.Source{BindPassword: "secret"}, "--bind-password", "secret"),
{
args: []string{
"ldap-test",
"--id", "1",
"--bind-password", "secret",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
BindPassword: "secret",
},
},
},
// case 19
{
args: []string{
@@ -668,7 +841,19 @@ func TestUpdateLdapBindDn(t *testing.T) {
},
},
// case 20
withConfig(&ldap.Source{SearchPageSize: 12}, "--page-size", "12"),
{
args: []string{
"ldap-test",
"--id", "1",
"--page-size", "12",
},
authSource: &auth.Source{
Type: auth.LDAP,
Cfg: &ldap.Source{
SearchPageSize: 12,
},
},
},
// case 21
{
args: []string{
@@ -779,20 +964,13 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
// Test cases
type testCase struct {
cases := []struct {
args []string
id int64
existingAuthSource *auth.Source
authSource *auth.Source
errMsg string
}
withConfig := func(config *ldap.Source, args ...string) testCase {
return testCase{
args: append([]string{"ldap-test", "--id", "1"}, args...),
authSource: &auth.Source{Type: auth.DLDAP, Cfg: config},
}
}
cases := []testCase{
}{
// case 0
{
args: []string{
@@ -842,7 +1020,16 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
},
// case 1
withConfig(&ldap.Source{}),
{
args: []string{
"ldap-test",
"--id", "1",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{},
},
},
// case 2
{
args: []string{
@@ -877,43 +1064,187 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
},
},
// case 4
withConfig(&ldap.Source{SecurityProtocol: ldap.SecurityProtocol(2)}, "--security-protocol", "starttls"),
{
args: []string{
"ldap-test",
"--id", "1",
"--security-protocol", "starttls",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{
SecurityProtocol: ldap.SecurityProtocol(2),
},
},
},
// case 5
withConfig(&ldap.Source{SkipVerify: true}, "--skip-tls-verify"),
{
args: []string{
"ldap-test",
"--id", "1",
"--skip-tls-verify",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{
SkipVerify: true,
},
},
},
// case 6
withConfig(&ldap.Source{Host: "ldap-server"}, "--host", "ldap-server"),
{
args: []string{
"ldap-test",
"--id", "1",
"--host", "ldap-server",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{
Host: "ldap-server",
},
},
},
// case 7
withConfig(&ldap.Source{Port: 987}, "--port", "987"),
{
args: []string{
"ldap-test",
"--id", "1",
"--port", "987",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{
Port: 987,
},
},
},
// case 8
withConfig(
&ldap.Source{UserBase: "ou=Users,dc=domain,dc=org"},
"--user-search-base", "ou=Users,dc=domain,dc=org",
),
{
args: []string{
"ldap-test",
"--id", "1",
"--user-search-base", "ou=Users,dc=domain,dc=org",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{
UserBase: "ou=Users,dc=domain,dc=org",
},
},
},
// case 9
withConfig(
&ldap.Source{Filter: "(&(objectClass=posixAccount)(cn=%s))"},
"--user-filter", "(&(objectClass=posixAccount)(cn=%s))",
),
{
args: []string{
"ldap-test",
"--id", "1",
"--user-filter", "(&(objectClass=posixAccount)(cn=%s))",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{
Filter: "(&(objectClass=posixAccount)(cn=%s))",
},
},
},
// case 10
withConfig(
&ldap.Source{AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)"},
"--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
),
{
args: []string{
"ldap-test",
"--id", "1",
"--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)",
},
},
},
// case 11
withConfig(&ldap.Source{AttributeUsername: "uid"}, "--username-attribute", "uid"),
{
args: []string{
"ldap-test",
"--id", "1",
"--username-attribute", "uid",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{
AttributeUsername: "uid",
},
},
},
// case 12
withConfig(&ldap.Source{AttributeName: "givenName"}, "--firstname-attribute", "givenName"),
{
args: []string{
"ldap-test",
"--id", "1",
"--firstname-attribute", "givenName",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{
AttributeName: "givenName",
},
},
},
// case 13
withConfig(&ldap.Source{AttributeSurname: "sn"}, "--surname-attribute", "sn"),
{
args: []string{
"ldap-test",
"--id", "1",
"--surname-attribute", "sn",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{
AttributeSurname: "sn",
},
},
},
// case 14
withConfig(&ldap.Source{AttributeMail: "mail"}, "--email-attribute", "mail"),
{
args: []string{
"ldap-test",
"--id", "1",
"--email-attribute", "mail",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{
AttributeMail: "mail",
},
},
},
// case 15
withConfig(&ldap.Source{AttributeSSHPublicKey: "publickey"}, "--public-ssh-key-attribute", "publickey"),
{
args: []string{
"ldap-test",
"--id", "1",
"--public-ssh-key-attribute", "publickey",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{
AttributeSSHPublicKey: "publickey",
},
},
},
// case 16
withConfig(
&ldap.Source{UserDN: "cn=%s,ou=Users,dc=domain,dc=org"},
"--user-dn", "cn=%s,ou=Users,dc=domain,dc=org",
),
{
args: []string{
"ldap-test",
"--id", "1",
"--user-dn", "cn=%s,ou=Users,dc=domain,dc=org",
},
authSource: &auth.Source{
Type: auth.DLDAP,
Cfg: &ldap.Source{
UserDN: "cn=%s,ou=Users,dc=domain,dc=org",
},
},
},
// case 17
{
args: []string{
+105 -49
View File
@@ -16,11 +16,10 @@ import (
func TestAddOauth(t *testing.T) {
testCases := []struct {
name string
args []string
config *oauth2.Source
twoFactorPolicy string
errMsg string
name string
args []string
source *auth_model.Source
errMsg string
}{
{
name: "valid config",
@@ -30,11 +29,17 @@ func TestAddOauth(t *testing.T) {
"--key", "some_key",
"--secret", "some_secret",
},
config: &oauth2.Source{
Scopes: []string{},
Provider: "github",
ClientID: "some_key",
ClientSecret: "some_secret",
source: &auth_model.Source{
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
Scopes: []string{},
Provider: "github",
ClientID: "some_key",
ClientSecret: "some_secret",
},
TwoFactorPolicy: "",
},
},
{
@@ -46,12 +51,18 @@ func TestAddOauth(t *testing.T) {
"--secret", "some_secret",
"--auto-discover-url", "https://example.com",
},
config: &oauth2.Source{
Scopes: []string{},
Provider: "openidConnect",
ClientID: "some_key",
ClientSecret: "some_secret",
OpenIDConnectAutoDiscoveryURL: "https://example.com",
source: &auth_model.Source{
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
Scopes: []string{},
Provider: "openidConnect",
ClientID: "some_key",
ClientSecret: "some_secret",
OpenIDConnectAutoDiscoveryURL: "https://example.com",
},
TwoFactorPolicy: "",
},
},
{
@@ -80,30 +91,35 @@ func TestAddOauth(t *testing.T) {
"--ssh-public-key-claim-name", "attr_ssh_pub_key",
"--full-name-claim-name", "attr_full_name",
},
config: &oauth2.Source{
Provider: "gitlab",
ClientID: "some_key",
ClientSecret: "some_secret",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: "https://example.com/token",
AuthURL: "https://example.com/auth",
ProfileURL: "https://example.com/profile",
EmailURL: "https://example.com/email",
Tenant: "some_tenant",
source: &auth_model.Source{
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
Provider: "gitlab",
ClientID: "some_key",
ClientSecret: "some_secret",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: "https://example.com/token",
AuthURL: "https://example.com/auth",
ProfileURL: "https://example.com/profile",
EmailURL: "https://example.com/email",
Tenant: "some_tenant",
},
IconURL: "https://example.com/icon",
Scopes: []string{"scope1", "scope2"},
RequiredClaimName: "claim_name",
RequiredClaimValue: "claim_value",
GroupClaimName: "group_name",
AdminGroup: "admin",
RestrictedGroup: "restricted",
GroupTeamMap: `{"group1": [1,2]}`,
GroupTeamMapRemoval: true,
SSHPublicKeyClaimName: "attr_ssh_pub_key",
FullNameClaimName: "attr_full_name",
},
IconURL: "https://example.com/icon",
Scopes: []string{"scope1", "scope2"},
RequiredClaimName: "claim_name",
RequiredClaimValue: "claim_value",
GroupClaimName: "group_name",
AdminGroup: "admin",
RestrictedGroup: "restricted",
GroupTeamMap: `{"group1": [1,2]}`,
GroupTeamMapRemoval: true,
SSHPublicKeyClaimName: "attr_ssh_pub_key",
FullNameClaimName: "attr_full_name",
TwoFactorPolicy: "skip",
},
twoFactorPolicy: "skip",
},
}
@@ -134,13 +150,7 @@ func TestAddOauth(t *testing.T) {
assert.EqualError(t, err, tc.errMsg)
} else {
assert.NoError(t, err)
assert.Equal(t, &auth_model.Source{
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: tc.config,
TwoFactorPolicy: tc.twoFactorPolicy,
}, createdSource)
assert.Equal(t, tc.source, createdSource)
}
})
}
@@ -148,10 +158,12 @@ func TestAddOauth(t *testing.T) {
func TestUpdateOauth(t *testing.T) {
testCases := []struct {
name string
args []string
authSource *auth_model.Source
errMsg string
name string
args []string
id int64
existingAuthSource *auth_model.Source
authSource *auth_model.Source
errMsg string
}{
{
name: "missing id",
@@ -162,6 +174,19 @@ func TestUpdateOauth(t *testing.T) {
},
{
name: "valid config",
id: 1,
existingAuthSource: &auth_model.Source{
ID: 1,
Type: auth_model.OAuth2,
Name: "old name",
IsActive: true,
Cfg: &oauth2.Source{
Provider: "github",
ClientID: "old_key",
ClientSecret: "old_secret",
},
TwoFactorPolicy: "",
},
args: []string{
"--id", "1",
"--name", "test",
@@ -185,6 +210,37 @@ func TestUpdateOauth(t *testing.T) {
},
{
name: "valid config with options",
id: 1,
existingAuthSource: &auth_model.Source{
ID: 1,
Type: auth_model.OAuth2,
Name: "old name",
IsActive: true,
Cfg: &oauth2.Source{
Provider: "gitlab",
ClientID: "old_key",
ClientSecret: "old_secret",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: "https://old.example.com/token",
AuthURL: "https://old.example.com/auth",
ProfileURL: "https://old.example.com/profile",
EmailURL: "https://old.example.com/email",
Tenant: "old_tenant",
},
IconURL: "https://old.example.com/icon",
Scopes: []string{"old_scope1", "old_scope2"},
RequiredClaimName: "old_claim_name",
RequiredClaimValue: "old_claim_value",
GroupClaimName: "old_group_name",
AdminGroup: "old_admin",
RestrictedGroup: "old_restricted",
GroupTeamMap: `{"old_group1": [1,2]}`,
GroupTeamMapRemoval: true,
SSHPublicKeyClaimName: "old_ssh_pub_key",
FullNameClaimName: "old_full_name",
},
TwoFactorPolicy: "",
},
args: []string{
"--id", "1",
"--name", "test",
+30 -4
View File
@@ -132,10 +132,11 @@ func TestAddSMTP(t *testing.T) {
func TestUpdateSMTP(t *testing.T) {
testCases := []struct {
name string
args []string
authSource *auth_model.Source
errMsg string
name string
args []string
existingAuthSource *auth_model.Source
authSource *auth_model.Source
errMsg string
}{
{
name: "missing id",
@@ -148,6 +149,17 @@ func TestUpdateSMTP(t *testing.T) {
},
{
name: "valid config",
existingAuthSource: &auth_model.Source{
ID: 1,
Type: auth_model.SMTP,
Name: "old name",
IsActive: true,
Cfg: &smtp.Source{
Auth: "PLAIN",
Host: "old host",
Port: 26,
},
},
args: []string{
"--id", "1",
"--name", "test",
@@ -168,6 +180,20 @@ func TestUpdateSMTP(t *testing.T) {
},
{
name: "valid config with options",
existingAuthSource: &auth_model.Source{
ID: 1,
Type: auth_model.SMTP,
Name: "old name",
IsActive: true,
Cfg: &smtp.Source{
Auth: "PLAIN",
Host: "old host",
Port: 26,
HeloHostname: "old.example.com",
AllowedDomains: "old.example.com",
},
TwoFactorPolicy: "",
},
args: []string{
"--id", "1",
"--name", "test",
+35 -22
View File
@@ -24,29 +24,42 @@ func TestAdminUserDelete(t *testing.T) {
require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.AccessToken{}))
}()
for _, tc := range []struct {
name string
args []string
}{
{"delete user by id", []string{"delete-test", "--id", ""}},
{"delete user by username", []string{"delete-test", "--username", "testuser"}},
{"delete user by email", []string{"delete-test", "--email", "testuser@gitea.local"}},
{"delete user by all 3 attributes", []string{"delete", "--id", "", "--username", "testuser", "--email", "testuser@gitea.local"}},
} {
t.Run(tc.name, func(t *testing.T) {
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
require.NoError(t, err)
if tc.args[1] == "--id" {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
tc.args[2] = strconv.FormatInt(user.ID, 10)
}
err = microcmdUserDelete().Run(ctx, tc.args)
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
})
setupTestUser := func(t *testing.T) {
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
require.NoError(t, err)
}
t.Run("delete user by id", func(t *testing.T) {
setupTestUser(t)
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--id", strconv.FormatInt(u.ID, 10)})
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
})
t.Run("delete user by username", func(t *testing.T) {
setupTestUser(t)
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--username", "testuser"})
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
})
t.Run("delete user by email", func(t *testing.T) {
setupTestUser(t)
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--email", "testuser@gitea.local"})
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
})
t.Run("delete user by all 3 attributes", func(t *testing.T) {
setupTestUser(t)
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserDelete().Run(ctx, []string{"delete", "--id", strconv.FormatInt(u.ID, 10), "--username", "testuser", "--email", "testuser@gitea.local"})
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
})
}
func TestAdminUserDeleteFailure(t *testing.T) {
+57 -24
View File
@@ -18,28 +18,61 @@ func TestMustChangePassword(t *testing.T) {
defer func() {
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
}()
for _, name := range []string{"testuser", "testuserexclude"} {
require.NoError(t, microcmdUserCreate().Run(t.Context(), []string{"create", "--username", name, "--email", name + "@gitea.local", "--random-password"}))
}
for _, tc := range []struct {
args []string
assertUser, assertExcludedUser assert.BoolAssertionFunc
}{
// Reset password change flag
{[]string{"change-test", "--all", "--unset"}, assert.False, assert.False},
// Make all users change password
{[]string{"change-test", "--all"}, assert.True, assert.True},
// Reset password change flag but exclude all tested users
{[]string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"}, assert.True, assert.True},
// Reset password change flag by listing multiple users
{[]string{"change-test", "--unset", "testuser", "testuserexclude"}, assert.False, assert.False},
// Exclude a user from all user
{[]string{"change-test", "--all", "--exclude", "testuserexclude"}, assert.True, assert.False},
// Unset a flag for single user
{[]string{"change-test", "--unset", "testuser"}, assert.False, assert.False},
} {
require.NoError(t, microcmdUserMustChangePassword().Run(t.Context(), tc.args))
tc.assertUser(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}).MustChangePassword)
tc.assertExcludedUser(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}).MustChangePassword)
}
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
require.NoError(t, err)
err = microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuserexclude", "--email", "testuserexclude@gitea.local", "--random-password"})
require.NoError(t, err)
// Reset password change flag
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset"})
require.NoError(t, err)
testUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.False(t, testUser.MustChangePassword)
testUserExclude := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.False(t, testUserExclude.MustChangePassword)
// Make all users change password
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.True(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.True(t, testUserExclude.MustChangePassword)
// Reset password change flag but exclude all tested users
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.True(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.True(t, testUserExclude.MustChangePassword)
// Reset password change flag by listing multiple users
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser", "testuserexclude"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.False(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.False(t, testUserExclude.MustChangePassword)
// Exclude a user from all user
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--exclude", "testuserexclude"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.True(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.False(t, testUserExclude.MustChangePassword)
// Unset a flag for single user
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.False(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.False(t, testUserExclude.MustChangePassword)
}
+79 -51
View File
@@ -76,82 +76,110 @@ func TestCliCmd(t *testing.T) {
cli.SubcommandHelpTemplate = "(subcommand help template)"
cases := []struct {
env map[string]string
cmds []string
exp string
env map[string]string
cmd string
exp string
}{
// help commands
{
cmds: []string{"./gitea -h", "./gitea help"},
exp: "DEFAULT CONFIGURATION:",
cmd: "./gitea -h",
exp: "DEFAULT CONFIGURATION:",
},
{
cmds: []string{
"./gitea -c /dev/null -h",
"./gitea -c /dev/null help",
"./gitea help -c /dev/null",
"./gitea -c /dev/null test-cmd -h",
"./gitea test-cmd -c /dev/null -h",
"./gitea test-cmd -h -c /dev/null",
"./gitea -c /dev/null test-cmd help",
"./gitea test-cmd -c /dev/null help",
"./gitea test-cmd help -c /dev/null",
},
cmd: "./gitea help",
exp: "DEFAULT CONFIGURATION:",
},
{
cmd: "./gitea -c /dev/null -h",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea -c /dev/null help",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea help -c /dev/null",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea -c /dev/null test-cmd -h",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea test-cmd -c /dev/null -h",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea test-cmd -h -c /dev/null",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea -c /dev/null test-cmd help",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea test-cmd -c /dev/null help",
exp: "ConfigFile: /dev/null",
},
{
cmd: "./gitea test-cmd help -c /dev/null",
exp: "ConfigFile: /dev/null",
},
// parse paths
{
cmds: []string{"./gitea test-cmd"},
exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf),
cmd: "./gitea test-cmd",
exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf),
},
{
cmds: []string{"./gitea -c /tmp/app.ini test-cmd"},
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
cmd: "./gitea -c /tmp/app.ini test-cmd",
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
},
{
cmds: []string{"./gitea test-cmd -c /tmp/app.ini"},
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
cmd: "./gitea test-cmd -c /tmp/app.ini",
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
},
{
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
cmds: []string{"./gitea test-cmd"},
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"),
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
cmd: "./gitea test-cmd",
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"),
},
{
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
cmds: []string{"./gitea test-cmd --work-path /tmp/other"},
exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"),
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
cmd: "./gitea test-cmd --work-path /tmp/other",
exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"),
},
{
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
cmds: []string{"./gitea test-cmd --config /tmp/app-other.ini"},
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"),
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
cmd: "./gitea test-cmd --config /tmp/app-other.ini",
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"),
},
}
for _, c := range cases {
for _, command := range c.cmds {
t.Run(command, func(t *testing.T) {
app := newTestApp(cli.Command{
Action: func(ctx context.Context, cmd *cli.Command) error {
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
},
})
for k, v := range c.env {
t.Setenv(k, v)
}
args := strings.Split(command, " ") // for test only, "split" is good enough
r, err := runTestApp(app, args...)
assert.NoError(t, err, command)
assert.NotEmpty(t, c.exp, command)
if !assert.Contains(t, r.Stdout, c.exp, command) {
t.Log("Full output:\n" + r.Stdout)
t.Log("Expected:\n" + c.exp)
}
t.Run(c.cmd, func(t *testing.T) {
app := newTestApp(cli.Command{
Action: func(ctx context.Context, cmd *cli.Command) error {
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
},
})
}
for k, v := range c.env {
t.Setenv(k, v)
}
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
r, err := runTestApp(app, args...)
assert.NoError(t, err, c.cmd)
assert.NotEmpty(t, c.exp, c.cmd)
if !assert.Contains(t, r.Stdout, c.exp, c.cmd) {
t.Log("Full output:\n" + r.Stdout)
t.Log("Expected:\n" + c.exp)
}
})
}
}
+8 -2
View File
@@ -13,11 +13,13 @@ import (
"gitea.dev/models/db"
"gitea.dev/modules/dump"
"gitea.dev/modules/json"
"gitea.dev/modules/log"
"gitea.dev/modules/setting"
"gitea.dev/modules/storage"
"gitea.dev/modules/util"
"gitea.com/go-chi/session"
"github.com/urfave/cli/v3"
)
@@ -247,8 +249,12 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
log.Info("Packing data directory...%s", setting.AppDataPath)
var excludes []string
if setting.SessionConfig.Provider == "file" {
excludes = append(excludes, setting.SessionConfig.ProviderConfig)
if setting.SessionConfig.OriginalProvider == "file" {
var opts session.Options
if err = json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil {
return err
}
excludes = append(excludes, opts.ProviderConfig)
}
if cmd.IsSet("skip-index") && cmd.Bool("skip-index") {
+1 -3
View File
@@ -33,7 +33,7 @@ func newDumpRepositoryCommand() *cli.Command {
&cli.StringFlag{
Name: "git_service",
Value: "",
Usage: "Git service, git, github, gitea, forgejo, gitlab. If clone_addr could be recognized, this could be ignored.",
Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.",
},
&cli.StringFlag{
Name: "repo_dir",
@@ -117,8 +117,6 @@ func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
serviceStr = "gitlab"
} else if strings.HasPrefix(strings.ToLower(cloneAddr), "https://gitea.com/") {
serviceStr = "gitea"
} else if strings.HasPrefix(strings.ToLower(cloneAddr), "https://codeberg.org/") {
serviceStr = "forgejo"
}
if serviceStr == "" {
return errors.New("git_service missed or clone_addr cannot be recognized")
+50 -14
View File
@@ -169,6 +169,10 @@ func parseGitHookCommitRefLine(line string) (oldCommitID, newCommitID string, re
}
func runHookPreReceive(ctx context.Context, c *cli.Command) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil
}
setup(ctx, c.Bool("debug"))
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
@@ -209,6 +213,7 @@ Gitea or set your environment appropriately.`, "")
refFullNames := make([]git.RefName, hookBatchSize)
count := 0
total := 0
lastline := 0
out := io.Discard
if setting.Git.VerbosePush {
@@ -221,6 +226,8 @@ Gitea or set your environment appropriately.`, "")
}
}
supportProcReceive := git.DefaultFeatures().SupportProcReceive
for scanner.Scan() {
// TODO: support news feeds for wiki
if isWiki {
@@ -233,23 +240,37 @@ Gitea or set your environment appropriately.`, "")
}
total++
oldCommitIDs[count] = oldCommitID
newCommitIDs[count] = newCommitID
refFullNames[count] = refFullName
count++
fmt.Fprintf(out, "*")
lastline++
if count >= hookBatchSize {
fmt.Fprintf(out, " Checking %d references\n", count)
// If the ref is a branch or tag, check if it's protected
// if supportProcReceive all ref should be checked because
// permission check was delayed
if supportProcReceive || refFullName.IsBranch() || refFullName.IsTag() {
oldCommitIDs[count] = oldCommitID
newCommitIDs[count] = newCommitID
refFullNames[count] = refFullName
count++
fmt.Fprintf(out, "*")
hookOptions.OldCommitIDs = oldCommitIDs
hookOptions.NewCommitIDs = newCommitIDs
hookOptions.RefFullNames = refFullNames
extra := private.HookPreReceive(ctx, ownerName, repoName, hookOptions)
if extra.HasError() {
return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error)
if count >= hookBatchSize {
fmt.Fprintf(out, " Checking %d references\n", count)
hookOptions.OldCommitIDs = oldCommitIDs
hookOptions.NewCommitIDs = newCommitIDs
hookOptions.RefFullNames = refFullNames
extra := private.HookPreReceive(ctx, ownerName, repoName, hookOptions)
if extra.HasError() {
return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error)
}
count = 0
lastline = 0
}
count = 0
} else {
fmt.Fprintf(out, ".")
}
if lastline >= hookBatchSize {
fmt.Fprintf(out, "\n")
lastline = 0
}
}
if err := scanner.Err(); err != nil {
@@ -267,6 +288,8 @@ Gitea or set your environment appropriately.`, "")
if extra.HasError() {
return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error)
}
} else if lastline > 0 {
fmt.Fprintf(out, "\n")
}
fmt.Fprintf(out, "Checked %d references in total\n", total)
@@ -276,6 +299,10 @@ Gitea or set your environment appropriately.`, "")
// runHookUpdate avoid to do heavy operations on update hook because it will be
// invoked for every ref update which does not like pre-receive and post-receive
func runHookUpdate(_ context.Context, c *cli.Command) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil
}
// Update is empty and is kept only for backwards compatibility
if len(os.Args) < 3 {
return nil
@@ -296,6 +323,11 @@ func runHookPostReceive(ctx context.Context, c *cli.Command) error {
return fmt.Errorf("failed to call 'git update-server-info': %w", err)
}
// Now if we're an internal don't do anything else
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil
}
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
if setting.OnlyAllowPushIfGiteaEnvironmentSet {
return fail(ctx, `Rejecting changes as Gitea environment not set.
@@ -443,6 +475,10 @@ Gitea or set your environment appropriately.`, "")
return nil
}
if !git.DefaultFeatures().SupportProcReceive {
return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.")
}
reader := bufio.NewReader(os.Stdin)
repoUser := os.Getenv(repo_module.EnvRepoUsername)
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
+6 -4
View File
@@ -195,10 +195,12 @@ func runServ(ctx context.Context, c *cli.Command) error {
}
if len(sshCmdArgs) < 2 {
// for AGit Flow
if cmd == "ssh_info" {
cprintf(c, "%s", agit.SshInfoJson)
return nil
if git.DefaultFeatures().SupportProcReceive {
// for AGit Flow
if cmd == "ssh_info" {
cprintf(c, "%s", agit.SshInfoJson)
return nil
}
}
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
}
+34 -35
View File
@@ -155,7 +155,7 @@
;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
;BUILTIN_SSH_SERVER_USER =
;;
;; Domain name to be exposed in clone URL, defaults to the domain part of ROOT_URL
;; Domain name to be exposed in clone URL, defaults to DOMAIN or the domain part of ROOT_URL
;SSH_DOMAIN =
;;
;; Port number to be exposed in clone URL.
@@ -198,7 +198,7 @@
;; For the built-in SSH server, choose the keypair to offer as the host key
;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
;; relative paths are made absolute relative to the APP_DATA_PATH
;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gitea.ed25519, ssh/gitea.ecdsa, ssh/gogs.rsa
;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa
;;
;; Enable SSH Authorized Key Backup when rewriting all keys, default is false
;SSH_AUTHORIZED_KEYS_BACKUP = false
@@ -237,7 +237,7 @@
;SSH_PER_WRITE_PER_KB_TIMEOUT = 30s
;;
;; Indicate whether to check minimum key size with corresponding type
;MINIMUM_KEY_SIZE_CHECK = true
;MINIMUM_KEY_SIZE_CHECK = false
;;
;; TLS Settings: Either ACME or manual
;; (Other common TLS configuration are found before)
@@ -459,7 +459,7 @@ INTERNAL_TOKEN =
;LOGIN_REMEMBER_DAYS = 31
;;
;; Name of cookie used to store authentication information.
;COOKIE_REMEMBER_NAME = gitea_remember
;COOKIE_REMEMBER_NAME = gitea_incredible
;;
;; URL or path that Gitea should redirect users to *after* performing its own logout.
;; Use this, if needed, when authentication is handled by a reverse proxy or SSO.
@@ -471,8 +471,7 @@ INTERNAL_TOKEN =
;REVERSE_PROXY_AUTHENTICATION_EMAIL = X-WEBAUTH-EMAIL
;REVERSE_PROXY_AUTHENTICATION_FULL_NAME = X-WEBAUTH-FULLNAME
;;
;; Interpret the X-Real-IP, X-Forwarded-For or Forwarded header, in that order, and set this as the remote IP for the request.
;; The client port is taken from the header value or, with X-Real-IP, from X-Real-Port.
;; Interpret X-Forwarded-For header or the X-Real-IP header and set this as the remote IP for the request
;REVERSE_PROXY_LIMIT = 1
;;
;; List of IP addresses and networks separated by comma of trusted proxy servers. Use `*` to trust all.
@@ -637,10 +636,10 @@ ENABLED = true
;; Pre-register OAuth2 applications for some universally useful services
;; * https://github.com/hickford/git-credential-oauth
;; * https://github.com/git-ecosystem/git-credential-manager
;; * https://gitea.com/gitea/cli
;; * https://gitea.com/gitea/tea
;; * Gitea App (the official Gitea mobile app)
;; * https://gitea.com/gitea/gitea-vscode
;DEFAULT_APPLICATIONS = git-credential-oauth, git-credential-manager, gitea-cli, gitea-app, gitea-vscode
;DEFAULT_APPLICATIONS = git-credential-oauth, git-credential-manager, tea, gitea-app, gitea-vscode
;;
;; By default, OAuth2 applications can only use "http" and "https" as their redirect URI schemes.
;; If you need to use other schemes (e.g. for desktop applications), you can specify them here as a comma-separated list.
@@ -784,6 +783,8 @@ LEVEL = Info
;; Respond to pushes to a non-default branch with a URL for creating a Pull Request (if the repository has them enabled)
;PULL_REQUEST_PUSH_MESSAGE = true
;;
;; (Go-Git only) Don't cache objects greater than this in memory. (Set to 0 to disable.)
;LARGE_OBJECT_THRESHOLD = 1048576
;; Set to true to forcibly set core.protectNTFS=false
;DISABLE_CORE_PROTECT_NTFS=false
;; Disable the usage of using partial clones for git.
@@ -838,7 +839,7 @@ LEVEL = Info
;EMAIL_DOMAIN_BLOCKLIST =
;;
;; Disallow registration, only allow admins to create accounts.
;DISABLE_REGISTRATION = true
;DISABLE_REGISTRATION = false
;;
;; Allow registration only using gitea itself, it works only when DISABLE_REGISTRATION is false
;ALLOW_ONLY_INTERNAL_REGISTRATION = false
@@ -867,10 +868,9 @@ LEVEL = Info
;; Allow users to sign-in with a passkey
;ENABLE_PASSKEY_AUTHENTICATION = true
;;
;; Reverse proxy authentication for the web interface, Git over HTTP, Git LFS and the package registry.
;; Gitea must only be reachable through the proxy, which must set or remove the authentication headers on every request.
;; More detail: https://github.com/gogits/gogs/issues/165
;ENABLE_REVERSE_PROXY_AUTHENTICATION = false
;; Reverse proxy authentication for the REST API under /api/v1.
; Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible.
;ENABLE_REVERSE_PROXY_AUTHENTICATION_API = false
;ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
;ENABLE_REVERSE_PROXY_EMAIL = false
@@ -971,11 +971,12 @@ LEVEL = Info
;; Value for the domain part of the user's email address in the git log if user
;; has set KeepEmailPrivate to true. The user's email will be replaced with a
;; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. Default
;; value is "noreply." + the domain part of ROOT_URL
;NO_REPLY_ADDRESS =
;; value is "noreply." + DOMAIN, where DOMAIN resolves to the value from server.DOMAIN
;; Note: do not use the <DOMAIN> notation below
;NO_REPLY_ADDRESS = ; noreply.<DOMAIN>
;;
;; Show Registration button, defaults to true only if both DISABLE_REGISTRATION and ALLOW_ONLY_EXTERNAL_REGISTRATION are false
;SHOW_REGISTRATION_BUTTON = false
;; Show Registration button
;SHOW_REGISTRATION_BUTTON = true
;;
;; Show milestones dashboard page - a view of all the user's milestones
;SHOW_MILESTONES_DASHBOARD_PAGE = true
@@ -1221,9 +1222,8 @@ LEVEL = Info
;; In default merge messages only include approvers who are official
;DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY = true
;;
;; Set default squash merge commit message body, valid options: PR_BODY (pull request description), COMMIT_MESSAGES (commit messages), BLANK (none)
;; Repositories store the current value when created or when their settings are next saved, and can change it there.
;DEFAULT_SQUASH_MERGE_MESSAGE = PR_BODY
;; In default squash-merge messages include the commit message of all commits comprising the pull request.
;POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES = false
;;
;; Add co-authored-by and co-committed-by trailers if committer does not match author
;ADD_CO_COMMITTER_TRAILERS = true
@@ -1437,14 +1437,11 @@ LEVEL = Info
;; The icon theme for folders (basic/material)
;FOLDER_ICON_THEME = basic
;;
;; All available reactions users can choose on issues, pull requests and comments.
;; All available reactions users can choose on issues/prs and comments.
;; Values can be emoji alias (:smile:) or a unicode emoji.
;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
;REACTIONS = +1, -1, laugh, hooray, confused, heart, rocket, eyes
;;
;; Reactions available for releases
;;RELEASE_REACTIONS = +1, laugh, hooray, heart, rocket, eyes
;;
;; Change the number of users that are displayed in reactions tooltip (triggered by mouse hover).
;REACTION_MAX_USER_NUM = 10
;;
@@ -1677,13 +1674,13 @@ LEVEL = Info
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; General queue type, currently support: level, channel, redis, dummy
;; default to level
;TYPE = level
;; General queue queue type, currently support: persistable-channel, channel, level, redis, dummy
;; default to persistable-channel
;TYPE = persistable-channel
;;
;; data-dir for storing level queues, individual queues will default to `queues/common` meaning the queue is shared.
;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared.
;; Relative paths will be made absolute against "APP_DATA_PATH"
;DATADIR = queues/common
;DATADIR = queues/
;;
;; Default queue length before a channel queue will block
;LENGTH = 100000
@@ -1691,7 +1688,7 @@ LEVEL = Info
;; Batch size to send for batched queues
;BATCH_LENGTH = 20
;;
;; When `TYPE` is `level`, this provides a directory for the underlying leveldb
;; When `TYPE` is `persistable-channel`, this provides a directory for the underlying leveldb
;; or additional options of the form `leveldb://path/to/db?option=value&....`, and will override `DATADIR`.
;; When `TYPE` is `redis` and this is left empty, it falls back to the shared [redis] CONN_STR.
;CONN_STR =
@@ -1759,6 +1756,7 @@ LEVEL = Info
;ENABLE_OPENID_SIGNIN = false
;;
;; Whether to allow registering via OpenID
;; Do not include to rely on rhw DISABLE_REGISTRATION setting
;;ENABLE_OPENID_SIGNUP = false
;;
;; Allowed URI patterns (POSIX regexp).
@@ -1989,7 +1987,7 @@ LEVEL = Info
;; Either "memory", "redis", "memcache", or "twoqueue". default is "memory"
;ADAPTER = memory
;;
;; For "memory" and "twoqueue", GC interval in seconds, default is 60
;; For "memory" only, GC interval in seconds, default is 60
;INTERVAL = 60
;;
;; For "redis" and "memcache", connection host address
@@ -2020,17 +2018,19 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Either "memory", "file", "redis" or "db", default is "file". "db" will reuse the configuration in [database]
;PROVIDER = file
;; Either "memory", "file", "redis", "db", "mysql", "couchbase", "memcache" or "postgres"
;; Default is "memory". "db" will reuse the configuration in [database]
;PROVIDER = memory
;;
;; Provider config options
;; memory: doesn't have any config yet
;; file: session file path, e.g. `data/sessions`, relative paths will be made absolute against _`AppWorkPath`_.
;; redis: default to [redis] CONN_STR
;; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
;PROVIDER_CONFIG =
;;
;; Session cookie name
;COOKIE_NAME = gitea_session
;COOKIE_NAME = i_like_gitea
;;
;; If you use session in https only: true or false. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
;COOKIE_SECURE =
@@ -2056,8 +2056,7 @@ LEVEL = Info
;; How Gitea deals with missing repository avatars
;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
;REPOSITORY_AVATAR_FALLBACK = none
;; Image URL for the "image" fallback, used as-is, defaults to Gitea's builtin repository avatar
;REPOSITORY_AVATAR_FALLBACK_IMAGE =
;REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png
;;
;; Max Width and Height of uploaded avatars.
;; This is to limit the amount of RAM used when resizing the image.
+8
View File
@@ -25,6 +25,7 @@ Depending on requirements, the following build tags can be included.
- `bindata`: Build a single monolithic binary, with all assets included. Required for distribution and production build.
- `pam`: Enable support for PAM (Linux Pluggable Authentication Modules).
Can be used to authenticate local users or extend authentication to methods available to PAM.
- `gogit`: (EXPERIMENTAL) Use go-git variants of Git commands.
To include all assets, use the `bindata` tag:
@@ -32,6 +33,13 @@ To include all assets, use the `bindata` tag:
TAGS="bindata" make build
```
Tag `gogit` is used to try to resolve some Windows-specific performance problems, POSIX systems don't need it.
You can build a Windows binary by:
```bash
GOOS=windows TAGS="bindata gogit" make build
```
## Changing default paths
Gitea will search for a number of things from the _`CustomPath`_.
+7 -5
View File
@@ -74,13 +74,15 @@ Changes to Gitea must be reviewed before they are accepted, including changes fr
We require two maintainer approvals for every PR. When that is satisfied, your PR gets the `lgtm/done` label. After that, you mainly fix merge conflicts and respond to or implement maintainer requests; maintainers drive getting the PR merged.
If a PR has `lgtm/done`, no open discussions, and no merge conflicts, a merger rewrites its title and description into the final [commit message](#commit-messages) and clicks **Merge when ready**. That adds the PR to the [merge queue](https://github.com/go-gitea/gitea/queue/main), which tests it on top of the latest `main` and the PRs ahead of it, and merges it once CI passes. Edits to the title and description no longer reach the commit once the queue starts building the PR, so make them before queueing.
If a PR has `lgtm/done`, no open discussions, and no merge conflicts, any maintainer may add `reviewed/wait-merge`. That puts the PR in the merge queue. PRs are merged from the queue in the order of this list:
Gitea uses its own tool, <https://github.com/go-gitea/gitea/blob/main/.github/workflows/giteabot.yml>, to automate parts of the review process. It:
<https://github.com/go-gitea/gitea/pulls?q=is%3Apr+label%3Areviewed%2Fwait-merge+sort%3Acreated-asc+is%3Aopen>
Gitea uses its own tool, <https://github.com/go-gitea/gitea/blob/main/.github/workflows/giteabot.yml>, to automate parts of the review process. The backporter:
- Counts maintainer approvals and sets the `lgtm/*` labels and the `giteabot/lgtm` status.
- Takes a PR out of the merge queue when it loses its approval.
- Creates a backport PR when needed after the initial PR merges.
- Removes the PR from the merge queue after it merges.
- Keeps the oldest branch in the merge queue up to date with merges.
### Final call
@@ -162,7 +164,7 @@ Any account with write access (including bots and TOC members) **must** use [2FA
Mergers are the maintainers who carry out the final merge of approved PRs. Their responsibilities, described throughout this guide, are:
- Adding PRs to the [merge queue](#getting-prs-merged) once a PR has `lgtm/done`, no open discussions, and no merge conflicts.
- Merging PRs from the [merge queue](#getting-prs-merged) in order, once a PR has `lgtm/done`, no open discussions, and no merge conflicts.
- Rewriting the PR title and description prior to the merge, making the [commit message](#commit-messages) clear.\
In particular, mergers should edit the PR description.\
Mergers should **not** edit the actual commit message except to remove unnecessary information. Because of that, even if users are looking at the PR, they can understand what changed.
+1 -4
View File
@@ -105,13 +105,10 @@ When adding configuration options it is not enough to add them to the
[`custom/conf/app.example.ini`](../custom/conf/app.example.ini), and document them in
the [configuration cheat sheet](https://docs.gitea.com/administration/config-cheat-sheet),
which lives in the [documentation repository](https://gitea.com/gitea/docs).
Every `app.example.ini` change needs a companion PR there that copies the ini text
verbatim into every affected version's cheat sheet, translated where feasible, with a
`WIP:` title prefix until the Gitea PR merges.
## Database migrations
Avoid migrations unless the alternative is a workaround. If you make breaking changes to a database-persisted struct under `models/`, add a
If you make breaking changes to a database-persisted struct under `models/`, add a
new migration in `modelmigration/`. See [testing.md](testing.md#migration-tests)
for running the migration tests.
+2 -15
View File
@@ -74,19 +74,6 @@ conditional `UPDATE` or a lock.
plain `Sync`.
- When inserting rows with preset IDs, MSSQL requires `SET IDENTITY_INSERT` to be
enabled and PostgreSQL requires the sequence to be updated afterwards.
- Struct conditions skip zero-valued fields, so `Get(&Reaction{IssueID: 1})` also
matches comment reactions. Use `Where` or `builder` when a zero value matters.
- Struct updates skip zero-valued fields unless selected by `Cols` or `AllCols()`.
`Cols` silently ignores unknown names. `updated` columns are always written unless
`NoAutoTime()` is used.
- `xorm:"deleted"` columns count both `0` and `NULL` as not deleted, so hand-written
conditions must accept both.
- On every start, `SyncAllTables` drops indexes absent from the model, so declare
migration-created indexes there, including in backports. It only warns about
nullability and default changes, so apply them through migrations.
- `ForUpdate` only works on MySQL and PostgreSQL. MSSQL rejects TEXT columns in
struct conditions and inserts of more than 1000 rows.
- Paginated queries need a unique tiebreaker like `id` for a stable order.
## Dependencies
@@ -140,8 +127,8 @@ breaking v1.
### Adding and maintaining API routes
- Document success responses in the swagger comments on the route. Replace explicit
error responses with a `default` response referencing `#/responses/error`.
- All possible results (errors, success, and failure messages) must be documented in
the swagger comments on the route.
- Every JSON request body must be defined as a struct in `modules/structs/` and
registered in [`routers/api/v1/swagger/options.go`](../routers/api/v1/swagger/options.go).
- Every JSON response must be defined as a struct in `modules/structs/` and
-4
View File
@@ -48,10 +48,6 @@ Run a single integration test with the `#` selector:
make test-integration#TestName
```
Each run gets its own work directory under `tests/integration/` and random ports, so SQLite runs
can execute in parallel. Runs against other databases still share the database and object storage.
The work directory is removed after a passing run and kept after a failure.
If you hit errors such as a mismatched database version or SSH push failures, try a
clean rebuild first:
+29 -55
View File
@@ -17,7 +17,6 @@ import unescapedHtmlLiteral from './tools/eslint-rules/unescaped-html-literal.ts
const jsExts = ['js', 'mjs', 'cjs'] as const;
const tsExts = ['ts', 'mts', 'cts'] as const;
const vueExts = ['vue'] as const;
const restrictedGlobals = [
{name: 'localStorage', message: 'Use `modules/user-settings.ts` instead.'},
@@ -37,7 +36,7 @@ export default defineConfig([
'public/assets/js',
]),
{
files: [`**/*.{${[...jsExts, ...tsExts, ...vueExts].join(',')}}`],
files: [`**/*.{${[...jsExts, ...tsExts].join(',')}}`],
ignores: ['dist/*'],
languageOptions: {
ecmaVersion: 'latest',
@@ -49,9 +48,7 @@ export default defineConfig([
ecmaFeatures: {
impliedStrict: true,
},
parser: typescriptParser,
project: true,
extraFileExtensions: vueExts.map((ext) => `.${ext}`),
},
},
linterOptions: {
@@ -69,11 +66,8 @@ export default defineConfig([
wc,
},
settings: {
'import-x/extensions': [...jsExts, ...tsExts, ...vueExts].map((ext) => `.${ext}`),
'import-x/parsers': {
'@typescript-eslint/parser': [...jsExts, ...tsExts].map((ext) => `.${ext}`),
'vue-eslint-parser': vueExts.map((ext) => `.${ext}`),
},
'import-x/extensions': [...jsExts, ...tsExts].map((ext) => `.${ext}`),
'import-x/parsers': {'@typescript-eslint/parser': [...jsExts, ...tsExts].map((ext) => `.${ext}`)},
'import-x/resolver': {'typescript': true},
},
rules: {
@@ -239,7 +233,6 @@ export default defineConfig([
'@typescript-eslint/no-unsafe-assignment': [0],
'@typescript-eslint/no-unsafe-call': [0],
'@typescript-eslint/no-unsafe-declaration-merging': [2],
'@typescript-eslint/no-unsafe-enum-assignment': [0],
'@typescript-eslint/no-unsafe-enum-comparison': [0],
'@typescript-eslint/no-unsafe-function-type': [2],
'@typescript-eslint/no-unsafe-member-access': [0],
@@ -311,7 +304,6 @@ export default defineConfig([
'func-names': [0],
'func-style': [0],
'getter-return': [2],
'gitea/unescaped-html-literal': [2],
'grouped-accessor-pairs': [2],
'guard-for-in': [0],
'id-denylist': [0],
@@ -712,7 +704,6 @@ export default defineConfig([
'unicorn/better-dom-traversing': [2],
'unicorn/catch-error-name': [0],
'unicorn/class-reference-in-static-methods': [2],
'unicorn/comma-spacing': [0], // only applies to json language
'unicorn/comment-content': [0],
'unicorn/consistent-arrow-return-style': [0],
'unicorn/consistent-assert': [0],
@@ -743,10 +734,8 @@ export default defineConfig([
'unicorn/filename-case': [0],
'unicorn/id-match': [2],
'unicorn/import-style': [0],
'unicorn/indent': [0], // only applies to json and css languages
'unicorn/isolated-functions': [2, {functions: []}],
'unicorn/iteration-fallback-style': [2, 'fallback'],
'unicorn/key-name-casing': [0], // only applies to json, yaml and toml languages
'unicorn/logical-assignment-operators': [0],
'unicorn/max-nested-calls': [0],
'unicorn/name-replacements': [0],
@@ -779,20 +768,21 @@ export default defineConfig([
'unicorn/no-chained-comparison': [2],
'unicorn/no-collection-bracket-access': [2],
'unicorn/no-computed-property-existence-check': [0],
'unicorn/no-conflicting-constraints': [2],
'unicorn/no-confusing-array-splice': [2],
'unicorn/no-confusing-array-with': [2],
'unicorn/no-console-spaces': [0],
'unicorn/no-constant-zero-expression': [2],
'unicorn/no-declarations-before-early-exit': [0],
'unicorn/no-deprecated-css-features': [0],
'unicorn/no-document-cookie': [2],
'unicorn/no-double-comparison': [2],
'unicorn/no-duplicate-css-selectors': [0],
'unicorn/no-duplicate-font-family-names': [0],
'unicorn/no-duplicate-if-branches': [2],
'unicorn/no-duplicate-logical-operands': [2],
'unicorn/no-duplicate-loops': [0],
'unicorn/no-duplicate-set-values': [2],
'unicorn/no-empty-file': [2],
'unicorn/no-empty-link-text': [0], // only applies to markdown language
'unicorn/no-error-property-assignment': [2],
'unicorn/no-exports-in-scripts': [2],
'unicorn/no-for-each': [2],
@@ -800,34 +790,21 @@ export default defineConfig([
'unicorn/no-global-object-property-assignment': [0],
'unicorn/no-immediate-mutation': [0],
'unicorn/no-impossible-length-comparison': [2],
'unicorn/no-incomplete-accessor-override': [2],
'unicorn/no-incorrect-query-selector': [2],
'unicorn/no-incorrect-template-string-interpolation': [0],
'unicorn/no-ineffective-csp-directives': [2],
'unicorn/no-instanceof-builtins': [2],
'unicorn/no-invalid-argument-count': [2],
'unicorn/no-invalid-boolean-attribute-value': [2],
'unicorn/no-invalid-character-comparison': [2],
'unicorn/no-invalid-dom-token': [2],
'unicorn/no-invalid-fetch-options': [2],
'unicorn/no-invalid-file-input-accept': [2],
'unicorn/no-invalid-integrity': [2],
'unicorn/no-invalid-intl-options': [2],
'unicorn/no-invalid-property-descriptor': [2],
'unicorn/no-invalid-media-features': [0],
'unicorn/no-invalid-remove-event-listener': [2],
'unicorn/no-invalid-response-options': [2],
'unicorn/no-invalid-style-set-property': [2],
'unicorn/no-invalid-temporal-arithmetic': [2],
'unicorn/no-invalid-url-protocol-comparison': [2],
'unicorn/no-invalid-well-known-symbol-methods': [2],
'unicorn/no-javascript-url': [0], // only applies to markdown language
'unicorn/no-keyword-prefix': [0],
'unicorn/no-late-current-target-access': [2],
'unicorn/no-late-event-control': [2],
'unicorn/no-leading-empty-lines': [0], // handled by @stylistic/no-multiple-empty-lines
'unicorn/no-lonely-if': [2],
'unicorn/no-loop-iterable-mutation': [2],
'unicorn/no-loss-of-precision': [0], // only applies to json, toml and css languages
'unicorn/no-magic-array-flat-depth': [0],
'unicorn/no-manually-wrapped-comments': [0], // too opinionated
'unicorn/no-mismatched-map-key': [2],
@@ -840,6 +817,7 @@ export default defineConfig([
'unicorn/no-negated-condition': [0],
'unicorn/no-negation-in-equality-check': [2],
'unicorn/no-nested-ternary': [0],
'unicorn/no-nesting-with-mixed-specificity': [0],
'unicorn/no-new-array': [0],
'unicorn/no-new-buffer': [2],
'unicorn/no-non-function-verb-prefix': [0],
@@ -848,10 +826,9 @@ export default defineConfig([
'unicorn/no-object-as-default-parameter': [0],
'unicorn/no-object-methods-with-collections': [2],
'unicorn/no-optional-chaining-on-undeclared-variable': [2],
'unicorn/no-prevent-default-in-passive-listener': [2],
'unicorn/no-process-exit': [0],
'unicorn/no-redundant-comparison': [2],
'unicorn/no-repeated-call-wrappers': [0],
'unicorn/no-redundant-nested-style-rules': [0],
'unicorn/no-return-array-push': [2],
'unicorn/no-selector-as-dom-name': [2],
'unicorn/no-shorthand-property-overrides': [0], // only applies to css language
@@ -867,16 +844,16 @@ export default defineConfig([
'unicorn/no-typeof-undefined': [2],
'unicorn/no-uncalled-method': [2],
'unicorn/no-undeclared-class-members': [2],
'unicorn/no-unknown-css-annotations': [0],
'unicorn/no-unknown-pseudo-selectors': [0],
'unicorn/no-unnecessary-array-flat-depth': [2],
'unicorn/no-unnecessary-array-flat-map': [2],
'unicorn/no-unnecessary-array-splice-count': [2],
'unicorn/no-unnecessary-await': [2],
'unicorn/no-unnecessary-boolean-comparison': [2],
'unicorn/no-unnecessary-default-arguments': [0],
'unicorn/no-unnecessary-fetch-options': [0],
'unicorn/no-unnecessary-global-this': [0],
'unicorn/no-unnecessary-nested-ternary': [2],
'unicorn/no-unnecessary-parameters': [0],
'unicorn/no-unnecessary-polyfills': [2],
'unicorn/no-unnecessary-slice-end': [2],
'unicorn/no-unnecessary-splice': [2],
@@ -888,15 +865,14 @@ export default defineConfig([
'unicorn/no-unreadable-object-destructuring': [0],
'unicorn/no-unsafe-buffer-conversion': [2],
'unicorn/no-unsafe-dom-html': [0],
'unicorn/no-unsafe-json-serialization': [2],
'unicorn/no-unsafe-promise-all-settled-values': [2],
'unicorn/no-unsafe-property-key': [0],
'unicorn/no-unsafe-sqlite-interpolation': [2],
'unicorn/no-unsafe-string-replacement': [2],
'unicorn/no-unscoped-css-nesting-selector': [0],
'unicorn/no-unused-builtin-method-return': [2],
'unicorn/no-unused-iterator-helper': [2],
'unicorn/no-unused-properties': [2],
'unicorn/no-url-in-search-params': [2],
'unicorn/no-useless-boolean-cast': [2],
'unicorn/no-useless-coercion': [2],
'unicorn/no-useless-collection-argument': [2],
@@ -933,12 +909,12 @@ export default defineConfig([
'unicorn/prefer-array-find': [0], // handled by @typescript-eslint/prefer-find
'unicorn/prefer-array-flat': [2],
'unicorn/prefer-array-flat-map': [2],
'unicorn/prefer-array-from-async': [0], // Array.fromAsync requires ES2026
'unicorn/prefer-array-from-async': [2],
'unicorn/prefer-array-from-map': [2],
'unicorn/prefer-array-from-range': [2],
'unicorn/prefer-array-index-of': [2],
'unicorn/prefer-array-iterable-methods': [2],
'unicorn/prefer-array-last-methods': [0], // Array#findLast/findLastIndex requires ES2023
'unicorn/prefer-array-last-methods': [2],
'unicorn/prefer-array-slice': [2],
'unicorn/prefer-array-some': [2],
'unicorn/prefer-at': [0],
@@ -955,7 +931,7 @@ export default defineConfig([
'unicorn/prefer-date-now': [2],
'unicorn/prefer-default-parameters': [0],
'unicorn/prefer-direct-iteration': [2],
'unicorn/prefer-dispose': [0], // `using` requires ES2027
'unicorn/prefer-dispose': [2],
'unicorn/prefer-dom-node-append': [2],
'unicorn/prefer-dom-node-html-methods': [0],
'unicorn/prefer-dom-node-remove': [2],
@@ -964,14 +940,14 @@ export default defineConfig([
'unicorn/prefer-early-return': [0],
'unicorn/prefer-else-if': [2],
'unicorn/prefer-error-is-error': [0],
'unicorn/prefer-escaped-irregular-whitespace': [2],
'unicorn/prefer-event-target': [2],
'unicorn/prefer-explicit-viewport-units': [0], // only applies to css language
'unicorn/prefer-export-from': [0],
'unicorn/prefer-flat-math-min-max': [2],
'unicorn/prefer-get-or-insert-computed': [0], // Map#getOrInsertComputed requires ES2026
'unicorn/prefer-get-or-insert-computed': [2],
'unicorn/prefer-global-number-constants': [2],
'unicorn/prefer-global-this': [0],
'unicorn/prefer-group-by': [0], // Object.groupBy/Map.groupBy requires ES2024
'unicorn/prefer-group-by': [2],
'unicorn/prefer-has-check': [2],
'unicorn/prefer-hoisting-branch-code': [2],
'unicorn/prefer-https': [0], // false-positives on namespace and schema URIs
@@ -982,12 +958,11 @@ export default defineConfig([
'unicorn/prefer-iterable-in-constructor': [2],
'unicorn/prefer-iterator-concat': [0], // too opinionated
'unicorn/prefer-iterator-helpers': [0],
'unicorn/prefer-iterator-to-array': [0], // Iterator#toArray requires ES2025
'unicorn/prefer-iterator-to-array': [2],
'unicorn/prefer-iterator-to-array-at-end': [2],
'unicorn/prefer-iterator-zip': [0],
'unicorn/prefer-json-import': [0],
'unicorn/prefer-keyboard-event-key': [2],
'unicorn/prefer-literal-ascii': [2],
'unicorn/prefer-location-assign': [2],
'unicorn/prefer-logical-operator-over-ternary': [0],
'unicorn/prefer-map-from-entries': [0],
@@ -995,6 +970,7 @@ export default defineConfig([
'unicorn/prefer-math-constants': [2],
'unicorn/prefer-math-min-max': [2],
'unicorn/prefer-math-trunc': [2],
'unicorn/prefer-media-feature-range-syntax': [0],
'unicorn/prefer-minimal-ternary': [0],
'unicorn/prefer-modern-dom-apis': [0],
'unicorn/prefer-modern-math-apis': [2],
@@ -1013,9 +989,8 @@ export default defineConfig([
'unicorn/prefer-optional-catch-binding': [2],
'unicorn/prefer-path2d': [2],
'unicorn/prefer-private-class-fields': [0],
'unicorn/prefer-promise-static-methods': [2],
'unicorn/prefer-promise-try': [0], // Promise.try requires ES2025
'unicorn/prefer-promise-with-resolvers': [0], // Promise.withResolvers requires ES2024
'unicorn/prefer-promise-try': [2],
'unicorn/prefer-promise-with-resolvers': [2],
'unicorn/prefer-prototype-methods': [2],
'unicorn/prefer-query-selector': [2],
'unicorn/prefer-queue-microtask': [2],
@@ -1028,7 +1003,6 @@ export default defineConfig([
'unicorn/prefer-set-methods': [0],
'unicorn/prefer-set-size': [2],
'unicorn/prefer-short-arrow-method': [2],
'unicorn/prefer-short-escape-sequences': [2],
'unicorn/prefer-simple-condition-first': [0],
'unicorn/prefer-simple-sort-comparator': [2],
'unicorn/prefer-simplified-conditions': [2],
@@ -1050,7 +1024,7 @@ export default defineConfig([
'unicorn/prefer-structured-clone': [2],
'unicorn/prefer-switch': [0],
'unicorn/prefer-temporal': [0],
'unicorn/prefer-temporal-conversion': [0], // Temporal requires ES2027
'unicorn/prefer-temporal-conversion': [2],
'unicorn/prefer-ternary': [0],
'unicorn/prefer-then-catch': [2],
'unicorn/prefer-toggle-attribute': [2],
@@ -1076,7 +1050,6 @@ export default defineConfig([
'unicorn/require-passive-events': [2],
'unicorn/require-post-message-target-origin': [0],
'unicorn/require-proxy-trap-boolean-return': [2],
'unicorn/require-text-decoder-streaming': [2],
'unicorn/single-line-block-comment-style': [0],
'unicorn/string-content': [0],
'unicorn/switch-case-braces': [0],
@@ -1123,13 +1096,17 @@ export default defineConfig([
},
},
{
files: vueExts.map((ext) => `**/*.${ext}`),
files: ['**/*.vue'],
languageOptions: {
parserOptions: {
parser: '@typescript-eslint/parser',
},
},
extends: [
vue.configs['flat/recommended'],
vueScopedCss.configs.recommended,
],
rules: {
'@typescript-eslint/no-redundant-type-constituents': [0], // types imported from .vue files resolve to any via typescript-eslint's *.vue shim
'vue/attributes-order': [0],
'vue/html-closing-bracket-spacing': [2, {startTag: 'never', endTag: 'never', selfClosingTag: 'never'}],
'vue/max-attributes-per-line': [0],
@@ -1242,9 +1219,6 @@ export default defineConfig([
{
files: ['*', 'tools/**/*', 'tests/**/*'],
languageOptions: {globals: globals.nodeBuiltin},
rules: {
'gitea/unescaped-html-literal': [0],
},
},
{
files: ['web_src/**/*'],
+61 -34
View File
@@ -5,57 +5,64 @@ go 1.27
toolchain go1.27.3
require (
connectrpc.com/connect/v2 v2.0.1
connectrpc.com/connect v1.21.0
gitea.com/go-chi/binding v0.0.0-20260819122636-082915a69981
gitea.com/go-chi/cache v0.2.1
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
gitea.com/go-chi/session v0.0.0-20260708011333-ebced8a7a2d6
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
gitea.dev/actionslib v1.5.0
gitea.dev/actionslib v1.2.1
gitea.dev/sdk v1.3.0
github.com/42wim/httpsig v1.3.0
github.com/42wim/httpsig v1.2.4
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
github.com/Azure/go-ntlmssp v0.1.1
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347
github.com/ProtonMail/go-crypto v1.5.2
github.com/ProtonMail/go-crypto v1.5.0
github.com/PuerkitoBio/goquery v1.13.0
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0
github.com/alecthomas/chroma/v2 v2.27.0
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.6.1
github.com/bohde/codel v0.2.0
github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c
github.com/buildkite/terminal-to-html/v3 v3.17.1
github.com/caddyserver/certmagic v0.25.6
github.com/caddyserver/certmagic v0.25.4
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260812203852-971c0284dc33
github.com/chi-middleware/proxy v1.1.1
github.com/coder/websocket v1.8.15
github.com/dlclark/regexp2/v2 v2.9.1
github.com/dlclark/regexp2/v2 v2.8.0
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
github.com/dustin/go-humanize v1.1.0
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5
github.com/emersion/go-imap v1.2.1
github.com/emirpasic/gods v1.18.1
github.com/felixge/fgprof v0.9.5
github.com/fsnotify/fsnotify v1.10.1
github.com/getkin/kin-openapi v0.151.0
github.com/go-chi/chi/v5 v5.5.0
github.com/getkin/kin-openapi v0.149.0
github.com/go-chi/chi/v5 v5.3.2
github.com/go-chi/cors v1.2.2
github.com/go-co-op/gocron/v2 v2.22.0
github.com/go-enry/go-enry/v2 v2.9.6
github.com/go-ldap/ldap/v3 v3.4.15
github.com/go-git/go-billy/v5 v5.9.1
github.com/go-git/go-git/v5 v5.19.2
github.com/go-ldap/ldap/v3 v3.4.14
github.com/go-redsync/redsync/v4 v4.18.0
github.com/go-sql-driver/mysql v1.10.1
github.com/go-webauthn/webauthn v0.18.2
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/go-github/v93 v93.0.0
github.com/google/go-github/v92 v92.0.0
github.com/google/licenseclassifier/v2 v2.0.0
github.com/google/pprof v0.0.0-20261016150608-1facf8f91b50
github.com/google/pprof v0.0.0-20260906184651-6331bc6350fe
github.com/gorilla/feeds v1.2.0
github.com/gorilla/sessions v1.4.0
github.com/hashicorp/go-version v1.9.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/huandu/xstrings v1.6.3
github.com/huandu/xstrings v1.6.1
github.com/jhillyerd/enmime/v2 v2.5.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.20.3
github.com/klauspost/compress v1.20.0
github.com/lib/pq v1.12.3
github.com/markbates/goth v1.82.0
github.com/mattn/go-isatty v0.0.24
@@ -64,32 +71,33 @@ require (
github.com/mholt/archives v0.1.5
github.com/microcosm-cc/bluemonday v1.0.27
github.com/microsoft/go-mssqldb v1.11.2
github.com/minio/minio-go/v7 v7.4.1
github.com/minio/minio-go/v7 v7.3.0
github.com/msteinert/pam/v2 v2.1.0
github.com/niklasfasching/go-org v1.9.1
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
github.com/pquerna/otp v1.5.0
github.com/prometheus/client_golang v1.25.0
github.com/prometheus/client_golang v1.24.1
github.com/quasoft/websspi v1.1.2
github.com/redis/go-redis/v9 v9.23.0
github.com/redis/go-redis/v9 v9.22.0
github.com/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3
github.com/sassoftware/go-rpmutils v0.4.0
github.com/sergi/go-diff v1.4.0
github.com/stretchr/testify v1.12.1
github.com/syndtr/goleveldb v1.0.0
github.com/ulikunitz/xz v0.5.17
github.com/urfave/cli-docs/v3 v3.1.0
github.com/urfave/cli/v3 v3.14.0
github.com/urfave/cli/v3 v3.13.0
github.com/wneessen/go-mail v0.8.1
github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.8.6
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gitlab.com/gitlab-org/api/client-go/v3 v3.29.0
gitlab.com/gitlab-org/api/client-go/v3 v3.13.0
go.yaml.in/yaml/v4 v4.0.0-rc.6.0.20261013184558-04dd63131525
golang.org/x/crypto v0.58.0
golang.org/x/image v0.47.0
golang.org/x/mod v0.42.0
golang.org/x/image v0.46.0
golang.org/x/mod v0.41.0
golang.org/x/net v0.62.0
golang.org/x/oauth2 v0.37.0
golang.org/x/sync v0.24.0
@@ -98,7 +106,7 @@ require (
google.golang.org/grpc v1.84.0
google.golang.org/protobuf v1.36.12
gopkg.in/ini.v1 v1.67.3
modernc.org/sqlite v1.60.1
modernc.org/sqlite v1.59.0
mvdan.cc/xurls/v2 v2.6.0
xorm.io/builder v0.3.13
xorm.io/xorm v1.4.3
@@ -106,7 +114,10 @@ require (
require (
cloud.google.com/go/compute/metadata v0.9.0 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.2.0 // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/RoaringBitmap/roaring/v2 v2.18.2 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/andybalholm/brotli v1.2.1 // indirect
@@ -132,18 +143,23 @@ require (
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
github.com/blevesearch/zapx/v16 v16.3.4 // indirect
github.com/blevesearch/zapx/v17 v17.2.3 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.3 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.4 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/caddyserver/zerossl v0.1.6 // indirect
github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c // indirect
github.com/caddyserver/zerossl v0.1.5 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/couchbase/go-couchbase v0.1.1 // indirect
github.com/couchbase/gomemcached v0.3.4 // indirect
github.com/couchbase/goutils v0.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
@@ -152,6 +168,7 @@ require (
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-openapi/jsonpointer v0.23.1 // indirect
github.com/go-openapi/swag/jsonname v0.26.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
@@ -159,7 +176,9 @@ require (
github.com/goccy/go-json v0.10.6 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/flatbuffers v25.12.19+incompatible // indirect
github.com/google/go-querystring v1.2.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect
@@ -169,8 +188,10 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/inbucket/html2text v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.6.0 // indirect
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
@@ -178,8 +199,8 @@ require (
github.com/markbates/going v1.0.3 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-runewidth v0.0.24 // indirect
github.com/mholt/acmez/v3 v3.1.7 // indirect
github.com/miekg/dns v1.1.73 // indirect
github.com/mholt/acmez/v3 v3.1.6 // indirect
github.com/miekg/dns v1.1.72 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
@@ -198,37 +219,43 @@ require (
github.com/olekukonko/ll v0.1.8 // indirect
github.com/olekukonko/tablewriter v1.1.4 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.34.1 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.27 // indirect
github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.3 // indirect
github.com/prometheus/common v0.72.0 // indirect
github.com/prometheus/procfs v0.22.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.70.1 // indirect
github.com/prometheus/procfs v0.21.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.10.2 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/stangelandcl/ppmd v0.1.1 // indirect
github.com/tinylib/msgp v1.6.4 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
github.com/zeebo/xxh3 v1.1.0 // indirect
go.etcd.io/bbolt v1.4.3 // indirect
go.uber.org/atomic v1.12.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.28.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
golang.org/x/time v0.16.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.51.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
modernc.org/libc v1.77.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
modernc.org/libc v1.75.7 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.12.1 // indirect
)
+197 -72
View File
@@ -2,22 +2,32 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=
code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
connectrpc.com/connect/v2 v2.0.1 h1:WTAycxe87UqxSM5oxS30d1dalZ+C5xz8Ec8O6zVYkHQ=
connectrpc.com/connect/v2 v2.0.1/go.mod h1:N32OLrstcPUzkykFynnefFro3+xzSdOrZlK2jHmS/xo=
connectrpc.com/connect v1.21.0 h1:LhqSJt7jHf5NJBo9Jq/t/9FjcYAideif0mg+qe2jCUs=
connectrpc.com/connect v1.21.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
gitea.com/go-chi/binding v0.0.0-20260819122636-082915a69981 h1:LmdlwGbzgZFZA3bK3R1q8QrNabaSz3KpnOJBLmzhN6E=
gitea.com/go-chi/binding v0.0.0-20260819122636-082915a69981/go.mod h1:q1SSPpkC9A0gfNnoqqZ3My6kEHIpKN6QsTI+Zx73B/o=
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
gitea.com/go-chi/cache v0.2.1/go.mod h1:Qic0HZ8hOHW62ETGbonpwz8WYypj9NieU9659wFUJ8Q=
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk=
gitea.com/go-chi/session v0.0.0-20260708011333-ebced8a7a2d6 h1:YWzVGeC/8SZThrJS48ZmQYLkzssdeABxHPhbdnxPDIU=
gitea.com/go-chi/session v0.0.0-20260708011333-ebced8a7a2d6/go.mod h1:KDvcfMUoXfATPHs2mbMoXFTXT45/FAFAS39waz9tPk0=
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw=
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY=
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitea.dev/actionslib v1.5.0 h1:dC3v2HTWbbvyNBSdg2W3kd4WNC/p+FnZaKpl1MdzM00=
gitea.dev/actionslib v1.5.0/go.mod h1:C9wmOFgDBCXyRvg4A3KTX8Vszs8v9+elH4QLGVDolqU=
gitea.dev/actionslib v1.2.1 h1:GL//K/0zIZV6h1OOksv1awvFVg0rg7fug2xWcG7mkG0=
gitea.dev/actionslib v1.2.1/go.mod h1:1+gqOKGSEPn2IFHgY8S3GC5Ld+Hn8jF3OxiFHQ/fpx4=
gitea.dev/sdk v1.3.0 h1:4U95+Ou6WHCQiRPDEyAzvl1Q7cxg5RUTWkqmOXr5w4I=
gitea.dev/sdk v1.3.0/go.mod h1:85EBqSAqUn1KlFCk40y3sTjWjBsB/KouL3slGXMA/2o=
github.com/42wim/httpsig v1.3.0 h1:HK2bieeZ99X9IvOoc1ST6yrhLek0f4MqlEGES2JCP3g=
github.com/42wim/httpsig v1.3.0/go.mod h1:lgC3Yrvp4lxi1odEF1ge3kQBu/VM+SRcZQu5YwiR3dU=
github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=
github.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps=
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432 h1:3Fcz1QzlS7Jv4FT2KI3cHNSZL+KPN3dXxurn9f3YL/Y=
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432/go.mod h1:BLWe6Nol65Xxncvaw07yYMxiyk02We1lBrbRYsMYsjE=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo=
@@ -34,10 +44,15 @@ github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDP
github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/5vCrMONS+g4u4LRHNgOXVSh3O43J2CnI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI=
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 h1:3JhDl+JysaO8nhNU1XMaw35VSGjV4IEQAefaG4Lyok4=
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347/go.mod h1:2ErI0aycD43Ufr6CFK5lT/NrHGmoZuVbn1nlPThw69o=
github.com/ProtonMail/go-crypto v1.5.2 h1:cucYnvqcY7UOXVD//mSyjeaPY0SSN3v5cDkYPxumINk=
github.com/ProtonMail/go-crypto v1.5.2/go.mod h1:/RaSu30DaKO4RY+XdV/ACcCcZkGr7AhUIduq5sjzzCo=
github.com/ProtonMail/go-crypto v1.5.0 h1:sKmuvjOgsnrtpMvZ+84MCnTJCpjxZ1qCFn076lz1yT0=
github.com/ProtonMail/go-crypto v1.5.0/go.mod h1:/RaSu30DaKO4RY+XdV/ACcCcZkGr7AhUIduq5sjzzCo=
github.com/PuerkitoBio/goquery v1.13.0 h1:mqHbjD7Jmnul4DTR24LKTjo1uUmHUh072kteGV+xpFM=
github.com/PuerkitoBio/goquery v1.13.0/go.mod h1:Hip5mdBL8K2wEGKJdr27sRaNwIdDajmCwB/ExUPwW+g=
github.com/RoaringBitmap/roaring/v2 v2.18.2 h1:oPq3Cgx//iDuJQVp6xSInAKW34J9CEwE5GmLI2z+Eic=
@@ -60,6 +75,10 @@ github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eT
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/andybalholm/cascadia v1.3.4 h1:vM2lgh0Vru9Vwyfm4cQqWP2HHMW0u0+2PAW7Q38Qufg=
github.com/andybalholm/cascadia v1.3.4/go.mod h1:BLRmbRjpEtNKieZOCCvYj4RqN+KRA41GBe/5O+G93kM=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -106,8 +125,8 @@ github.com/blevesearch/zapx/v16 v16.3.4 h1:hDAqA8qusZTNbPEL7//w5P65UZ2de6yhSeUaT
github.com/blevesearch/zapx/v16 v16.3.4/go.mod h1:zqkPPqs9GS9FzVWzCO3Wf1X044yWAV17+4zb+FTiEHg=
github.com/blevesearch/zapx/v17 v17.2.3 h1:UYYJPAt5b2tVxldx5h0jmv23RMsg8/UZKFVya7v92po=
github.com/blevesearch/zapx/v17 v17.2.3/go.mod h1:r7mb4QWbDQSkbAnOjCb9iCfkcrzajB4yBdJpuBIo/fE=
github.com/bmatcuk/doublestar/v4 v4.10.3 h1:1UIv1+fqH3++ViVkpnaPTB+Pi1UHycnPFAq14zgK+gI=
github.com/bmatcuk/doublestar/v4 v4.10.3/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
@@ -128,16 +147,18 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buildkite/terminal-to-html/v3 v3.17.1 h1:gs9WhKUwxK27YHf3hijj4tG4wuDLfCANYbRPd5ltMdI=
github.com/buildkite/terminal-to-html/v3 v3.17.1/go.mod h1:0pv+Z75FC+NvIju2o+Cw+YHIDkueeudIoeyfVm8i8GQ=
github.com/caddyserver/certmagic v0.25.6 h1:vHMtFSLKTa6kuZz6w0SnRMpQ388tSkww8ye355BgAM4=
github.com/caddyserver/certmagic v0.25.6/go.mod h1:xq6cRNimqW+Tv91XNc5lNHs2XYTsLhQhYiH01H4AySs=
github.com/caddyserver/zerossl v0.1.6 h1:1yrhTx5DWi43wOJPQ+Y4mVl++EPmSqqT/4REW//lsas=
github.com/caddyserver/zerossl v0.1.6/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/caddyserver/certmagic v0.25.4 h1:8eIXh0HC3MsGnNo8One+BCxMGTbe5zb/oz+2KsxBFQg=
github.com/caddyserver/certmagic v0.25.4/go.mod h1:YVs43D5+H/Dckt4bTga1KSO/xYfFBfVZainGDywYPAA=
github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE=
github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260812203852-971c0284dc33 h1:YG5mXlv9SxS4rdtSxK3RuH146x1HIQzsbVJqSp/Rvfc=
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20260812203852-971c0284dc33/go.mod h1:R+SetERD4+IL7QH0WHp9MLifvITvh9gL3g8vX1j2Fcs=
github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0=
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
@@ -152,8 +173,17 @@ github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/couchbase/go-couchbase v0.1.1 h1:ClFXELcKj/ojyoTYbsY34QUrrYCBi/1G749sXSCkdhk=
github.com/couchbase/go-couchbase v0.1.1/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
github.com/couchbase/gomemcached v0.3.4 h1:VGdrZUJbt5lLyI/MXnyVCZKHKYXg/vaud08lJIAeZps=
github.com/couchbase/gomemcached v0.3.4/go.mod h1:pISAjweI42vljCumsJIo7CVhqIMIIP9g3Wfhl1JJw68=
github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE=
github.com/couchbase/goutils v0.3.0 h1:rsv72B6BDjW9jmwlfiDUrdu3EpNvPuo5WLULHzQ0DLE=
github.com/couchbase/goutils v0.3.0/go.mod h1:7Gm+D3vXfV4HS+hQWvKfy6e6ILCptGXNqBKvQXhplhk=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -164,10 +194,10 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2/v2 v2.9.1 h1:q6bZW/NN9VodqMjYXpuic2lZxdHPfFTzGS+MJxIJmKQ=
github.com/dlclark/regexp2/v2 v2.9.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2/v2 v2.8.0 h1:CekDhPLGfm+GAJmPVFIG+5dqMIQPkyHJll7BbdneDfw=
github.com/dlclark/regexp2/v2 v2.8.0/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
@@ -175,6 +205,8 @@ github.com/dustin/go-humanize v1.1.0 h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2
github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5 h1:MTcuJQkIFRLfNn9FfAvdO0p3FhNyaCf+IGTwy0TxX6E=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5/go.mod h1:SizrS3EM1vFF0v/JZlJ5LfK6tXhwln2823YZwNh/vBE=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
@@ -182,6 +214,8 @@ github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTe
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
@@ -192,14 +226,17 @@ github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx5
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo=
github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/getkin/kin-openapi v0.151.0 h1:p8NIstL53h620DnlO9R4pR4QRkEzJ3jbw9flb4kc1hg=
github.com/getkin/kin-openapi v0.151.0/go.mod h1:1+BHDzstro+P5CKtPy1X4PfofnFgmRe6uvMy9+r9fKY=
github.com/getkin/kin-openapi v0.149.0 h1:ZbhmVJ4yq5RZDUsyP8lcBcGMsjsaTqXEFt6isdtMDfA=
github.com/getkin/kin-openapi v0.149.0/go.mod h1:1+BHDzstro+P5CKtPy1X4PfofnFgmRe6uvMy9+r9fKY=
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=
github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi/v5 v5.5.0 h1:xdKQ1dJmVJsmgahrXSKhUx8NyVPzpwdlyrrQFpMPJgg=
github.com/go-chi/chi/v5 v5.5.0/go.mod h1:d1IG7GtKPSjPyBP365PH8sAGVHk3qfB/IhjFNRH1wWM=
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.3.2 h1:5YQkICvTCSZ25hoRsyJazN0scjzKGiu4VAUc7H1o1nY=
github.com/go-chi/chi/v5 v5.3.2/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-co-op/gocron/v2 v2.22.0 h1:uEuH2F7k7VoESb1BYSaffuuV+T0kkpzsC0aXk7/z79I=
@@ -208,10 +245,18 @@ github.com/go-enry/go-enry/v2 v2.9.6 h1:np63eOtMV56zfYDHnFVgpEVOk8fr2kmylcMnAZUD
github.com/go-enry/go-enry/v2 v2.9.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.9.1 h1:8U73XiOTfINdItHVa6z4Gv7ToObcZ6grkqQbLryLCdA=
github.com/go-git/go-billy/v5 v5.9.1/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY=
github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-ldap/ldap/v3 v3.4.15 h1:eRB4f4Ecn1POL1mO86+uUDlecmyWsdiJwh60bAUS4UY=
github.com/go-ldap/ldap/v3 v3.4.15/go.mod h1:jeD0c98EdZQCKKKKe4kTWs5aCf6y+j8KuYsYLznZSQk=
github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=
github.com/go-ldap/ldap/v3 v3.4.14/go.mod h1:S4eJUMUNjDkE0ZJtIZdybwyb03sGGLW6gxXT1Hs8VKA=
github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4=
github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY=
github.com/go-openapi/swag/jsonname v0.26.1 h1:VReupaV6WxlAsCn0e4DUfgV6bPmINnPpyJDLqSfNPcE=
@@ -252,6 +297,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -267,6 +314,9 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=
github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -275,8 +325,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v93 v93.0.0 h1:qElXPLv+XbSz1qrLdWyb0t3gSYGsWBTzr9T4/PZTCfk=
github.com/google/go-github/v93 v93.0.0/go.mod h1:l8I3rt4/mdjANKpg4Y84LEy9HJQCetUkZ8WTl2bPU8M=
github.com/google/go-github/v92 v92.0.0 h1:4vW4RVffwIvoEfIA4RX09mRSB47qQX557/2+HgeWRRg=
github.com/google/go-github/v92 v92.0.0/go.mod h1:w3CH62ZcmRfvW1cdXpyTztSOVMtdjxtKpgo0GouLmjY=
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
@@ -289,10 +339,12 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/licenseclassifier/v2 v2.0.0 h1:1Y57HHILNf4m0ABuMVb6xk4vAJYEUO0gDxNpog0pyeA=
github.com/google/licenseclassifier/v2 v2.0.0/go.mod h1:cOjbdH0kyC9R22sdQbYsFkto4NGCAc+ZSwbeThazEtM=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20261016150608-1facf8f91b50 h1:PlNWJRxu5APSV0pWqfIsZ0qt9OOGHSWfZQ4zvx+WgWs=
github.com/google/pprof v0.0.0-20261016150608-1facf8f91b50/go.mod h1:yUo1AMMpsPIDFGuwDK8nLcf8+vvAd5l2O6JDZ6hpwrM=
github.com/google/pprof v0.0.0-20260906184651-6331bc6350fe h1:QAinXoAFJdGQYztXn3VpFey7KCwpedbZ/EkzbplQ0cY=
github.com/google/pprof v0.0.0-20260906184651-6331bc6350fe/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
@@ -324,11 +376,13 @@ github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.6.3 h1:7997PcI0mYC9uPNBJ7udg/KrdJh1GhJid8f2tzKFHYQ=
github.com/huandu/xstrings v1.6.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.6.1 h1:yYdKSd6Sjv3fNZt9BDjl1FDsPNfQEaYi19L5LzK2JKs=
github.com/huandu/xstrings v1.6.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/inbucket/html2text v1.0.0 h1:N5kza++4uBBDJ2Z3KUnTRyPNoBcW+YfOgNiNmNB+sgs=
github.com/inbucket/html2text v1.0.0/go.mod h1:5TrhXQKGU+LXurODaSm55Y9eXoPBRnYiOz4x2XfUoJU=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
@@ -348,11 +402,16 @@ github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7X
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.20.3 h1:zWNoFqq/lSuYQRim3dUXlXeA0IqwuZGicdG5ZTfY5j4=
github.com/klauspost/compress v1.20.3/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
@@ -373,8 +432,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
github.com/letsencrypt/pebble/v2 v2.10.1 h1:oKHx3lgN4e5Nno2LKTMrVx+b+NkDptkO9aDireiBDGE=
github.com/letsencrypt/pebble/v2 v2.10.1/go.mod h1:KtYhQ4YTjT5MtoCZ6RTCXlbrrz6cKyXROCuTpIUDJFY=
github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ=
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
@@ -394,24 +453,24 @@ github.com/mattn/go-sqlite3 v1.14.52 h1:wVbm2Qnf4OXkqhBTSPuCRZDRnxfbVrrmiCEroVdo
github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
github.com/meilisearch/meilisearch-go v0.36.3 h1:Yx1aTY5jDgtbStPVkhJTDoLnZTy5sejQSPyjfNMy6e4=
github.com/meilisearch/meilisearch-go v0.36.3/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM=
github.com/mholt/acmez/v3 v3.1.7 h1:XTqpuUcRdRoIBuDoI+M4mCnRnPTPN4rqH3Mb723Rg4U=
github.com/mholt/acmez/v3 v3.1.7/go.mod h1:Lwv6P/czh/AOq+c99tAzP68/VP92s+8y+hDs4FCgxvo=
github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk=
github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY=
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/microsoft/go-mssqldb v1.11.2 h1:FCgeBIK8um2+X4tbun6Q71N1KsfyCDPKY41e1yGVjSE=
github.com/microsoft/go-mssqldb v1.11.2/go.mod h1:CYgwG5AMXFojbjTg+GNP5G/y6uz1BhTyZaPqQWzkGnQ=
github.com/miekg/dns v1.1.73 h1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE=
github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
github.com/minio/crc64nvme v1.1.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.4.1 h1:PtU/zIOfQRWZ+QydXAZysc+xZYHdG+BBR4Z6TUvy73w=
github.com/minio/minio-go/v7 v7.4.1/go.mod h1:nQnQi7zTfXR4lREtYXZQWqo5aw0Rqv+ZqCGrTORygRk=
github.com/minio/minio-go/v7 v7.3.0 h1:HM4pFCSQq/TK+j0/zmorSh5ddh81iDgRgU0BG0Vz/YU=
github.com/minio/minio-go/v7 v7.3.0/go.mod h1:KUPWdecEO1LWyUz+sTGXAuf2jZHrPh5fCsRH86QbPfk=
github.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM=
github.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -467,6 +526,8 @@ github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -476,18 +537,18 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v1.25.0 h1:cDtW1TcFsMi1RJc8khwCKB4VhzwV04aKqbYtNOXuiK0=
github.com/prometheus/client_golang v1.25.0/go.mod h1:j2X/4/kaQHLPnmyEU8zjmkndo1T317VpCIQEml8LTKk=
github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo=
github.com/prometheus/client_model v0.6.3/go.mod h1:gpN5P9S7Rr6Yr92PiQ+Ixvhf6JZEkF1dnxsYL2aPBEM=
github.com/prometheus/common v0.72.0 h1:tAYsE+sPJxIncDAobm4H5aQjmox9ZxEIIqPbiffa8G4=
github.com/prometheus/common v0.72.0/go.mod h1:77NWqAQ2tXT7BIK40qjJdw5Acrsrg1TlHAnsQi3i6mk=
github.com/prometheus/procfs v0.22.0 h1:6q9+/JL9IKAPbCmBrv9n5O5Ty3NKnciV5X7YGw0oics=
github.com/prometheus/procfs v0.22.0/go.mod h1:CvmFr/GVhIjIvWJZW3tgkODBQMRIf0EyWMQLHCHab58=
github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=
github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY=
github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc=
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
github.com/redis/go-redis/v9 v9.23.0 h1:/TTVdJa8BRNsybwwGGnmiYktHNlUXtbOQxlWpHBPL3s=
github.com/redis/go-redis/v9 v9.23.0/go.mod h1:EWP2UUk+XDaI1s5nQB5mablZN6vccXlmBQTyfnF87D0=
github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0=
github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4=
github.com/redis/rueidis v1.0.77 h1:ZR41bgJcm7oRFb3aSDPrRhC0eonDSrPzjvvZvHIlNjM=
github.com/redis/rueidis v1.0.77/go.mod h1:L8mnCQJJaSNL6I4pIR6Rz732HTGS9vmuXm0yT9dRvjo=
github.com/redis/rueidis/rueidiscompat v1.0.77 h1:S1xiLQsFv8XQ46e0LelC8ihGqbDzVRocXXZLzVAeh64=
@@ -496,20 +557,32 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q=
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
@@ -533,6 +606,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
@@ -545,14 +619,20 @@ github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77ro
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.17 h1:flR0y/x1hgM8EGV1AW3Xll6T413G0glV8UfBwR617V4=
github.com/ulikunitz/xz v0.5.17/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw=
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw=
github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to=
github.com/urfave/cli/v3 v3.14.0 h1:a8414NQlHJs0c/iBsulKLzlES0n/lEAskbL2LKpU4/s=
github.com/urfave/cli/v3 v3.14.0/go.mod h1:vXn6HxPNccJSzQr2QvwVncOKrgYGIHU0HY5h8B2nQj4=
github.com/urfave/cli/v3 v3.13.0 h1:Dr6jqMfIyyFsRVn7Nz5mqLsMY+ZMpfh3a0aMs+umPVY=
github.com/urfave/cli/v3 v3.13.0/go.mod h1:vXn6HxPNccJSzQr2QvwVncOKrgYGIHU0HY5h8B2nQj4=
github.com/wneessen/go-mail v0.8.1 h1:tVcncj02/QySVFw3zr/kXOzZcuFQqBNT6K+Rbgm/pcM=
github.com/wneessen/go-mail v0.8.1/go.mod h1:dWZ61zadzCIyvB4y1/YzC5O7MrbbzBfPkARmbosdf8w=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
@@ -572,12 +652,12 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
gitlab.com/gitlab-org/api/client-go/v3 v3.29.0 h1:wPdHPkd5rIRkxcFzlpm0dHxqBEVrkaDpDhpdikuTRNA=
gitlab.com/gitlab-org/api/client-go/v3 v3.29.0/go.mod h1:jBFhGkTHLKkgeHEdS/A8yA0tQ6UHq96QVsS5muu6flM=
gitlab.com/gitlab-org/api/client-go/v3 v3.13.0 h1:CTUXvcL6OrGjYNafbws5C1lJwlXrxGJHWQGMF+hCdz0=
gitlab.com/gitlab-org/api/client-go/v3 v3.13.0/go.mod h1:k7uYxRoIeuSqyWZSJfAPZlf0L32OH0lUBImAl4gWUFc=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE=
go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
@@ -601,24 +681,39 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
golang.org/x/image v0.47.0 h1:za/8gVz++PPZqmZJBNSISICN5Rrj5zixP/8K++fgmqo=
golang.org/x/image v0.47.0/go.mod h1:IkN5o4A7vq55vXN8FWjjnhg9Rs2GOtffqsSi9mWAKYE=
golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ=
golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY=
golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
@@ -628,6 +723,11 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -636,22 +736,37 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM=
golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -659,14 +774,22 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE=
golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI=
golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -694,6 +817,8 @@ gopkg.in/ini.v1 v1.67.3 h1:iM9Lhz5MRSGhHVGGwCuzG9KO8PoirCXj/m/qTmOJJQw=
gopkg.in/ini.v1 v1.67.3/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -703,10 +828,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.29.7 h1:q+NXGJ0bK3b4TXFYQQVr9pYETGnmwFWkrUzJnMya/Tg=
modernc.org/cc/v4 v4.29.7/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.36.1 h1:ZNIUZAryN0UgnJwtyxrdEzcFc3yD4Cu4AzjfPXsLsIE=
modernc.org/ccgo/v4 v4.36.1/go.mod h1:rrtGc2QkS239nYb/mQNuBMyjq3/y3ZXWbBjPoV3wqzA=
modernc.org/cc/v4 v4.29.2 h1:h6+9ciCnPKutf4I03CvheAvDLX7+IHlqR6Iy6J+cgd8=
modernc.org/cc/v4 v4.29.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.35.0 h1:F+TUsmw09QxLzmi3aeYYGxjAXarmZaKgj3mKQHNaA8w=
modernc.org/ccgo/v4 v4.35.0/go.mod h1:qrVGs9S3Sr2Ztcg9ve+kTAYMp5a3YvWjo+SoN06kJ5I=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
@@ -715,8 +840,8 @@ modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc=
modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.77.1 h1:Ct8j47QtiZ1Enj2DtFXQtUqrPCAjdCmPjtCuvrYQ0Hs=
modernc.org/libc v1.77.1/go.mod h1:87/pZ4L6nD1zqW4nItuS12YO7hN1igAah34xjnQo/W0=
modernc.org/libc v1.75.7 h1:o3DTP9/0p9pKmY2WCKQaySW6wIiZhNM7wc2lUoyhfew=
modernc.org/libc v1.75.7/go.mod h1:bO5o2ztHxBb2rjz0PgdHN0sSMw57CgxGFLZ3Qd/QpVQ=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
@@ -725,8 +850,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.60.1 h1:/blz53O951KWFOso4QQvEs/Fq6cDBKLtMVrYNSeJVKw=
modernc.org/sqlite v1.60.1/go.mod h1:1dIoEagfDE72QytD5scH1lxARtaUgKgHC/NuApA27r0=
modernc.org/sqlite v1.59.0 h1:X1es1GpqBlS/5T+vbM4HLUdaa8OtQx468DF2vrx+38A=
modernc.org/sqlite v1.59.0/go.mod h1:+paeT2A3iPRHkQDwG7oA6Tk0zQd5woMEI8q7orfry8k=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
-17
View File
@@ -518,20 +518,3 @@ func ModifyColumn(ctx context.Context, x EngineMigration, tableName string, col
func Iterate[Bean any](ctx context.Context, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error {
return db.Iterate(ctx, cond, f)
}
func EngineTransaction(x EngineMigration, f func(Session) error) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
err := f(sess)
if err != nil {
return err
}
return sess.Commit()
}
func IterateTableByColumn[Bean any](ctx context.Context, x EngineMigration, sess Session, colName string, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error {
return db.IterateTableByColumn(ctx, x, sess, colName, cond, f)
}
+29 -10
View File
@@ -45,15 +45,38 @@ func Test_DropTableColumns(t *testing.T) {
x.SetMapper(names.GonicMapper{})
for i := range columns {
for j := i; j < len(columns); j++ {
if err := x.Sync(new(DropTest)); err != nil {
t.Errorf("unable to create DropTest table: %v", err)
return
}
sess := x.NewSession()
if err := sess.Begin(); err != nil {
sess.Close()
t.Errorf("unable to begin transaction: %v", err)
return
}
if err := base.DropTableColumns(sess, "drop_test", columns[i:]...); err != nil {
sess.Close()
t.Errorf("Unable to drop columns[%d:]: %s from drop_test: %v", i, columns[i:], err)
return
}
if err := sess.Commit(); err != nil {
sess.Close()
t.Errorf("unable to commit transaction: %v", err)
return
}
sess.Close()
if err := x.DropTables(new(DropTest)); err != nil {
t.Errorf("unable to drop table: %v", err)
return
}
for j := range columns[i+1:] {
if err := x.Sync(new(DropTest)); err != nil {
t.Errorf("unable to create DropTest table: %v", err)
return
}
dropcols := columns[i:]
if j > i {
dropcols = append([]string{columns[i]}, columns[j:]...)
}
dropcols := append([]string{columns[i]}, columns[j+i+1:]...)
sess := x.NewSession()
if err := sess.Begin(); err != nil {
sess.Close()
@@ -62,11 +85,7 @@ func Test_DropTableColumns(t *testing.T) {
}
if err := base.DropTableColumns(sess, "drop_test", dropcols...); err != nil {
sess.Close()
if j == i {
t.Errorf("Unable to drop columns[%d:]: %s from drop_test: %v", i, columns[i:], err)
} else {
t.Errorf("Unable to drop columns: %s from drop_test: %v", dropcols, err)
}
t.Errorf("Unable to drop columns: %s from drop_test: %v", dropcols, err)
return
}
if err := sess.Commit(); err != nil {
+1 -8
View File
@@ -431,16 +431,9 @@ func prepareMigrationTasks() []*migration {
newMigration(354, "Add Actions job queue indexes", v28.AddActionQueueIndexes),
newMigration(355, "Add AutoMerge merged_commit_id column", v28.AddAutoMergeMergedCommitID),
// Gitea 28.0.0 ends at migration ID number 355 (database version 356)
// HERE: 2 migrations from 29 are added since they don't change database structure
newMigration(356, "Add index on action_run commit_sha", v29.AddActionRunCommitSHAIndex),
newMigration(357, "Normalize legacy team authorize values", v29.NormalizeLegacyTeamAuthorize),
newMigration(358, "Track previous remember-me token hashes", v29.AddPreviousAuthTokenHash),
newMigration(359, "Add stage and number columns to action_task_step", v29.AddStageAndNumberToActionTaskStep),
newMigration(360, "Move branch protection settings to JSON", v29.MoveBranchProtectionConfigToJSON),
newMigration(361, "Drop legacy commit_id column from comment", v29.DropCommentCommitID),
newMigration(362, "Add reusable_workflow_ref column to action_run_job", v29.AddReusableWorkflowRefToActionRunJob),
newMigration(363, "Add release_id to reaction table", v29.AddReleaseIDToReaction),
newMigration(364, "Add new table project_workflow", v29.AddProjectWorkflow),
}
return preparedMigrations
}
+2 -6
View File
@@ -105,7 +105,7 @@ func LoadTableSchemasMap(t *testing.T, x base.EngineMigration) map[string]*schem
func mainTest(m *testing.M) int {
testlogger.Init()
workPath, err := setting.PrepareIntegrationTestConfig()
err := setting.PrepareIntegrationTestConfig()
if err != nil {
return testlogger.MainErrorf("Unable to prepare integration test config: %v", err)
}
@@ -117,11 +117,7 @@ func mainTest(m *testing.M) int {
setting.Database.SlowQueryThreshold = 0
setting.LoadDBSetting()
setting.InitLoggersForTest()
exitCode := m.Run()
if exitCode == 0 {
_ = os.RemoveAll(workPath)
}
return exitCode
return m.Run()
}
func MainTest(m *testing.M) {
+1 -6
View File
@@ -78,17 +78,12 @@ func Test_ConvertScopedAccessTokens(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, int64(0), count)
session := x.NewSession()
defer session.Close()
assert.NoError(t, session.Begin())
for _, tc := range tests {
_, err = session.Insert(&AccessToken{
_, err = x.Insert(&AccessToken{
Scope: string(tc.Old),
})
assert.NoError(t, err)
}
assert.NoError(t, session.Commit())
// migrate the scopes
err = ConvertScopedAccessTokens(t.Context(), x)
+14 -10
View File
@@ -43,11 +43,13 @@ func Test_AddUniqueIndexForUserBadge(t *testing.T) {
}
// check that we have duplicates
for _, userID := range []int{1, 3} {
count, err := x.Where("user_id = ? AND badge_id = ?", userID, userID).Count(&UserBadgeBefore{})
assert.NoError(t, err)
assert.Equal(t, int64(2), count)
}
count, err := x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{})
assert.NoError(t, err)
assert.Equal(t, int64(2), count)
count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{})
assert.NoError(t, err)
assert.Equal(t, int64(2), count)
totalCount, err := x.Count(&UserBadgeBefore{})
assert.NoError(t, err)
@@ -60,11 +62,13 @@ func Test_AddUniqueIndexForUserBadge(t *testing.T) {
}
// verify the duplicates were removed
for _, userID := range []int{1, 3} {
count, err := x.Where("user_id = ? AND badge_id = ?", userID, userID).Count(&UserBadgeBefore{})
assert.NoError(t, err)
assert.Equal(t, int64(1), count)
}
count, err = x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{})
assert.NoError(t, err)
assert.Equal(t, int64(1), count)
count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{})
assert.NoError(t, err)
assert.Equal(t, int64(1), count)
// check total count
totalCount, err = x.Count(&UserBadgeBefore{})
+1 -1
View File
@@ -11,7 +11,7 @@ import (
// NormalizeLegacyTeamAuthorize sets leftover read/write authorize values to none.
// https://github.com/go-gitea/gitea/pull/34128 made non-admin teams use team_unit (authorize=none).
// Any positive authorize now means blanket access on every unit; migrating legacy read/write
// authorize>=write now means blanket access on every unit; migrating legacy read/write
// to none preserves their existing team_unit-scoped access.
func NormalizeLegacyTeamAuthorize(_ context.Context, x base.EngineMigration) error {
// AccessModeNone=0, AccessModeRead=1, AccessModeWrite=2, AccessModeAdmin=3
-25
View File
@@ -1,25 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v29
import (
"context"
"gitea.dev/modelmigration/base"
"gitea.dev/modules/timeutil"
"xorm.io/xorm"
)
func AddPreviousAuthTokenHash(_ context.Context, x base.EngineMigration) error {
type AuthToken struct {
PreviousTokenHash string
RotatedUnix timeutil.TimeStamp
}
_, err := x.SyncWithOptions(xorm.SyncOptions{
IgnoreDropIndices: true,
IgnoreConstrains: true,
}, new(AuthToken))
return err
}
-25
View File
@@ -1,25 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v29
import (
"context"
"gitea.dev/modelmigration/base"
"xorm.io/xorm"
)
func AddStageAndNumberToActionTaskStep(_ context.Context, x base.EngineMigration) error {
type ActionTaskStep struct {
Stage int64 `xorm:"NOT NULL DEFAULT 0"`
Number int64 `xorm:"NOT NULL DEFAULT 0"`
}
_, err := x.SyncWithOptions(xorm.SyncOptions{
IgnoreConstrains: true,
IgnoreDropIndices: true,
}, new(ActionTaskStep))
return err
}
-112
View File
@@ -1,112 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v29
import (
"context"
"gitea.dev/modelmigration/base"
"gitea.dev/modules/json"
"gitea.dev/modules/timeutil"
"gitea.dev/modules/util"
"xorm.io/xorm"
)
type protectedBranchConfigV358 struct {
CanPush bool `xorm:"NOT NULL DEFAULT false" json:"can_push,omitzero"`
EnableWhitelist bool `json:"enable_whitelist,omitzero"`
WhitelistUserIDs []int64 `xorm:"JSON TEXT" json:"whitelist_user_ids,omitzero"`
WhitelistTeamIDs []int64 `xorm:"JSON TEXT" json:"whitelist_team_ids,omitzero"`
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false" json:"enable_merge_whitelist,omitzero"`
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false" json:"-"`
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT" json:"merge_whitelist_user_ids,omitzero"`
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT" json:"merge_whitelist_team_ids,omitzero"`
EnableBypassAllowlist bool `xorm:"NOT NULL DEFAULT false" json:"enable_bypass_allowlist,omitzero"`
BypassAllowlistUserIDs []int64 `xorm:"JSON TEXT" json:"bypass_allowlist_user_ids,omitzero"`
BypassAllowlistTeamIDs []int64 `xorm:"JSON TEXT" json:"bypass_allowlist_team_ids,omitzero"`
CanForcePush bool `xorm:"NOT NULL DEFAULT false" json:"can_force_push,omitzero"`
EnableForcePushAllowlist bool `xorm:"NOT NULL DEFAULT false" json:"enable_force_push_allowlist,omitzero"`
ForcePushAllowlistUserIDs []int64 `xorm:"JSON TEXT" json:"force_push_allowlist_user_ids,omitzero"`
ForcePushAllowlistTeamIDs []int64 `xorm:"JSON TEXT" json:"force_push_allowlist_team_ids,omitzero"`
ForcePushAllowlistDeployKeys bool `xorm:"NOT NULL DEFAULT false" json:"-"`
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false" json:"enable_status_check,omitzero"`
StatusCheckContexts []string `xorm:"JSON TEXT" json:"status_check_contexts,omitzero"`
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false" json:"enable_approvals_whitelist,omitzero"`
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT" json:"approvals_whitelist_user_ids,omitzero"`
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT" json:"approvals_whitelist_team_ids,omitzero"`
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0" json:"required_approvals,omitzero"`
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false" json:"block_on_rejected_reviews,omitzero"`
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false" json:"block_on_official_review_requests,omitzero"`
BlockOnCodeownerReviews bool `xorm:"NOT NULL DEFAULT false" json:"block_on_codeowner_reviews,omitzero"`
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false" json:"block_on_outdated_branch,omitzero"`
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false" json:"dismiss_stale_approvals,omitzero"`
IgnoreStaleApprovals bool `xorm:"NOT NULL DEFAULT false" json:"ignore_stale_approvals,omitzero"`
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false" json:"require_signed_commits,omitzero"`
ProtectedFilePatterns string `xorm:"TEXT" json:"protected_file_patterns,omitzero"`
UnprotectedFilePatterns string `xorm:"TEXT" json:"unprotected_file_patterns,omitzero"`
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false" json:"block_admin_merge_override,omitzero"`
}
type protectedBranchV358 struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s)"`
BranchName string `xorm:"UNIQUE(s)"`
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
Config string `xorm:"LONGTEXT"`
CreatedUnix timeutil.TimeStamp
UpdatedUnix timeutil.TimeStamp
}
func (protectedBranchV358) TableName() string { return "protected_branch" }
type intermediateProtectedBranch struct {
ID int64 `xorm:"pk autoincr"`
NewConfig string `xorm:"LONGTEXT config"`
OldConfig protectedBranchConfigV358 `xorm:"extends"`
}
func (intermediateProtectedBranch) TableName() string { return "protected_branch" }
func MoveBranchProtectionConfigToJSON(ctx context.Context, x base.EngineMigration) error {
if _, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(protectedBranchV358)); err != nil {
return err
}
exists, err := x.Dialect().IsColumnExist(x.DB(), ctx, "protected_branch", "can_push")
if err != nil || !exists {
return err
}
err = base.EngineTransaction(x, func(sess base.Session) error {
err := base.IterateTableByColumn(ctx, x, sess, "id", nil, func(ctx context.Context, pb *intermediateProtectedBranch) error {
if pb.NewConfig != "" {
return nil // skip the already migrated fields
}
const deployKeyUserID int64 = -3 // frozen user.DeployKeyUserID
if pb.OldConfig.WhitelistDeployKeys {
pb.OldConfig.WhitelistUserIDs = append(pb.OldConfig.WhitelistUserIDs, deployKeyUserID)
}
if pb.OldConfig.ForcePushAllowlistDeployKeys {
pb.OldConfig.ForcePushAllowlistUserIDs = append(pb.OldConfig.ForcePushAllowlistUserIDs, deployKeyUserID)
}
configJson, err := json.Marshal(pb.OldConfig)
util.MustNoError(err)
_, err = sess.ID(pb.ID).Cols("config").Update(&protectedBranchV358{Config: string(configJson)})
return err
})
if err != nil {
return err
}
legacyTableInfo, err := x.TableInfo(new(protectedBranchConfigV358))
if err != nil {
return err
}
return base.DropTableColumns(sess, "protected_branch", legacyTableInfo.ColumnsSeq()...)
})
if err != nil {
return err
}
// SQLite rebuilds the table when dropping columns, so restore its unique index.
return x.Sync(new(protectedBranchV358))
}
-116
View File
@@ -1,116 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v29
import (
"fmt"
"testing"
"gitea.dev/modelmigration/migrationtest"
"gitea.dev/modules/json"
"gitea.dev/modules/timeutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type protectedBranchBeforeV358 struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s)"`
BranchName string `xorm:"UNIQUE(s)"`
Priority int64
CreatedUnix timeutil.TimeStamp
UpdatedUnix timeutil.TimeStamp
Config protectedBranchConfigV358 `xorm:"extends"`
}
func (protectedBranchBeforeV358) TableName() string { return "protected_branch" }
func TestMoveBranchProtectionConfigToJSON(t *testing.T) {
x, deferrable := migrationtest.PrepareTestEnv(t, 0, new(protectedBranchBeforeV358))
defer deferrable()
if x == nil || t.Failed() {
return
}
const configJSON = `{
"can_push":true,
"enable_whitelist":true,
"whitelist_user_ids":[2,4,-3],
"whitelist_team_ids":[1,2],
"enable_merge_whitelist":true,
"merge_whitelist_user_ids":[3,5],
"merge_whitelist_team_ids":[3,4],
"enable_bypass_allowlist":true,
"bypass_allowlist_user_ids":[6,7],
"bypass_allowlist_team_ids":[5,6],
"can_force_push":true,
"enable_force_push_allowlist":true,
"force_push_allowlist_user_ids":[8,9,-3],
"force_push_allowlist_team_ids":[7,8],
"enable_status_check":true,
"status_check_contexts":["ci/*","lint"],
"enable_approvals_whitelist":true,
"approvals_whitelist_user_ids":[10,11],
"approvals_whitelist_team_ids":[9,10],
"required_approvals":2,
"block_on_rejected_reviews":true,
"block_on_official_review_requests":true,
"block_on_codeowner_reviews":true,
"block_on_outdated_branch":true,
"dismiss_stale_approvals":true,
"ignore_stale_approvals":true,
"require_signed_commits":true,
"protected_file_patterns":"*.go;*.md",
"unprotected_file_patterns":"docs/**",
"block_admin_merge_override":true
}`
var config protectedBranchConfigV358
require.NoError(t, json.Unmarshal([]byte(configJSON), &config))
config.WhitelistUserIDs = []int64{2, 4}
config.ForcePushAllowlistUserIDs = []int64{8, 9}
config.WhitelistDeployKeys = true
config.ForcePushAllowlistDeployKeys = true
for i := range 101 {
branch := protectedBranchBeforeV358{RepoID: 1, BranchName: fmt.Sprintf("release-%d/*", i), Priority: int64(i), CreatedUnix: 123, UpdatedUnix: 456}
if i%2 == 0 {
branch.Config = config
} else if i == 1 {
branch.Config.StatusCheckContexts = []string{}
}
_, err := x.Insert(&branch)
require.NoError(t, err)
}
require.NoError(t, MoveBranchProtectionConfigToJSON(t.Context(), x))
// A retry must preserve the migrated configuration and its indexes.
require.NoError(t, MoveBranchProtectionConfigToJSON(t.Context(), x))
var branches []protectedBranchV358
require.NoError(t, x.Asc("id").Find(&branches))
require.Len(t, branches, 101)
for i, branch := range branches {
assert.EqualValues(t, i+1, branch.ID)
assert.EqualValues(t, 1, branch.RepoID)
assert.Equal(t, fmt.Sprintf("release-%d/*", i), branch.BranchName)
assert.EqualValues(t, i, branch.Priority)
assert.EqualValues(t, 123, branch.CreatedUnix)
assert.EqualValues(t, 456, branch.UpdatedUnix)
if i%2 == 0 {
assert.JSONEq(t, configJSON, branch.Config)
} else if i == 1 {
assert.JSONEq(t, `{"status_check_contexts":[]}`, branch.Config)
} else {
assert.JSONEq(t, "{}", branch.Config)
}
}
tables, err := x.DBMetas()
require.NoError(t, err)
for _, table := range tables {
if table.Name == "protected_branch" {
assert.ElementsMatch(t, []string{"id", "repo_id", "branch_name", "priority", "config", "created_unix", "updated_unix"}, table.ColumnsSeq())
}
}
_, err = x.Insert(&protectedBranchV358{RepoID: 1, BranchName: branches[0].BranchName})
require.Error(t, err, "the repository/rule unique constraint must survive the migration")
}
-21
View File
@@ -1,21 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v29
import (
"context"
"gitea.dev/modelmigration/base"
)
func DropCommentCommitID(ctx context.Context, x base.EngineMigration) error {
exists, err := x.Dialect().IsColumnExist(x.DB(), ctx, "comment", "commit_id")
if err != nil || !exists {
return err
}
return base.EngineTransaction(x, func(sess base.Session) error {
return base.DropTableColumns(sess, "comment", "commit_id")
})
}
-23
View File
@@ -1,23 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v29
import (
"context"
"gitea.dev/modelmigration/base"
"xorm.io/xorm"
)
func AddReusableWorkflowRefToActionRunJob(_ context.Context, x base.EngineMigration) error {
type ActionRunJob struct {
ReusableWorkflowRef string `xorm:"VARCHAR(512) NOT NULL DEFAULT ''"`
}
_, err := x.SyncWithOptions(xorm.SyncOptions{
IgnoreConstrains: true,
IgnoreDropIndices: true,
}, new(ActionRunJob))
return err
}
-33
View File
@@ -1,33 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v29
import (
"context"
"gitea.dev/modelmigration/base"
"gitea.dev/modules/timeutil"
)
func AddReleaseIDToReaction(_ctx context.Context, x base.EngineMigration) error {
// rows older than v123 can hold NULL, which the unique index doesn't treat as a duplicate of ''
if _, err := x.Exec("UPDATE reaction SET original_author = '' WHERE original_author IS NULL"); err != nil {
return err
}
type Reaction struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(s) NOT NULL"` // first unique column implies INDEX
IssueID int64 `xorm:"UNIQUE(s) INDEX(issue_comment) NOT NULL"`
CommentID int64 `xorm:"UNIQUE(s) INDEX(issue_comment) INDEX NOT NULL"`
ReleaseID int64 `xorm:"UNIQUE(s) INDEX NOT NULL DEFAULT(0)"`
OriginalAuthorID int64 `xorm:"UNIQUE(s) NOT NULL DEFAULT(0)"`
OriginalAuthor string `xorm:"UNIQUE(s) NOT NULL DEFAULT('')"`
Type string `xorm:"UNIQUE(s) NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}
return x.Sync(new(Reaction))
}
-26
View File
@@ -1,26 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v29
import (
"context"
"gitea.dev/modelmigration/base"
"gitea.dev/modules/timeutil"
)
func AddProjectWorkflow(_ context.Context, x base.EngineMigration) error {
type ProjectWorkflow struct {
ID int64 `xorm:"pk autoincr"`
ProjectID int64 `xorm:"INDEX"`
WorkflowEvent string
WorkflowFilters string `xorm:"TEXT JSON"`
WorkflowActions string `xorm:"TEXT JSON"`
Enabled bool `xorm:"DEFAULT true NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}
return x.Sync(&ProjectWorkflow{})
}
-28
View File
@@ -1,28 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
// BelongingScope identifies the owner (user/org) or repository an Actions secret or variable belongs to.
// Both zero values mean "global scope".
// Always use the functions to create a BelongingScope, rather than creating one directly, to avoid accidentally creating an invalid scope.
type BelongingScope struct {
internalOwnerID int64
internalRepoID int64
}
func BelongingScopeOwner(ownerID int64) BelongingScope {
return BelongingScope{internalOwnerID: ownerID}
}
func BelongingScopeRepo(repoID int64) BelongingScope {
return BelongingScope{internalRepoID: repoID}
}
func BelongingScopeGlobal() BelongingScope {
return BelongingScope{}
}
func (scope BelongingScope) GetOwnerRepoIDs() (ownerID, repoID int64) {
return scope.internalOwnerID, scope.internalRepoID
}
+1 -1
View File
@@ -64,7 +64,7 @@ func (cfg *OwnerActionsConfig) GetMaxTokenPermissions() repo_model.ActionsTokenP
return *cfg.MaxTokenPermissions
}
// Default max is write for everything
return repo_model.MakeWriteAllActionsTokenPermissions()
return repo_model.MakeActionsTokenPermissions(perm.AccessModeWrite)
}
// ClampPermissions ensures that the given permissions don't exceed the maximum
+14 -18
View File
@@ -15,7 +15,6 @@ import (
"gitea.dev/models/db"
repo_model "gitea.dev/models/repo"
user_model "gitea.dev/models/user"
"gitea.dev/modules/container"
"gitea.dev/modules/git"
"gitea.dev/modules/json"
"gitea.dev/modules/log"
@@ -297,9 +296,7 @@ func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, er
}
func GetRunsByRepoAndID(ctx context.Context, repoID int64, runIDs []int64) (runs []*ActionRun, err error) {
if len(runIDs) > 0 {
err = db.GetEngine(ctx).In("id", runIDs).Where("repo_id=?", repoID).OrderBy("id").Find(&runs)
}
err = db.GetEngine(ctx).In("id", runIDs).Where("repo_id=?", repoID).OrderBy("id").Find(&runs)
return runs, err
}
@@ -409,27 +406,26 @@ func CancelPreviousJobsByRunConcurrency(ctx context.Context, attempt *ActionRunA
return nil, nil
}
var jobsToCancel []*ActionRunJob
attempts, jobs, err := getConcurrencyEntriesToReplace(ctx, attempt.RepoID, attempt.ConcurrencyGroup, attempt.ConcurrencyCancel)
if err != nil {
return nil, fmt.Errorf("find concurrent runs and jobs: %w", err)
}
jobs = slices.DeleteFunc(jobs, func(job *ActionRunJob) bool { return job.RunID == attempt.RunID })
jobsToCancel = append(jobsToCancel, jobs...)
// cancel runs in the same concurrency group
attempts = slices.DeleteFunc(attempts, func(concurrentAttempt *ActionRunAttempt) bool { return concurrentAttempt.RunID == attempt.RunID })
attemptJobs, err := getRunJobsByAttempts(ctx, attempts)
if err != nil {
return nil, fmt.Errorf("find concurrent attempt jobs: %w", err)
}
return CancelJobs(ctx, append(jobs, attemptJobs...), false)
}
func getRunJobsByAttempts(ctx context.Context, attempts []*ActionRunAttempt) (jobs []*ActionRunJob, _ error) {
for chunk := range slices.Chunk(attempts, db.DefaultMaxInSize) {
attemptIDs := container.FilterSlice(chunk, func(attempt *ActionRunAttempt) (int64, bool) { return attempt.ID, true })
if err := db.GetEngine(ctx).In("run_attempt_id", attemptIDs).Find(&jobs); err != nil {
return nil, err
for _, concurrentAttempt := range attempts {
if concurrentAttempt.RunID == attempt.RunID {
continue
}
jobs, err := GetRunJobsByRunAndAttemptID(ctx, concurrentAttempt.RunID, concurrentAttempt.ID)
if err != nil {
return nil, fmt.Errorf("find run %d attempt %d jobs: %w", concurrentAttempt.RunID, concurrentAttempt.ID, err)
}
jobsToCancel = append(jobsToCancel, jobs...)
}
return jobs, nil
return CancelJobs(ctx, jobsToCancel, false)
}
+1 -1
View File
@@ -179,7 +179,7 @@ func UpdateRunAttempt(ctx context.Context, attempt *ActionRunAttempt, cols ...st
return err
}
if run.LatestAttemptID != attempt.ID {
log.Warn("run %d cannot be updated by old attempt %d, latest attempt is %d", run.ID, attempt.ID, run.LatestAttemptID)
log.Warn("run %d cannot be updated by an old attempt %d", run.LatestAttemptID, attempt.ID)
return nil
}
+43 -31
View File
@@ -109,8 +109,6 @@ type ActionRunJob struct {
// ReusableWorkflowContent is the content of the reusable workflow specified by "uses:".
// Only set when IsReusableCaller is true.
ReusableWorkflowContent []byte `xorm:"LONGBLOB"`
// ReusableWorkflowRef is the fully qualified ref, or the commit, that "uses:" resolved to when the content was loaded.
ReusableWorkflowRef string `xorm:"VARCHAR(512) NOT NULL DEFAULT ''"`
// CallSecrets encodes the reusable workflow caller's "secrets:" section:
// - "" : no "secrets:" section (children only see auto-generated tokens).
// - "inherit" : the caller wrote "secrets: inherit".
@@ -272,24 +270,6 @@ func GetLatestAttemptJobsByRun(ctx context.Context, run *ActionRun) (ActionJobLi
return jobs, nil
}
func GetLatestAttemptJobsByRuns(ctx context.Context, runs []*ActionRun) (map[int64]ActionJobList, error) {
result := make(map[int64]ActionJobList, len(runs))
if len(runs) == 0 {
return result, nil
}
attemptIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) { return run.LatestAttemptID, run.LatestAttemptID != 0 })
legacyRunIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) { return run.ID, run.LatestAttemptID == 0 })
cond := builder.In("run_attempt_id", attemptIDs).Or(builder.Eq{"run_attempt_id": 0}.And(builder.In("run_id", legacyRunIDs)))
var jobs []*ActionRunJob
if err := db.GetEngine(ctx).Where(cond).OrderBy("id").Find(&jobs); err != nil {
return nil, err
}
for _, job := range jobs {
result[job.RunID] = append(result[job.RunID], job)
}
return result, nil
}
// GetAllRunJobsByRepoAndRunID returns all jobs for a run across all attempts.
func GetAllRunJobsByRepoAndRunID(ctx context.Context, repoID, runID int64) (ActionJobList, error) {
var jobs []*ActionRunJob
@@ -696,18 +676,43 @@ func hasFailFastMatrixFailure(jobs []*ActionRunJob) bool {
// It's useful when a new run is triggered, and all previous runs needn't be continued anymore.
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) ([]*ActionRunJob, error) {
// Find all runs in the specified repository, reference, and workflow with non-final status
runIDs := builder.Select("id").From("action_run").Where(FindRunOptions{
runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{
RepoID: repoID,
Ref: ref,
WorkflowID: workflowID,
TriggerEvent: event,
Status: []Status{StatusRunning, StatusWaiting, StatusBlocked, StatusCancelling},
}.ToConds())
var jobs []*ActionRunJob
if err := db.GetEngine(ctx).Where(builder.In("run_id", runIDs)).Find(&jobs); err != nil {
})
if err != nil {
return nil, err
}
return CancelJobs(ctx, jobs, false)
// If there are no runs found, there's no need to proceed with cancellation, so return nil.
if total == 0 {
return nil, nil
}
cancelledJobs := make([]*ActionRunJob, 0, total)
// Iterate over each found run and cancel its associated jobs.
for _, run := range runs {
// Find all jobs associated with the current run.
jobs, err := db.Find[ActionRunJob](ctx, FindRunJobOptions{
RunID: run.ID,
})
if err != nil {
return cancelledJobs, err
}
cjs, err := CancelJobs(ctx, jobs, false)
if err != nil {
return cancelledJobs, err
}
cancelledJobs = append(cancelledJobs, cjs...)
}
// Return nil to indicate successful cancellation of all running and waiting jobs.
return cancelledJobs, nil
}
// GetAncestorCallerIDs returns the IDs of the reusable workflow callers the job is nested in.
@@ -724,7 +729,7 @@ func GetAncestorCallerIDs(ctx context.Context, job *ActionRunJob) (container.Set
return ids, nil
}
func CancelPreviousJobsByJobConcurrency(ctx context.Context, job *ActionRunJob) ([]*ActionRunJob, error) {
func CancelPreviousJobsByJobConcurrency(ctx context.Context, job *ActionRunJob) (jobsToCancel []*ActionRunJob, _ error) {
if job.RawConcurrency == "" {
return nil, nil
}
@@ -744,14 +749,21 @@ func CancelPreviousJobsByJobConcurrency(ctx context.Context, job *ActionRunJob)
return nil, err
}
jobs = slices.DeleteFunc(jobs, func(j *ActionRunJob) bool { return j.ID == job.ID || callerIDs.Contains(j.ID) })
jobsToCancel = append(jobsToCancel, jobs...)
// cancel runs in the same concurrency group
attempts = slices.DeleteFunc(attempts, func(attempt *ActionRunAttempt) bool { return attempt.ID == job.RunAttemptID })
attemptJobs, err := getRunJobsByAttempts(ctx, attempts)
if err != nil {
return nil, fmt.Errorf("find concurrent attempt jobs: %w", err)
for _, attempt := range attempts {
if attempt.ID == job.RunAttemptID {
continue
}
jobs, err := GetRunJobsByRunAndAttemptID(ctx, attempt.RunID, attempt.ID)
if err != nil {
return nil, fmt.Errorf("find run %d attempt %d jobs: %w", attempt.RunID, attempt.ID, err)
}
jobsToCancel = append(jobsToCancel, jobs...)
}
return CancelJobs(ctx, append(jobs, attemptJobs...), false)
return CancelJobs(ctx, jobsToCancel, false)
}
// CancelJobs cancels every cancellable job it is given, force skipping the graceful cancelling
+17 -12
View File
@@ -10,6 +10,22 @@ import (
)
func TestAggregateJobStatus(t *testing.T) {
testStatuses := func(expected Status, statuses []Status) {
t.Helper()
var jobs []*ActionRunJob
for _, v := range statuses {
jobs = append(jobs, &ActionRunJob{Status: v})
}
actual := AggregateJobStatus(jobs)
if !assert.Equal(t, expected, actual) {
var statusStrings []string
for _, s := range statuses {
statusStrings = append(statusStrings, s.String())
}
t.Errorf("AggregateJobStatus(%v) = %v, want %v", statusStrings, statusNames[actual], statusNames[expected])
}
}
cases := []struct {
statuses []Status
expected Status
@@ -76,17 +92,6 @@ func TestAggregateJobStatus(t *testing.T) {
}
for _, c := range cases {
var jobs []*ActionRunJob
for _, status := range c.statuses {
jobs = append(jobs, &ActionRunJob{Status: status})
}
actual := AggregateJobStatus(jobs)
if !assert.Equal(t, c.expected, actual) {
var statusStrings []string
for _, status := range c.statuses {
statusStrings = append(statusStrings, status.String())
}
t.Errorf("AggregateJobStatus(%v) = %v, want %v", statusStrings, statusNames[actual], statusNames[c.expected])
}
testStatuses(c.expected, c.statuses)
}
}
+230 -239
View File
@@ -16,267 +16,189 @@ import (
"github.com/stretchr/testify/require"
)
func TestRunJobDatabase(t *testing.T) {
func TestGetPriorAttemptChildrenByParent(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
ctx := t.Context()
t.Run("prior attempt children by parent", func(t *testing.T) {
ctx := t.Context()
// 3 attempts of one run:
// 1: caller expanded with 3 matrix instances of "work" + non-matrix sibling "summary".
// 2: caller skipped, no children rows.
// 3: placeholder "current" attempt for the walkback subtest.
// 3 attempts of one run:
// 1: caller expanded with 3 matrix instances of "work" + non-matrix sibling "summary".
// 2: caller skipped, no children rows.
// 3: placeholder "current" attempt for the walkback subtest.
run := &ActionRun{
Title: "prior-children-test",
RepoID: 4,
Index: 9501,
OwnerID: 1,
WorkflowID: "matrix.yaml",
TriggerUserID: 1,
Ref: "refs/heads/master",
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
Event: "push",
TriggerEvent: "push",
EventPayload: "{}",
Status: StatusSuccess,
}
require.NoError(t, db.Insert(ctx, run))
run := &ActionRun{
Title: "prior-children-test",
RepoID: 4,
Index: 9501,
OwnerID: 1,
WorkflowID: "matrix.yaml",
const callerAttemptJobID int64 = 9001
insertAttempt := func(t *testing.T, num int64, status Status) *ActionRunAttempt {
t.Helper()
a := &ActionRunAttempt{
RepoID: run.RepoID,
RunID: run.ID,
Attempt: num,
TriggerUserID: 1,
Ref: "refs/heads/master",
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
Event: "push",
TriggerEvent: "push",
EventPayload: "{}",
Status: StatusSuccess,
Status: status,
}
require.NoError(t, db.Insert(ctx, run))
const callerAttemptJobID int64 = 9001
insertAttempt := func(t *testing.T, num int64, status Status) *ActionRunAttempt {
t.Helper()
a := &ActionRunAttempt{
RepoID: run.RepoID,
RunID: run.ID,
Attempt: num,
TriggerUserID: 1,
Status: status,
}
require.NoError(t, db.Insert(ctx, a))
return a
}
insertCaller := func(t *testing.T, attemptID int64, status Status, expanded bool) *ActionRunJob {
t.Helper()
caller := &ActionRunJob{
RunID: run.ID,
RunAttemptID: attemptID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
Name: "caller",
JobID: "caller",
Attempt: 1,
Status: status,
AttemptJobID: callerAttemptJobID,
IsReusableCaller: true,
IsExpanded: expanded,
}
require.NoError(t, db.Insert(ctx, caller))
return caller
}
insertChild := func(t *testing.T, attemptID, parentID, attemptJobID int64, name, jobID string) {
t.Helper()
require.NoError(t, db.Insert(ctx, &ActionRunJob{
RunID: run.ID,
RunAttemptID: attemptID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
Name: name,
JobID: jobID,
Attempt: 1,
Status: StatusSuccess,
AttemptJobID: attemptJobID,
ParentJobID: parentID,
}))
require.NoError(t, db.Insert(ctx, a))
return a
}
insertCaller := func(t *testing.T, attemptID int64, status Status, expanded bool) *ActionRunJob {
t.Helper()
caller := &ActionRunJob{
RunID: run.ID,
RunAttemptID: attemptID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
Name: "caller",
JobID: "caller",
Attempt: 1,
Status: status,
AttemptJobID: callerAttemptJobID,
IsReusableCaller: true,
IsExpanded: expanded,
}
require.NoError(t, db.Insert(ctx, caller))
return caller
}
insertChild := func(t *testing.T, attemptID, parentID, attemptJobID int64, name, jobID string) {
t.Helper()
require.NoError(t, db.Insert(ctx, &ActionRunJob{
RunID: run.ID,
RunAttemptID: attemptID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
Name: name,
JobID: jobID,
Attempt: 1,
Status: StatusSuccess,
AttemptJobID: attemptJobID,
ParentJobID: parentID,
}))
}
attempt1 := insertAttempt(t, 1, StatusSuccess)
caller1 := insertCaller(t, attempt1.ID, StatusSuccess, true)
insertChild(t, attempt1.ID, caller1.ID, 101, "work (alpha)", "work")
insertChild(t, attempt1.ID, caller1.ID, 102, "work (beta)", "work")
insertChild(t, attempt1.ID, caller1.ID, 103, "work (gamma)", "work")
insertChild(t, attempt1.ID, caller1.ID, 104, "summary", "summary")
attempt1 := insertAttempt(t, 1, StatusSuccess)
caller1 := insertCaller(t, attempt1.ID, StatusSuccess, true)
insertChild(t, attempt1.ID, caller1.ID, 101, "work (alpha)", "work")
insertChild(t, attempt1.ID, caller1.ID, 102, "work (beta)", "work")
insertChild(t, attempt1.ID, caller1.ID, 103, "work (gamma)", "work")
insertChild(t, attempt1.ID, caller1.ID, 104, "summary", "summary")
attempt2 := insertAttempt(t, 2, StatusSkipped)
insertCaller(t, attempt2.ID, StatusSkipped, false) // no children intentionally
attempt2 := insertAttempt(t, 2, StatusSkipped)
insertCaller(t, attempt2.ID, StatusSkipped, false) // no children intentionally
// both subtests expect attempt 1's expansion, differing only in the "current" attempt id
assertAttempt1Children := func(t *testing.T, out map[string]map[string]*ActionRunJob) {
t.Helper()
// outer map keyed by JobID: "work" has 3 matrix instances, "summary" 1
assert.Len(t, out, 2)
assert.Len(t, out["work"], 3, "matrix instances must each get their own inner-map entry")
assert.Len(t, out["summary"], 1)
// both subtests expect attempt 1's expansion, differing only in the "current" attempt id
assertAttempt1Children := func(t *testing.T, out map[string]map[string]*ActionRunJob) {
t.Helper()
// outer map keyed by JobID: "work" has 3 matrix instances, "summary" 1
assert.Len(t, out, 2)
assert.Len(t, out["work"], 3, "matrix instances must each get their own inner-map entry")
assert.Len(t, out["summary"], 1)
require.NotNil(t, out["work"]["work (alpha)"])
require.NotNil(t, out["work"]["work (beta)"])
require.NotNil(t, out["work"]["work (gamma)"])
require.NotNil(t, out["summary"]["summary"])
require.NotNil(t, out["work"]["work (alpha)"])
require.NotNil(t, out["work"]["work (beta)"])
require.NotNil(t, out["work"]["work (gamma)"])
require.NotNil(t, out["summary"]["summary"])
assert.Equal(t, int64(101), out["work"]["work (alpha)"].AttemptJobID)
assert.Equal(t, int64(102), out["work"]["work (beta)"].AttemptJobID)
assert.Equal(t, int64(103), out["work"]["work (gamma)"].AttemptJobID)
assert.Equal(t, int64(104), out["summary"]["summary"].AttemptJobID)
}
assert.Equal(t, int64(101), out["work"]["work (alpha)"].AttemptJobID)
assert.Equal(t, int64(102), out["work"]["work (beta)"].AttemptJobID)
assert.Equal(t, int64(103), out["work"]["work (gamma)"].AttemptJobID)
assert.Equal(t, int64(104), out["summary"]["summary"].AttemptJobID)
}
t.Run("matrix instances and non-matrix sibling are indexed by (JobID, Name)", func(t *testing.T) {
// "current" = attempt 2; prior = attempt 1, which is the immediately preceding attempt.
out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt2.ID, callerAttemptJobID)
require.NoError(t, err)
assertAttempt1Children(t, out)
})
t.Run("walkback past an attempt where the caller had no children", func(t *testing.T) {
attempt3 := insertAttempt(t, 3, StatusRunning)
// "current" = attempt 3; the immediately preceding attempt 2 has no children, so the lookup must walk further back to attempt 1.
out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt3.ID, callerAttemptJobID)
require.NoError(t, err)
assertAttempt1Children(t, out)
})
t.Run("matrix instances and non-matrix sibling are indexed by (JobID, Name)", func(t *testing.T) {
// "current" = attempt 2; prior = attempt 1, which is the immediately preceding attempt.
out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt2.ID, callerAttemptJobID)
require.NoError(t, err)
assertAttempt1Children(t, out)
})
t.Run("cancel reusable callers", func(t *testing.T) {
for _, tc := range []struct {
name, title, workflowID string
index int64
status Status
}{
{"NestedBlockedReusableCaller", "cancel-nested-caller", "caller.yaml", 9701, StatusBlocked},
{"CallerWaitsForCancellingChild", "caller-cancelling-child", "test.yaml", 9811, StatusRunning},
} {
t.Run(tc.name, func(t *testing.T) {
ctx := t.Context()
run := &ActionRun{
Title: tc.title,
RepoID: 4,
Index: tc.index,
OwnerID: 1,
WorkflowID: tc.workflowID,
TriggerUserID: 1,
Ref: "refs/heads/master",
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
Event: "push",
TriggerEvent: "push",
EventPayload: "{}",
Status: tc.status,
}
require.NoError(t, db.Insert(ctx, run))
attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: tc.status}
require.NoError(t, db.Insert(ctx, attempt))
run.LatestAttemptID = attempt.ID
require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
newJob := func(job *ActionRunJob) *ActionRunJob {
job.RunID = run.ID
job.RunAttemptID = attempt.ID
job.RepoID = run.RepoID
job.OwnerID = run.OwnerID
job.CommitSHA = run.CommitSHA
job.JobID = job.Name
job.Attempt = 1
job.Status = tc.status
require.NoError(t, db.Insert(ctx, job))
return job
}
// A reusable caller subtree with a Blocked descendant (e.g. a nested caller stuck on an invalid `uses:`) must aggregate to Cancelled, when the run is cancelled.
if tc.status == StatusBlocked {
// outer: a valid top-level caller that expanded; inner: a nested caller stuck Blocked (invalid uses, never expands).
outer := newJob(&ActionRunJob{Name: "outer", AttemptJobID: 1, IsReusableCaller: true, CallUses: "./.gitea/workflows/lib.yml"})
inner := newJob(&ActionRunJob{Name: "inner", AttemptJobID: 2, ParentJobID: outer.ID, IsReusableCaller: true, CallUses: "https://other.example.com/o/r/.gitea/workflows/ci.yml@v1"})
// Cancel all jobs of the attempt, ordered by id (parent before child).
jobs, err := GetRunJobsByRunAndAttemptID(ctx, run.ID, attempt.ID)
require.NoError(t, err)
_, err = CancelJobs(ctx, jobs, false)
require.NoError(t, err)
for _, j := range []*ActionRunJob{outer, inner} {
got := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: j.ID})
assert.Equal(t, StatusCancelled, got.Status, "job %q should be cancelled", j.JobID)
}
gotAttempt := unittest.AssertExistsAndLoadBean(t, &ActionRunAttempt{ID: attempt.ID})
assert.Equal(t, StatusCancelled, gotAttempt.Status, "attempt must aggregate to Cancelled")
gotRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID})
assert.Equal(t, StatusCancelled, gotRun.Status, "run must aggregate to Cancelled, not stay Blocked")
} else {
outer := newJob(&ActionRunJob{Name: "outer", IsReusableCaller: true, IsExpanded: true})
inner := newJob(&ActionRunJob{Name: "inner", ParentJobID: outer.ID, IsReusableCaller: true, IsExpanded: true})
child := newJob(&ActionRunJob{Name: "child", ParentJobID: inner.ID})
runner := &ActionRunner{UUID: "caller-cancelling-child", Name: "caller-cancelling-child", HasCancellingSupport: true}
require.NoError(t, db.Insert(ctx, runner))
task := &ActionTask{JobID: child.ID, Attempt: 1, RunnerID: runner.ID, Status: StatusRunning, Started: timeutil.TimeStampNow(), RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: run.CommitSHA}
require.NoError(t, db.Insert(ctx, task))
child.TaskID = task.ID
_, err := UpdateRunJob(ctx, child, nil, "task_id")
require.NoError(t, err)
cancelled, err := CancelJobs(ctx, []*ActionRunJob{outer}, false)
require.NoError(t, err)
assert.Len(t, cancelled, 3)
for _, job := range []*ActionRunJob{outer, inner, child} {
assert.Equal(t, StatusCancelling, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
}
_, err = UpdateTaskByState(ctx, runner.ID, &runnerv1.TaskState{Id: task.ID, Result: runnerv1.Result_RESULT_CANCELLED})
require.NoError(t, err)
for _, job := range []*ActionRunJob{outer, inner, child} {
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
}
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID}).Status)
}
})
}
t.Run("walkback past an attempt where the caller had no children", func(t *testing.T) {
attempt3 := insertAttempt(t, 3, StatusRunning)
// "current" = attempt 3; the immediately preceding attempt 2 has no children, so the lookup must walk further back to attempt 1.
out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt3.ID, callerAttemptJobID)
require.NoError(t, err)
assertAttempt1Children(t, out)
})
}
t.Run("batch load and cancel run jobs", func(t *testing.T) {
ctx := t.Context()
// A reusable caller subtree with a Blocked descendant (e.g. a nested caller stuck on an invalid `uses:`) must aggregate to Cancelled, when the run is cancelled.
func TestCancelJobs_NestedBlockedReusableCaller(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
ctx := t.Context()
legacyRun := &ActionRun{RepoID: 4, OwnerID: 1, Index: 9600, TriggerUserID: 1, Ref: "refs/heads/batch", Status: StatusRunning}
run := &ActionRun{RepoID: 4, OwnerID: 1, Index: 9601, TriggerUserID: 1, Ref: "refs/heads/batch", Status: StatusRunning}
require.NoError(t, db.Insert(ctx, legacyRun, run))
require.NoError(t, db.Insert(ctx, &ActionRunJob{RunID: legacyRun.ID, RepoID: legacyRun.RepoID, OwnerID: legacyRun.OwnerID}))
oldAttempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1}
latestAttempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 2, TriggerUserID: 2}
require.NoError(t, db.Insert(ctx, oldAttempt, latestAttempt))
for _, attemptID := range []int64{oldAttempt.ID, latestAttempt.ID, latestAttempt.ID} {
require.NoError(t, db.Insert(ctx, &ActionRunJob{RunID: run.ID, RunAttemptID: attemptID, RepoID: run.RepoID, OwnerID: run.OwnerID}))
run := &ActionRun{
Title: "cancel-nested-caller",
RepoID: 4,
Index: 9701,
OwnerID: 1,
WorkflowID: "caller.yaml",
TriggerUserID: 1,
Ref: "refs/heads/master",
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
Event: "push",
TriggerEvent: "push",
EventPayload: "{}",
Status: StatusBlocked,
}
require.NoError(t, db.Insert(ctx, run))
attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: StatusBlocked}
require.NoError(t, db.Insert(ctx, attempt))
run.LatestAttemptID = attempt.ID
require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
newJob := func(name string, attemptJobID, parentID int64, callUses string) *ActionRunJob {
job := &ActionRunJob{
RunID: run.ID,
RunAttemptID: attempt.ID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
Name: name,
JobID: name,
Attempt: 1,
Status: StatusBlocked,
AttemptJobID: attemptJobID,
IsReusableCaller: true,
CallUses: callUses,
ParentJobID: parentID,
}
run.LatestAttemptID = latestAttempt.ID
require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
require.NoError(t, db.Insert(ctx, job))
return job
}
runs := RunList{legacyRun, run}
jobsByRun, err := GetLatestAttemptJobsByRuns(ctx, runs)
require.NoError(t, err)
for _, listedRun := range runs {
expected, err := GetLatestAttemptJobsByRun(ctx, listedRun)
require.NoError(t, err)
require.NotEmpty(t, expected)
assert.Equal(t, expected, jobsByRun[listedRun.ID])
}
// outer: a valid top-level caller that expanded; inner: a nested caller stuck Blocked (invalid uses, never expands).
outer := newJob("outer", 1, 0, "./.gitea/workflows/lib.yml")
inner := newJob("inner", 2, outer.ID, "https://other.example.com/o/r/.gitea/workflows/ci.yml@v1")
attempts, err := runs.GetLatestAttempts(ctx)
require.NoError(t, err)
require.Len(t, attempts, 1)
assert.EqualValues(t, 2, attempts[latestAttempt.ID].TriggerUser.ID)
// Cancel all jobs of the attempt, ordered by id (parent before child).
jobs, err := GetRunJobsByRunAndAttemptID(ctx, run.ID, attempt.ID)
require.NoError(t, err)
_, err = CancelJobs(ctx, jobs, false)
require.NoError(t, err)
chunkedAttempts := []*ActionRunAttempt{oldAttempt}
for i := range db.DefaultMaxInSize {
chunkedAttempts = append(chunkedAttempts, &ActionRunAttempt{ID: -int64(i + 1)})
}
attemptJobs, err := getRunJobsByAttempts(ctx, append(chunkedAttempts, latestAttempt))
require.NoError(t, err)
assert.Len(t, attemptJobs, 3)
cancelled, err := CancelPreviousJobs(ctx, run.RepoID, run.Ref, "", "")
require.NoError(t, err)
assert.Len(t, cancelled, 4)
})
for _, j := range []*ActionRunJob{outer, inner} {
got := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: j.ID})
assert.Equal(t, StatusCancelled, got.Status, "job %q should be cancelled", j.JobID)
}
gotAttempt := unittest.AssertExistsAndLoadBean(t, &ActionRunAttempt{ID: attempt.ID})
assert.Equal(t, StatusCancelled, gotAttempt.Status, "attempt must aggregate to Cancelled")
gotRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID})
assert.Equal(t, StatusCancelled, gotRun.Status, "run must aggregate to Cancelled, not stay Blocked")
}
func TestSettleRunAfterCancel(t *testing.T) {
@@ -476,3 +398,72 @@ func TestForceCancelJobs(t *testing.T) {
assert.Equal(t, StatusCancelled, callerAfter.Status)
})
}
func TestCancelJobs_CallerWaitsForCancellingChild(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
ctx := t.Context()
run := &ActionRun{
Title: "caller-cancelling-child",
RepoID: 4,
Index: 9811,
OwnerID: 1,
WorkflowID: "test.yaml",
TriggerUserID: 1,
Ref: "refs/heads/master",
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
Event: "push",
TriggerEvent: "push",
EventPayload: "{}",
Status: StatusRunning,
}
require.NoError(t, db.Insert(ctx, run))
attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: StatusRunning}
require.NoError(t, db.Insert(ctx, attempt))
run.LatestAttemptID = attempt.ID
require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
newJob := func(name string, parentID int64, isCaller bool) *ActionRunJob {
job := &ActionRunJob{
RunID: run.ID,
RunAttemptID: attempt.ID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
Name: name,
JobID: name,
Attempt: 1,
Status: StatusRunning,
ParentJobID: parentID,
IsReusableCaller: isCaller,
IsExpanded: isCaller,
}
require.NoError(t, db.Insert(ctx, job))
return job
}
outer := newJob("outer", 0, true)
inner := newJob("inner", outer.ID, true)
child := newJob("child", inner.ID, false)
runner := &ActionRunner{UUID: "caller-cancelling-child", Name: "caller-cancelling-child", HasCancellingSupport: true}
require.NoError(t, db.Insert(ctx, runner))
task := &ActionTask{JobID: child.ID, Attempt: 1, RunnerID: runner.ID, Status: StatusRunning, Started: timeutil.TimeStampNow(), RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: run.CommitSHA}
require.NoError(t, db.Insert(ctx, task))
child.TaskID = task.ID
_, err := UpdateRunJob(ctx, child, nil, "task_id")
require.NoError(t, err)
cancelled, err := CancelJobs(ctx, []*ActionRunJob{outer}, false)
require.NoError(t, err)
assert.Len(t, cancelled, 3)
for _, job := range []*ActionRunJob{outer, inner, child} {
assert.Equal(t, StatusCancelling, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
}
_, err = UpdateTaskByState(ctx, runner.ID, &runnerv1.TaskState{Id: task.ID, Result: runnerv1.Result_RESULT_CANCELLED})
require.NoError(t, err)
for _, job := range []*ActionRunJob{outer, inner, child} {
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name)
}
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID}).Status)
}
-17
View File
@@ -5,8 +5,6 @@ package actions
import (
"context"
"maps"
"slices"
"gitea.dev/models/db"
repo_model "gitea.dev/models/repo"
@@ -44,21 +42,6 @@ func (runs RunList) LoadTriggerUser(ctx context.Context) error {
return nil
}
// GetLatestAttempts returns the latest attempts of the runs keyed by attempt ID
func (runs RunList) GetLatestAttempts(ctx context.Context) (map[int64]*ActionRunAttempt, error) {
attemptIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
return run.LatestAttemptID, run.LatestAttemptID != 0
})
attempts := make(map[int64]*ActionRunAttempt, len(attemptIDs))
if len(attemptIDs) == 0 {
return attempts, nil
}
if err := db.GetEngine(ctx).In("id", attemptIDs).Find(&attempts); err != nil {
return nil, err
}
return attempts, ActionRunAttemptList(slices.Collect(maps.Values(attempts))).LoadTriggerUser(ctx)
}
func (runs RunList) LoadRepos(ctx context.Context) error {
repoIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
return run.RepoID, run.Repo == nil
+1 -9
View File
@@ -200,21 +200,13 @@ func (r *ActionRunner) GenerateAndFillToken() {
// CanMatchLabels checks whether the runner's labels can match a job's "runs-on"
// See https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idruns-on
func (r *ActionRunner) CanMatchLabels(jobRunsOn []string) bool {
return len(jobRunsOn) > 0 && !slices.ContainsFunc(jobRunsOn, func(label string) bool { return !util.SliceContainsString(r.AgentLabels, label, true) })
return !slices.ContainsFunc(jobRunsOn, func(label string) bool { return !util.SliceContainsString(r.AgentLabels, label, true) })
}
func init() {
db.RegisterModel(&ActionRunner{})
}
func GetRunnersMapByIDs(ctx context.Context, ids []int64) (map[int64]*ActionRunner, error) {
runners := make(map[int64]*ActionRunner, len(ids))
if len(ids) == 0 {
return runners, nil
}
return runners, db.GetEngine(ctx).In("id", ids).Find(&runners)
}
// FindRunnerOptions
// ownerID == 0 and repoID == 0 means any runner including global runners
// repoID != 0 and WithAvailable == false means any runner for the given repo
+65 -26
View File
@@ -12,32 +12,72 @@ import (
"github.com/stretchr/testify/assert"
)
func TestShouldPersistRunnerTimestamps(t *testing.T) {
for _, scenario := range []struct {
name string
persist func(timeutil.TimeStamp, time.Time) bool
interval time.Duration
freshAge time.Duration
func TestShouldPersistLastOnline(t *testing.T) {
now := time.Now()
tests := []struct {
name string
last timeutil.TimeStamp
want bool
}{
{"online", ShouldPersistLastOnline, RunnerHeartbeatInterval, 5 * time.Second},
{"active", ShouldPersistLastActive, RunnerActiveInterval, time.Second},
} {
t.Run(scenario.name, func(t *testing.T) {
now := time.Now()
for _, tt := range []struct {
name string
last timeutil.TimeStamp
want bool
}{
{"fresh, skip write", timeutil.TimeStamp(now.Add(-scenario.freshAge).Unix()), false},
{"exactly at interval, write", timeutil.TimeStamp(now.Add(-scenario.interval).Unix()), true},
{"stale, write", timeutil.TimeStamp(now.Add(-2 * scenario.interval).Unix()), true},
{"zero (never seen), write", 0, true},
} {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, scenario.persist(tt.last, now))
})
}
{
name: "fresh, skip write",
last: timeutil.TimeStamp(now.Add(-5 * time.Second).Unix()),
want: false,
},
{
name: "exactly at interval, write",
last: timeutil.TimeStamp(now.Add(-RunnerHeartbeatInterval).Unix()),
want: true,
},
{
name: "stale, write",
last: timeutil.TimeStamp(now.Add(-2 * RunnerHeartbeatInterval).Unix()),
want: true,
},
{
name: "zero (never seen), write",
last: 0,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, ShouldPersistLastOnline(tt.last, now))
})
}
}
func TestShouldPersistLastActive(t *testing.T) {
now := time.Now()
tests := []struct {
name string
last timeutil.TimeStamp
want bool
}{
{
name: "fresh, skip write",
last: timeutil.TimeStamp(now.Add(-1 * time.Second).Unix()),
want: false,
},
{
name: "exactly at interval, write",
last: timeutil.TimeStamp(now.Add(-RunnerActiveInterval).Unix()),
want: true,
},
{
name: "stale, write",
last: timeutil.TimeStamp(now.Add(-2 * RunnerActiveInterval).Unix()),
want: true,
},
{
name: "zero (never seen), write",
last: 0,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, ShouldPersistLastActive(tt.last, now))
})
}
}
@@ -46,5 +86,4 @@ func TestCanMatchLabelsCaseInsensitive(t *testing.T) {
runner := &ActionRunner{AgentLabels: []string{"self-hosted", "Linux", "X64"}}
assert.True(t, runner.CanMatchLabels([]string{"SELF-HOSTED", "linux"}))
assert.False(t, runner.CanMatchLabels([]string{"linux", "arm64"}))
assert.False(t, runner.CanMatchLabels(nil))
}
-10
View File
@@ -5,7 +5,6 @@ package actions
import (
"context"
"errors"
"fmt"
"gitea.dev/models/db"
@@ -120,12 +119,3 @@ func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRu
}
return &runnerToken, nil
}
// GetOrCreateActiveRunnerToken returns the latest runner token, or a new one if there is none or it is no longer active
func GetOrCreateActiveRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
token, err := GetLatestRunnerToken(ctx, ownerID, repoID)
if errors.Is(err, util.ErrNotExist) || (token != nil && !token.IsActive) {
return NewRunnerToken(ctx, ownerID, repoID)
}
return token, err
}
+11 -49
View File
@@ -41,57 +41,26 @@ func init() {
db.RegisterModel(new(ActionSchedule))
}
type scheduleSpecKey struct {
scheduleID int64
spec string
}
// CreateScheduleTaskBySchedules creates new schedule task.
func CreateScheduleTaskBySchedules(ctx context.Context, rows []*ActionSchedule) error {
// Return early if there are no rows to insert
if len(rows) == 0 {
return nil
}
// ReplaceScheduleTasksByRepo updates schedules in place and keeps the specs of unchanged crons, so re-registering cannot skip a due occurrence.
func ReplaceScheduleTasksByRepo(ctx context.Context, repoID int64, rows []*ActionSchedule) error {
return db.WithTx(ctx, func(ctx context.Context) error {
var oldSchedules []*ActionSchedule
if err := db.GetEngine(ctx).Cols("id", "workflow_id").Where("repo_id = ?", repoID).Find(&oldSchedules); err != nil {
return err
}
var oldSpecs []*ActionScheduleSpec
if err := db.GetEngine(ctx).Cols("id", "schedule_id", "spec").Where("repo_id = ?", repoID).Find(&oldSpecs); err != nil {
return err
}
scheduleIDs := make(map[string]int64, len(oldSchedules))
for _, schedule := range oldSchedules {
scheduleIDs[schedule.WorkflowID] = schedule.ID
}
specIDs := make(map[scheduleSpecKey]int64, len(oldSpecs))
for _, spec := range oldSpecs {
specIDs[scheduleSpecKey{spec.ScheduleID, spec.Spec}] = spec.ID
}
var keptScheduleIDs, keptSpecIDs []int64
// Loop through each schedule row
for _, row := range rows {
row.Title = util.EllipsisDisplayString(row.Title, 255)
if id, ok := scheduleIDs[row.WorkflowID]; ok {
row.ID = id
if _, err := db.GetEngine(ctx).ID(id).AllCols().Update(row); err != nil {
return err
}
} else if err := db.Insert(ctx, row); err != nil {
// Create new schedule row
if err := db.Insert(ctx, row); err != nil {
return err
}
keptScheduleIDs = append(keptScheduleIDs, row.ID)
// Loop through each schedule spec and create a new spec row
now := time.Now()
for _, spec := range row.Specs {
key := scheduleSpecKey{row.ID, spec}
if id, ok := specIDs[key]; ok {
delete(specIDs, key)
keptSpecIDs = append(keptSpecIDs, id)
continue
}
specRow := &ActionScheduleSpec{
RepoID: row.RepoID,
ScheduleID: row.ID,
@@ -112,16 +81,9 @@ func ReplaceScheduleTasksByRepo(ctx context.Context, repoID int64, rows []*Actio
if err = db.Insert(ctx, specRow); err != nil {
return err
}
keptSpecIDs = append(keptSpecIDs, specRow.ID)
}
}
// an empty NotIn matches every row
if _, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).NotIn("id", keptScheduleIDs).Delete(&ActionSchedule{}); err != nil {
return err
}
_, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).NotIn("id", keptSpecIDs).Delete(&ActionScheduleSpec{})
return err
return nil
})
}
@@ -143,7 +105,7 @@ func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) ([
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks
// There is no other place we can do this because the app.ini will be changed manually
if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
return nil, fmt.Errorf("CleanRepoScheduleTasks: %w", err)
return nil, fmt.Errorf("DeleteCronTaskByRepo: %v", err)
}
// cancel running cron jobs of this repository and delete old schedules
jobs, err := CancelPreviousJobs(
@@ -154,7 +116,7 @@ func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) ([
webhook_module.HookEventSchedule,
)
if err != nil {
return jobs, fmt.Errorf("CleanRepoScheduleTasks: %w", err)
return jobs, fmt.Errorf("CancelPreviousJobs: %v", err)
}
return jobs, nil
}
-43
View File
@@ -1,43 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"testing"
"time"
"gitea.dev/models/unittest"
"gitea.dev/modules/timeutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestReplaceScheduleTasksByRepo(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
const repoID = 4711
register := func(commitSHA, spec string) {
require.NoError(t, ReplaceScheduleTasksByRepo(t.Context(), repoID, []*ActionSchedule{
{RepoID: repoID, WorkflowID: "nightly.yml", CommitSHA: commitSHA, Specs: []string{spec}},
}))
}
register("c1", "17 */6 * * *")
spec := unittest.AssertExistsAndLoadBean(t, &ActionScheduleSpec{RepoID: repoID})
spec.Next = timeutil.TimeStamp(time.Now().Add(-time.Minute).Unix())
require.NoError(t, UpdateScheduleSpec(t.Context(), spec, "next"))
register("c2", "17 */6 * * *")
unittest.AssertExistsAndLoadBean(t, &ActionSchedule{ID: spec.ScheduleID, CommitSHA: "c2"})
unittest.AssertExistsAndLoadBean(t, &ActionScheduleSpec{ID: spec.ID, ScheduleID: spec.ScheduleID, Next: spec.Next})
register("c3", "23 */6 * * *")
unittest.AssertNotExistsBean(t, &ActionScheduleSpec{ID: spec.ID})
assert.Greater(t, unittest.AssertExistsAndLoadBean(t, &ActionScheduleSpec{RepoID: repoID}).Next, spec.Next)
require.NoError(t, ReplaceScheduleTasksByRepo(t.Context(), repoID, nil))
unittest.AssertNotExistsBean(t, &ActionSchedule{RepoID: repoID})
unittest.AssertNotExistsBean(t, &ActionScheduleSpec{RepoID: repoID})
}
+4
View File
@@ -59,6 +59,10 @@ func (s Status) HasRun() bool {
return s.In(StatusSuccess, StatusFailure)
}
func (s Status) IsUnknown() bool {
return s == StatusUnknown
}
func (s Status) IsSuccess() bool {
return s == StatusSuccess
}
+30 -17
View File
@@ -11,28 +11,41 @@ import (
"github.com/stretchr/testify/assert"
)
func TestStatusResultConversions(t *testing.T) {
func TestStatusAsResult(t *testing.T) {
cases := []struct {
status Status
result runnerv1.Result
fromResult bool
status Status
want runnerv1.Result
}{
{StatusUnknown, runnerv1.Result_RESULT_UNSPECIFIED, true},
{StatusWaiting, runnerv1.Result_RESULT_UNSPECIFIED, false},
{StatusRunning, runnerv1.Result_RESULT_UNSPECIFIED, false},
{StatusBlocked, runnerv1.Result_RESULT_UNSPECIFIED, false},
{StatusSuccess, runnerv1.Result_RESULT_SUCCESS, true},
{StatusFailure, runnerv1.Result_RESULT_FAILURE, true},
{StatusCancelled, runnerv1.Result_RESULT_CANCELLED, true},
{StatusCancelling, runnerv1.Result_RESULT_CANCELLED, false},
{StatusSkipped, runnerv1.Result_RESULT_SKIPPED, true},
{StatusUnknown, runnerv1.Result_RESULT_UNSPECIFIED},
{StatusWaiting, runnerv1.Result_RESULT_UNSPECIFIED},
{StatusRunning, runnerv1.Result_RESULT_UNSPECIFIED},
{StatusBlocked, runnerv1.Result_RESULT_UNSPECIFIED},
{StatusSuccess, runnerv1.Result_RESULT_SUCCESS},
{StatusFailure, runnerv1.Result_RESULT_FAILURE},
{StatusCancelled, runnerv1.Result_RESULT_CANCELLED},
{StatusCancelling, runnerv1.Result_RESULT_CANCELLED},
{StatusSkipped, runnerv1.Result_RESULT_SKIPPED},
}
for _, tt := range cases {
assert.Equal(t, tt.result, tt.status.AsResult(), "status=%s", tt.status)
if tt.fromResult {
assert.Equal(t, tt.status, StatusFromResult(tt.result), "result=%s", tt.result)
}
assert.Equal(t, tt.want, tt.status.AsResult(), "status=%s", tt.status)
}
}
func TestStatusFromResult(t *testing.T) {
cases := []struct {
result runnerv1.Result
want Status
}{
{runnerv1.Result_RESULT_UNSPECIFIED, StatusUnknown},
{runnerv1.Result_RESULT_SUCCESS, StatusSuccess},
{runnerv1.Result_RESULT_FAILURE, StatusFailure},
{runnerv1.Result_RESULT_CANCELLED, StatusCancelled},
{runnerv1.Result_RESULT_SKIPPED, StatusSkipped},
}
for _, tt := range cases {
assert.Equal(t, tt.want, StatusFromResult(tt.result), "result=%s", tt.result)
}
}
+22 -14
View File
@@ -7,7 +7,6 @@ import (
"context"
"errors"
"fmt"
"slices"
"strings"
"time"
@@ -479,11 +478,6 @@ func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.Task
for _, v := range state.Steps {
stepStates[v.Id] = v
}
reportedSteps := state.ReportedSteps
if err := validateReportedSteps(reportedSteps); err != nil {
log.Warn("Ignore the steps reported for task %d: %v", state.Id, err)
reportedSteps = nil
}
// Only one request can update the task because the final state needs to be calculated with all job states.
// Otherwise, concurrent requests with transaction will make the SQL read stale job state and result in wrong final state.
@@ -540,14 +534,22 @@ func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.Task
return err
}
if len(reportedSteps) > 0 {
return updateReportedSteps(ctx, task, reportedSteps, now)
}
if slices.ContainsFunc(task.Steps, (*ActionTaskStep).IsReported) {
return nil // the indexes of the steps of the workflow file no longer match the stored steps
}
for _, step := range task.Steps {
step.applyState(stepStates[step.Index], now)
var result runnerv1.Result
if v, ok := stepStates[step.Index]; ok {
result = v.Result
step.LogIndex = v.LogIndex
step.LogLength = v.LogLength
if step.Started == 0 && v.StartedAt != nil {
step.Started = now
}
}
if result != runnerv1.Result_RESULT_UNSPECIFIED {
step.Status = StatusFromResult(result)
step.Stopped = util.IfZero(step.Stopped, now)
} else if step.Started != 0 {
step.Status = StatusRunning
}
if _, err := db.GetEngine(ctx).ID(step.ID).Update(step); err != nil {
return err
}
@@ -631,7 +633,13 @@ func StopTask(ctx context.Context, taskID int64, status Status) error {
}
for _, step := range task.Steps {
step.stop(status, now)
if !step.Status.IsDone() {
step.Status = status
if step.Started == 0 {
step.Started = now
}
step.Stopped = now
}
if _, err := e.ID(step.ID).Update(step); err != nil {
return err
}
-24
View File
@@ -9,7 +9,6 @@ import (
"gitea.dev/models/db"
"gitea.dev/modules/container"
"gitea.dev/modules/timeutil"
"gitea.dev/modules/util"
"xorm.io/builder"
)
@@ -42,29 +41,6 @@ func (tasks TaskList) LoadJobs(ctx context.Context) error {
return jobsList.LoadAttributes(ctx, true)
}
func (tasks TaskList) LoadSteps(ctx context.Context) error {
taskIDs := container.FilterSlice(tasks, func(task *ActionTask) (int64, bool) {
return task.ID, task.Steps == nil
})
if len(taskIDs) == 0 {
return nil
}
var steps []*ActionTaskStep
if err := db.GetEngine(ctx).In("task_id", taskIDs).OrderBy("task_id, `index`").Find(&steps); err != nil {
return err
}
stepsByTask := make(map[int64][]*ActionTaskStep, len(taskIDs))
for _, step := range steps {
stepsByTask[step.TaskID] = append(stepsByTask[step.TaskID], step)
}
for _, task := range tasks {
if task.Steps == nil {
task.Steps = util.SliceNilAsEmpty(stepsByTask[task.ID])
}
}
return nil
}
func (tasks TaskList) LoadAttributes(ctx context.Context) error {
return tasks.LoadJobs(ctx)
}
-144
View File
@@ -5,14 +5,10 @@ package actions
import (
"context"
"fmt"
"time"
runnerv1 "gitea.dev/actionslib/runner/v1"
"gitea.dev/models/db"
"gitea.dev/modules/container"
"gitea.dev/modules/timeutil"
"gitea.dev/modules/util"
)
// ActionTaskStep represents a step of ActionTask
@@ -25,8 +21,6 @@ type ActionTaskStep struct {
Status Status `xorm:"index"`
LogIndex int64
LogLength int64
Stage int64 `xorm:"NOT NULL DEFAULT 0"` // a runnerv1.StepStage, unspecified for the steps parsed from the workflow file instead of reported by the runner
Number int64 `xorm:"NOT NULL DEFAULT 0"` // the index in the workflow file of the step a PRE, MAIN or POST stage belongs to
Started timeutil.TimeStamp
Stopped timeutil.TimeStamp
Created timeutil.TimeStamp `xorm:"created"`
@@ -37,27 +31,6 @@ func (step *ActionTaskStep) Duration() time.Duration {
return calculateDuration(step.Started, step.Stopped, step.Status, step.Updated)
}
// StepStage returns the stored stage, which can be one added to the enum after this Gitea version
func (step *ActionTaskStep) StepStage() runnerv1.StepStage {
return runnerv1.StepStage(step.Stage)
}
// IsReported reports whether the runner reported the step instead of Gitea parsing it from the workflow file.
func (step *ActionTaskStep) IsReported() bool {
return step.StepStage() != runnerv1.StepStage_STEP_STAGE_UNSPECIFIED
}
// WorkflowStepIndex returns the index of the step in the workflow file, or -1 for a step that is not written there.
func (step *ActionTaskStep) WorkflowStepIndex() int64 {
switch step.StepStage() {
case runnerv1.StepStage_STEP_STAGE_UNSPECIFIED:
return step.Index
case runnerv1.StepStage_STEP_STAGE_MAIN:
return step.Number
}
return -1
}
func init() {
db.RegisterModel(new(ActionTaskStep))
}
@@ -66,120 +39,3 @@ func GetTaskStepsByTaskID(ctx context.Context, taskID int64) ([]*ActionTaskStep,
var steps []*ActionTaskStep
return steps, db.GetEngine(ctx).Where("task_id=?", taskID).OrderBy("`index` ASC").Find(&steps)
}
// applyState applies the state the runner reported for the step, nil meaning it reported none.
func (step *ActionTaskStep) applyState(state *runnerv1.StepState, now timeutil.TimeStamp) {
var result runnerv1.Result
if state != nil {
result = state.Result
step.LogIndex = state.LogIndex
step.LogLength = state.LogLength
if step.Started == 0 && state.StartedAt != nil {
step.Started = now
}
}
if result != runnerv1.Result_RESULT_UNSPECIFIED {
step.Status = StatusFromResult(result)
step.Stopped = util.IfZero(step.Stopped, now)
} else if step.Started != 0 {
step.Status = StatusRunning
}
}
// stop finishes a step that had not finished when its task stopped
func (step *ActionTaskStep) stop(status Status, now timeutil.TimeStamp) {
if step.Status.IsDone() {
return
}
step.Status = status
step.Started = util.IfZero(step.Started, now)
step.Stopped = now
}
// maxReportedSteps bounds the steps a runner can make Gitea store for one task.
const maxReportedSteps = 1000
type reportedStepKey struct {
stage runnerv1.StepStage
number int64
}
func validateReportedSteps(steps []*runnerv1.StepState) error {
if len(steps) > maxReportedSteps {
return fmt.Errorf("%d steps exceed the limit of %d", len(steps), maxReportedSteps)
}
seen := make(container.Set[reportedStepKey], len(steps))
for i, v := range steps {
if v.Stage <= runnerv1.StepStage_STEP_STAGE_UNSPECIFIED { // a stage added later is shown by its name
return fmt.Errorf("step %d has invalid stage %d", i, v.Stage)
}
if v.Id != int64(i) || v.Number < 0 || v.LogIndex < 0 || v.LogLength < 0 {
return fmt.Errorf("step %d has invalid id %d, number %d or log range %d+%d", i, v.Id, v.Number, v.LogIndex, v.LogLength)
}
if !seen.Add(reportedStepKey{v.Stage, v.Number}) {
return fmt.Errorf("step %d duplicates stage %s of number %d", i, v.Stage, v.Number)
}
}
return nil
}
// updateReportedSteps replaces the steps of the task with the ones the runner reported.
// A step keeps its times and status when the runner reports it again at another position.
func updateReportedSteps(ctx context.Context, task *ActionTask, reported []*runnerv1.StepState, now timeutil.TimeStamp) error {
e := db.GetEngine(ctx)
sameLayout := len(task.Steps) == len(reported)
for i := 0; sameLayout && i < len(reported); i++ {
step := task.Steps[i]
sameLayout = step.IsReported() && reportedStepKey{step.StepStage(), step.Number} == reportedStepKey{reported[i].Stage, reported[i].Number}
}
if sameLayout {
for i, step := range task.Steps {
step.Name = util.EllipsisDisplayString(reported[i].Name, 255)
step.applyState(reported[i], now)
if task.Status.IsDone() {
step.stop(task.Status, now)
}
if _, err := e.ID(step.ID).Update(step); err != nil {
return err
}
}
return nil
}
known := make(map[reportedStepKey]*ActionTaskStep, len(task.Steps))
for _, step := range task.Steps {
if step.IsReported() {
known[reportedStepKey{step.StepStage(), step.Number}] = step
}
}
steps := make([]*ActionTaskStep, len(reported))
for i, v := range reported {
key := reportedStepKey{v.Stage, v.Number}
step := &ActionTaskStep{
Name: util.EllipsisDisplayString(v.Name, 255),
TaskID: task.ID,
Index: int64(i),
RepoID: task.RepoID,
Stage: int64(key.stage),
Number: key.number,
Status: StatusWaiting,
}
if old, ok := known[key]; ok {
step.Status, step.Started, step.Stopped = old.Status, old.Started, old.Stopped
}
step.applyState(v, now)
if task.Status.IsDone() {
step.stop(task.Status, now)
}
steps[i] = step
}
if _, err := e.Delete(&ActionTaskStep{TaskID: task.ID}); err != nil {
return err
}
if _, err := e.Insert(steps); err != nil {
return err
}
task.Steps = steps
return nil
}
+142 -212
View File
@@ -242,112 +242,150 @@ func TestStopTaskCancellingKeepsReportTime(t *testing.T) {
// its job to the waiting queue and deletes the task and its steps, so a failure
// while assembling the runner response cannot strand the job in running state.
func TestReleaseTaskForRunner(t *testing.T) {
for _, tt := range []struct {
name string
runTitle string
runIndex int64
jobName string
batchSize int
}{
{"release", "release-task-test-run", 9902, "release-job", 0},
{"pagination", "pagination-test-run", 9903, "target-job", 2},
} {
t.Run(tt.name, func(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
if tt.batchSize != 0 {
defer func(orig int) { pickTaskBatchSize = orig }(pickTaskBatchSize)
pickTaskBatchSize = tt.batchSize
}
require.NoError(t, unittest.PrepareTestDatabase())
run := &ActionRun{
Title: tt.runTitle,
RepoID: 1,
OwnerID: 2,
WorkflowID: "test.yaml",
Index: tt.runIndex,
TriggerUserID: 2,
Ref: "refs/heads/main",
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
Event: "push",
TriggerEvent: "push",
Status: StatusWaiting,
}
require.NoError(t, db.Insert(t.Context(), run))
disabledRun := &ActionRun{RepoID: 2, TriggerUserID: 2}
require.NoError(t, db.Insert(t.Context(), disabledRun))
disabledJob := &ActionRunJob{RunID: disabledRun.ID, RepoID: 2, Status: StatusWaiting, RunsOn: []string{"ubuntu-latest"}}
require.NoError(t, db.Insert(t.Context(), disabledJob))
if tt.batchSize != 0 {
// Five waiting jobs the runner cannot run, then one it can.
for i := range 5 {
mismatch := &ActionRunJob{
RunID: run.ID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
Name: "mismatch-" + string(rune('a'+i)),
Attempt: 1,
JobID: "mismatch-" + string(rune('a'+i)),
Status: StatusWaiting,
RunsOn: []string{"windows-latest"},
}
require.NoError(t, db.Insert(t.Context(), mismatch))
}
}
job := &ActionRunJob{
RunID: run.ID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
Name: tt.jobName,
Attempt: 1,
JobID: tt.jobName,
Status: StatusWaiting,
RunsOn: []string{"ubuntu-latest"},
WorkflowPayload: []byte("on: push\njobs:\n " + tt.jobName + ":\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"),
}
require.NoError(t, db.Insert(t.Context(), job))
runner := &ActionRunner{
UUID: tt.name + "-runner-uuid",
Name: tt.name + "-runner",
AgentLabels: []string{"ubuntu-latest"},
}
runner.GenerateAndFillToken()
require.NoError(t, db.Insert(t.Context(), runner))
require.NoError(t, db.Insert(t.Context(), &repo_model.RepoUnit{RepoID: run.RepoID, Type: unit.TypeActions}))
task, cancelledJobs, err := CreateTaskForRunner(t.Context(), runner)
require.NoError(t, err)
require.NotNil(t, task)
require.Len(t, cancelledJobs, 1)
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: disabledJob.ID}).Status)
claimed := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID})
if tt.batchSize != 0 {
assert.Equal(t, StatusRunning, claimed.Status)
assert.Equal(t, task.ID, claimed.TaskID)
} else {
require.Equal(t, StatusRunning, claimed.Status)
require.Equal(t, task.ID, claimed.TaskID)
require.NoError(t, ReleaseTaskForRunner(t.Context(), task))
// Job is back in the waiting queue with no task assigned.
released := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID})
assert.Equal(t, StatusWaiting, released.Status)
assert.Zero(t, released.TaskID)
assert.Zero(t, released.Started)
// The task and its steps are gone.
unittest.AssertNotExistsBean(t, &ActionTask{ID: task.ID})
unittest.AssertNotExistsBean(t, &ActionTaskStep{TaskID: task.ID})
}
})
run := &ActionRun{
Title: "release-task-test-run",
RepoID: 1,
OwnerID: 2,
WorkflowID: "test.yaml",
Index: 9902,
TriggerUserID: 2,
Ref: "refs/heads/main",
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
Event: "push",
TriggerEvent: "push",
Status: StatusWaiting,
}
require.NoError(t, db.Insert(t.Context(), run))
disabledRun := &ActionRun{RepoID: 2, TriggerUserID: 2}
require.NoError(t, db.Insert(t.Context(), disabledRun))
disabledJob := &ActionRunJob{RunID: disabledRun.ID, RepoID: 2, Status: StatusWaiting, RunsOn: []string{"ubuntu-latest"}}
require.NoError(t, db.Insert(t.Context(), disabledJob))
job := &ActionRunJob{
RunID: run.ID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
Name: "release-job",
Attempt: 1,
JobID: "release-job",
Status: StatusWaiting,
RunsOn: []string{"ubuntu-latest"},
WorkflowPayload: []byte("on: push\njobs:\n release-job:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"),
}
require.NoError(t, db.Insert(t.Context(), job))
runner := &ActionRunner{
UUID: "release-runner-uuid",
Name: "release-runner",
AgentLabels: []string{"ubuntu-latest"},
}
runner.GenerateAndFillToken()
require.NoError(t, db.Insert(t.Context(), runner))
require.NoError(t, db.Insert(t.Context(), &repo_model.RepoUnit{RepoID: run.RepoID, Type: unit.TypeActions}))
task, cancelledJobs, err := CreateTaskForRunner(t.Context(), runner)
require.NoError(t, err)
require.NotNil(t, task)
require.Len(t, cancelledJobs, 1)
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: disabledJob.ID}).Status)
claimed := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID})
require.Equal(t, StatusRunning, claimed.Status)
require.Equal(t, task.ID, claimed.TaskID)
require.NoError(t, ReleaseTaskForRunner(t.Context(), task))
// Job is back in the waiting queue with no task assigned.
released := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID})
assert.Equal(t, StatusWaiting, released.Status)
assert.Zero(t, released.TaskID)
assert.Zero(t, released.Started)
// The task and its steps are gone.
unittest.AssertNotExistsBean(t, &ActionTask{ID: task.ID})
unittest.AssertNotExistsBean(t, &ActionTaskStep{TaskID: task.ID})
}
// TestCreateTaskForRunnerPagination verifies that a job sitting beyond the first page is still claimed
func TestCreateTaskForRunnerPagination(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
defer func(orig int) { pickTaskBatchSize = orig }(pickTaskBatchSize)
pickTaskBatchSize = 2
run := &ActionRun{
Title: "pagination-test-run",
RepoID: 1,
OwnerID: 2,
WorkflowID: "test.yaml",
Index: 9903,
TriggerUserID: 2,
Ref: "refs/heads/main",
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0",
Event: "push",
TriggerEvent: "push",
Status: StatusWaiting,
}
require.NoError(t, db.Insert(t.Context(), run))
disabledRun := &ActionRun{RepoID: 2, TriggerUserID: 2}
require.NoError(t, db.Insert(t.Context(), disabledRun))
disabledJob := &ActionRunJob{RunID: disabledRun.ID, RepoID: 2, Status: StatusWaiting, RunsOn: []string{"ubuntu-latest"}}
require.NoError(t, db.Insert(t.Context(), disabledJob))
// Five waiting jobs the runner cannot run, then one it can.
// With a page size of 2 the matching job only appears on the third page.
for i := range 5 {
mismatch := &ActionRunJob{
RunID: run.ID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
Name: "mismatch-" + string(rune('a'+i)),
Attempt: 1,
JobID: "mismatch-" + string(rune('a'+i)),
Status: StatusWaiting,
RunsOn: []string{"windows-latest"},
}
require.NoError(t, db.Insert(t.Context(), mismatch))
}
target := &ActionRunJob{
RunID: run.ID,
RepoID: run.RepoID,
OwnerID: run.OwnerID,
CommitSHA: run.CommitSHA,
Name: "target-job",
Attempt: 1,
JobID: "target-job",
Status: StatusWaiting,
RunsOn: []string{"ubuntu-latest"},
WorkflowPayload: []byte("on: push\njobs:\n target-job:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"),
}
require.NoError(t, db.Insert(t.Context(), target))
runner := &ActionRunner{
UUID: "pagination-runner-uuid",
Name: "pagination-runner",
AgentLabels: []string{"ubuntu-latest"},
}
runner.GenerateAndFillToken()
require.NoError(t, db.Insert(t.Context(), runner))
require.NoError(t, db.Insert(t.Context(), &repo_model.RepoUnit{RepoID: run.RepoID, Type: unit.TypeActions}))
task, cancelledJobs, err := CreateTaskForRunner(t.Context(), runner)
require.NoError(t, err)
require.NotNil(t, task)
require.Len(t, cancelledJobs, 1)
assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: disabledJob.ID}).Status)
claimed := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: target.ID})
assert.Equal(t, StatusRunning, claimed.Status)
assert.Equal(t, task.ID, claimed.TaskID)
}
type failFirstStepWrite struct{ fired atomic.Bool }
@@ -384,114 +422,6 @@ func TestUpdateTaskByStateIsAtomic(t *testing.T) {
assert.GreaterOrEqual(t, unittest.AssertExistsAndLoadBean(t, &ActionTaskStep{TaskID: task.ID}).Started, before)
}
func TestUpdateTaskByStateReportedSteps(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
task, _ := newRunningTaskForCancelling(t, "reported-steps-job", true)
require.NoError(t, db.Insert(t.Context(), &ActionTaskStep{Name: "Run echo", TaskID: task.ID, RepoID: task.RepoID, Status: StatusWaiting}))
report := func(t *testing.T, steps []*runnerv1.StepState, reported ...*runnerv1.StepState) []*ActionTaskStep {
t.Helper()
_, err := UpdateTaskByState(t.Context(), task.RunnerID, &runnerv1.TaskState{Id: task.ID, Steps: steps, ReportedSteps: reported})
require.NoError(t, err)
got, err := GetTaskStepsByTaskID(t.Context(), task.ID)
require.NoError(t, err)
return got
}
started := timestamppb.Now()
setup := &runnerv1.StepState{Id: 0, Name: "Set up job", Stage: runnerv1.StepStage_STEP_STAGE_SETUP, StartedAt: started}
main := &runnerv1.StepState{Id: 1, Name: "Run echo", Stage: runnerv1.StepStage_STEP_STAGE_MAIN}
legacy := []*runnerv1.StepState{{Id: 0, StartedAt: started, LogLength: 3}}
t.Run("invalid reported steps fall back to the workflow steps", func(t *testing.T) {
got := report(t, legacy, setup, &runnerv1.StepState{Id: 1, Stage: runnerv1.StepStage_STEP_STAGE_SETUP})
require.Len(t, got, 1)
assert.False(t, got[0].IsReported())
assert.Equal(t, StatusRunning, got[0].Status)
assert.EqualValues(t, 3, got[0].LogLength)
})
t.Run("reported steps replace the workflow steps", func(t *testing.T) {
got := report(t, legacy, setup, main)
require.Len(t, got, 2)
assert.Equal(t, runnerv1.StepStage_STEP_STAGE_SETUP, got[0].StepStage())
assert.EqualValues(t, -1, got[0].WorkflowStepIndex())
assert.Equal(t, StatusRunning, got[0].Status)
assert.NotZero(t, got[0].Started)
assert.Equal(t, "Run echo", got[1].Name)
assert.Equal(t, StatusWaiting, got[1].Status)
assert.EqualValues(t, 0, got[1].WorkflowStepIndex())
})
t.Run("an inserted step keeps the state of the others", func(t *testing.T) {
before, err := GetTaskStepsByTaskID(t.Context(), task.ID)
require.NoError(t, err)
doneSetup := &runnerv1.StepState{Id: 0, Name: "Set up job", Stage: runnerv1.StepStage_STEP_STAGE_SETUP, StartedAt: started, Result: runnerv1.Result_RESULT_SUCCESS, LogLength: 4}
pre := &runnerv1.StepState{Id: 1, Name: "Pre Run checkout", Stage: runnerv1.StepStage_STEP_STAGE_PRE, StartedAt: started, LogIndex: 4, LogLength: 2}
movedMain := &runnerv1.StepState{Id: 2, Name: "Run echo", Stage: runnerv1.StepStage_STEP_STAGE_MAIN}
got := report(t, legacy, doneSetup, pre, movedMain)
require.Len(t, got, 3)
assert.Equal(t, StatusSuccess, got[0].Status)
assert.Equal(t, before[0].Started, got[0].Started)
assert.Equal(t, runnerv1.StepStage_STEP_STAGE_PRE, got[1].StepStage())
assert.Equal(t, StatusRunning, got[1].Status)
assert.EqualValues(t, 4, got[1].LogIndex)
assert.EqualValues(t, 2, got[2].Index)
})
t.Run("workflow steps no longer apply once reported", func(t *testing.T) {
before, err := GetTaskStepsByTaskID(t.Context(), task.ID)
require.NoError(t, err)
got := report(t, []*runnerv1.StepState{{Id: 0, Result: runnerv1.Result_RESULT_FAILURE}})
assert.Equal(t, before, got)
})
t.Run("unfinished steps stop with the task", func(t *testing.T) {
doneSetup := &runnerv1.StepState{Id: 0, Name: "Set up job", Stage: runnerv1.StepStage_STEP_STAGE_SETUP, StartedAt: started, Result: runnerv1.Result_RESULT_SUCCESS}
runningMain := &runnerv1.StepState{Id: 1, Name: "Run echo", Stage: runnerv1.StepStage_STEP_STAGE_MAIN, StartedAt: started}
cleanup := &runnerv1.StepState{Id: 2, Name: "Complete job", Stage: runnerv1.StepStage_STEP_STAGE_CLEANUP}
_, err := UpdateTaskByState(t.Context(), task.RunnerID, &runnerv1.TaskState{
Id: task.ID, Result: runnerv1.Result_RESULT_FAILURE,
ReportedSteps: []*runnerv1.StepState{doneSetup, runningMain, cleanup},
})
require.NoError(t, err)
got, err := GetTaskStepsByTaskID(t.Context(), task.ID)
require.NoError(t, err)
require.Len(t, got, 3)
assert.Equal(t, StatusSuccess, got[0].Status)
for _, step := range got[1:] {
assert.Equal(t, StatusFailure, step.Status, step.Name)
assert.NotZero(t, step.Started, step.Name)
assert.NotZero(t, step.Stopped, step.Name)
}
})
}
func TestValidateReportedSteps(t *testing.T) {
step := func(id int64, stage runnerv1.StepStage, number int64) *runnerv1.StepState {
return &runnerv1.StepState{Id: id, Stage: stage, Number: number}
}
assert.NoError(t, validateReportedSteps([]*runnerv1.StepState{
step(0, runnerv1.StepStage_STEP_STAGE_SETUP, 0),
step(1, runnerv1.StepStage_STEP_STAGE_PRE, 0),
step(2, runnerv1.StepStage_STEP_STAGE_MAIN, 0),
step(3, runnerv1.StepStage_STEP_STAGE_MAIN, 1),
step(4, runnerv1.StepStage_STEP_STAGE_CLEANUP, 0),
step(5, runnerv1.StepStage(100), 0), // a stage added later, with several steps told apart by number
step(6, runnerv1.StepStage(100), 1),
}))
for name, steps := range map[string][]*runnerv1.StepState{
"unspecified stage": {step(0, runnerv1.StepStage_STEP_STAGE_UNSPECIFIED, 0)},
"negative stage": {step(0, runnerv1.StepStage(-1), 0)},
"id not position": {step(1, runnerv1.StepStage_STEP_STAGE_SETUP, 0)},
"negative number": {step(0, runnerv1.StepStage_STEP_STAGE_MAIN, -1)},
"duplicate main": {step(0, runnerv1.StepStage_STEP_STAGE_MAIN, 0), step(1, runnerv1.StepStage_STEP_STAGE_MAIN, 0)},
"duplicate setup": {step(0, runnerv1.StepStage_STEP_STAGE_SETUP, 0), step(1, runnerv1.StepStage_STEP_STAGE_SETUP, 0)},
"too many": make([]*runnerv1.StepState, maxReportedSteps+1),
} {
assert.Error(t, validateReportedSteps(steps), name)
}
}
// newRunningTaskForCancelling inserts a running run/job/task assigned to a fresh runner,
// which is the state every cancellation test starts from.
func newRunningTaskForCancelling(t *testing.T, name string, hasCancellingSupport bool) (*ActionTask, *ActionRunJob) {
+19
View File
@@ -89,3 +89,22 @@ func calculateDuration(started, stopped timeutil.TimeStamp, status Status, fallb
}
return max(timeSince(s), 0).Truncate(time.Second)
}
// best effort function to convert an action schedule to action run, to be used in GenerateGiteaContext
func (s *ActionSchedule) ToActionRun() *ActionRun {
return &ActionRun{
Title: s.Title,
RepoID: s.RepoID,
Repo: s.Repo,
OwnerID: s.OwnerID,
WorkflowID: s.WorkflowID,
TriggerUserID: s.TriggerUserID,
TriggerUser: s.TriggerUser,
Ref: s.Ref,
CommitSHA: s.CommitSHA,
Event: s.Event,
EventPayload: s.EventPayload,
Created: s.Created,
Updated: s.Updated,
}
}
+16 -6
View File
@@ -15,14 +15,24 @@ import (
)
func TestLogIndexes_ToDB(t *testing.T) {
inputIndexes := LogIndexes{1, 2, 0, -1, -2, math.MaxInt64, math.MinInt64}
got, err := inputIndexes.ToDB()
require.NoError(t, err)
tests := []struct {
indexes LogIndexes
}{
{
indexes: []int64{1, 2, 0, -1, -2, math.MaxInt64, math.MinInt64},
},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got, err := tt.indexes.ToDB()
require.NoError(t, err)
indexes := LogIndexes{}
require.NoError(t, indexes.FromDB(got))
indexes := LogIndexes{}
require.NoError(t, indexes.FromDB(got))
assert.Equal(t, inputIndexes, indexes)
assert.Equal(t, tt.indexes, indexes)
})
}
}
func Test_calculateDuration(t *testing.T) {
+18 -13
View File
@@ -48,7 +48,13 @@ func init() {
db.RegisterModel(new(ActionVariable))
}
func InsertVariable(ctx context.Context, scope BelongingScope, name, data, description string) (*ActionVariable, error) {
func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data, description string) (*ActionVariable, error) {
if ownerID != 0 && repoID != 0 {
// It's trying to create a variable that belongs to a repository, but OwnerID has been set accidentally.
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
ownerID = 0
}
if utf8.RuneCountInString(data) > VariableDataMaxLength {
return nil, util.NewInvalidArgumentErrorf("data too long")
}
@@ -56,8 +62,8 @@ func InsertVariable(ctx context.Context, scope BelongingScope, name, data, descr
description = util.TruncateRunes(description, VariableDescriptionMaxLength)
variable := &ActionVariable{
OwnerID: scope.internalOwnerID,
RepoID: scope.internalRepoID,
OwnerID: ownerID,
RepoID: repoID,
Name: strings.ToUpper(name),
Data: data,
Description: description,
@@ -67,10 +73,10 @@ func InsertVariable(ctx context.Context, scope BelongingScope, name, data, descr
type FindVariablesOpts struct {
db.ListOptions
IDs []int64
Name string
BelongingScope BelongingScope
IDs []int64
RepoID int64
OwnerID int64 // it will be ignored if RepoID is set
Name string
}
func (opts FindVariablesOpts) ToOrders() string {
@@ -90,13 +96,12 @@ func (opts FindVariablesOpts) ToConds() builder.Cond {
// Since we now support instance-level variables,
// there is no need to check for null values for `owner_id` and `repo_id`
ownerID, repoID := opts.BelongingScope.GetOwnerRepoIDs()
cond = cond.And(builder.Eq{"repo_id": repoID})
if repoID != 0 { // if RepoID is set
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
if opts.RepoID != 0 { // if RepoID is set
// ignore OwnerID and treat it as 0
cond = cond.And(builder.Eq{"owner_id": 0})
} else {
cond = cond.And(builder.Eq{"owner_id": ownerID})
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
}
if opts.Name != "" {
@@ -147,14 +152,14 @@ func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string,
}
// Org / User level
ownerVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{BelongingScope: BelongingScopeOwner(run.Repo.OwnerID)})
ownerVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{OwnerID: run.Repo.OwnerID})
if err != nil {
log.Error("find variables of org: %d, error: %v", run.Repo.OwnerID, err)
return nil, err
}
// Repo level
repoVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{BelongingScope: BelongingScopeRepo(run.RepoID)})
repoVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{RepoID: run.RepoID})
if err != nil {
log.Error("find variables of repo: %d, error: %v", run.RepoID, err)
return nil, err
+10 -4
View File
@@ -436,6 +436,12 @@ type GetFeedsOptions struct {
DontCount bool // do counting in GetFeeds
}
func (opts *GetFeedsOptions) ApplyPublicOnly(publicOnly bool) {
if publicOnly {
opts.IncludePrivate = false
}
}
// ActivityReadable return whether doer can read activities of user
func ActivityReadable(user, doer *user_model.User) bool {
return !user.KeepActivityPrivate ||
@@ -472,13 +478,13 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
}
// check activity visibility for actor ( similar to activityReadable() )
if !user_model.DoerCanViewPrivate(opts.Actor) {
if opts.Actor == nil {
cond = cond.And(builder.In("act_user_id",
builder.Select("`user`.id").Where(
builder.Eq{"keep_activity_private": false, "visibility": structs.VisibleTypePublic},
).From("`user`"),
))
} else if !user_model.DoerIsSiteAdminFullAccess(opts.Actor) {
} else if !opts.Actor.IsAdmin {
uidCond := builder.Select("`user`.id").From("`user`").Where(
builder.Eq{"keep_activity_private": false}.
And(builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))).
@@ -503,7 +509,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
}
// check readable repositories by doer/actor
if !user_model.DoerIsSiteAdminFullAccess(opts.Actor) {
if opts.Actor == nil || !opts.Actor.IsAdmin {
cond = cond.And(builder.In("repo_id", repo_model.AccessibleRepoIDsQuery(opts.Actor)))
}
@@ -533,7 +539,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
}
}
if !opts.IncludePrivate || user_model.IsPublicOnlyDoer(opts.Actor) {
if !opts.IncludePrivate {
cond = cond.And(builder.Eq{"`action`.is_private": false})
}
if !opts.IncludeDeleted {
+1 -1
View File
@@ -212,7 +212,7 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
var err error
var cond builder.Cond
// if the actor is the requested user or is an administrator, we can skip the ActivityQueryCondition
if opts.RequestedUser != nil && user_model.DoerCanViewPrivate(opts.Actor) && (user_model.DoerIsSiteAdminFullAccess(opts.Actor) || opts.Actor.ID == opts.RequestedUser.ID) {
if opts.Actor != nil && opts.RequestedUser != nil && (opts.Actor.IsAdmin || opts.Actor.ID == opts.RequestedUser.ID) {
cond = builder.Eq{
"user_id": opts.RequestedUser.ID,
}.And(
+13 -8
View File
@@ -104,14 +104,19 @@ func TestConsistencyUpdateAction(t *testing.T) {
//
// Get rid of incorrectly set created_unix
//
for _, wantCount := range []int{1, 0} {
count, err := activities_model.CountActionCreatedUnixString(t.Context())
assert.NoError(t, err)
assert.EqualValues(t, wantCount, count)
count, err = activities_model.FixActionCreatedUnixString(t.Context())
assert.NoError(t, err)
assert.EqualValues(t, wantCount, count)
}
count, err := activities_model.CountActionCreatedUnixString(t.Context())
assert.NoError(t, err)
assert.EqualValues(t, 1, count)
count, err = activities_model.FixActionCreatedUnixString(t.Context())
assert.NoError(t, err)
assert.EqualValues(t, 1, count)
count, err = activities_model.CountActionCreatedUnixString(t.Context())
assert.NoError(t, err)
assert.EqualValues(t, 0, count)
count, err = activities_model.FixActionCreatedUnixString(t.Context())
assert.NoError(t, err)
assert.EqualValues(t, 0, count)
//
// XORM must be happy now
+96 -36
View File
@@ -228,15 +228,30 @@ func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.Repository
repoIDs := nl.getPendingRepoIDs()
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
err := db.QueryAndProcessByChunk(repoIDs, func(chunk []int64) (out []*repo_model.Repository, err error) {
err = db.GetEngine(ctx).In("id", chunk).Find(&out)
return out, err
}, func(repo *repo_model.Repository) error {
repos[repo.ID] = repo
return nil
})
if err != nil {
return nil, nil, err
left := len(repoIDs)
for left > 0 {
limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", repoIDs[:limit]).
Rows(new(repo_model.Repository))
if err != nil {
return nil, nil, err
}
for rows.Next() {
var repo repo_model.Repository
err = rows.Scan(&repo)
if err != nil {
rows.Close()
return nil, nil, err
}
repos[repo.ID] = &repo
}
_ = rows.Close()
left -= limit
repoIDs = repoIDs[limit:]
}
failed := []int{}
@@ -284,15 +299,30 @@ func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) {
issueIDs := nl.getPendingIssueIDs()
issues := make(map[int64]*issues_model.Issue, len(issueIDs))
err := db.QueryAndProcessByChunk(issueIDs, func(chunk []int64) (out []*issues_model.Issue, err error) {
err = db.GetEngine(ctx).In("id", chunk).Find(&out)
return out, err
}, func(issue *issues_model.Issue) error {
issues[issue.ID] = issue
return nil
})
if err != nil {
return nil, err
left := len(issueIDs)
for left > 0 {
limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", issueIDs[:limit]).
Rows(new(issues_model.Issue))
if err != nil {
return nil, err
}
for rows.Next() {
var issue issues_model.Issue
err = rows.Scan(&issue)
if err != nil {
rows.Close()
return nil, err
}
issues[issue.ID] = &issue
}
_ = rows.Close()
left -= limit
issueIDs = issueIDs[limit:]
}
failures := []int{}
@@ -361,15 +391,30 @@ func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) {
userIDs := nl.getUserIDs()
users := make(map[int64]*user_model.User, len(userIDs))
err := db.QueryAndProcessByChunk(userIDs, func(chunk []int64) (out []*user_model.User, err error) {
err = db.GetEngine(ctx).In("id", chunk).Find(&out)
return out, err
}, func(user *user_model.User) error {
users[user.ID] = user
return nil
})
if err != nil {
return nil, err
left := len(userIDs)
for left > 0 {
limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", userIDs[:limit]).
Rows(new(user_model.User))
if err != nil {
return nil, err
}
for rows.Next() {
var user user_model.User
err = rows.Scan(&user)
if err != nil {
rows.Close()
return nil, err
}
users[user.ID] = &user
}
_ = rows.Close()
left -= limit
userIDs = userIDs[limit:]
}
failures := []int{}
@@ -394,15 +439,30 @@ func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) {
commentIDs := nl.getPendingCommentIDs()
comments := make(map[int64]*issues_model.Comment, len(commentIDs))
err := db.QueryAndProcessByChunk(commentIDs, func(chunk []int64) (out []*issues_model.Comment, err error) {
err = db.GetEngine(ctx).In("id", chunk).Find(&out)
return out, err
}, func(comment *issues_model.Comment) error {
comments[comment.ID] = comment
return nil
})
if err != nil {
return nil, err
left := len(commentIDs)
for left > 0 {
limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", commentIDs[:limit]).
Rows(new(issues_model.Comment))
if err != nil {
return nil, err
}
for rows.Next() {
var comment issues_model.Comment
err = rows.Scan(&comment)
if err != nil {
rows.Close()
return nil, err
}
comments[comment.ID] = &comment
}
_ = rows.Close()
left -= limit
commentIDs = commentIDs[limit:]
}
failures := []int{}
+17 -77
View File
@@ -15,7 +15,6 @@ import (
user_model "gitea.dev/models/user"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCreateOrUpdateIssueNotifications(t *testing.T) {
@@ -110,71 +109,6 @@ func TestNotificationsForUser(t *testing.T) {
}
}
func TestNotificationListLoadAttributes(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
first := &activities_model.Notification{RepoID: 1, IssueID: 1, UserID: 1, CommentID: 2}
last := &activities_model.Notification{RepoID: 2, IssueID: 4, UserID: 2, CommentID: 8}
loaded := &activities_model.Notification{
RepoID: 1, IssueID: 1, UserID: 1, CommentID: 2,
Repository: &repo_model.Repository{ID: 1},
Issue: &issues_model.Issue{ID: 1},
User: &user_model.User{ID: 1},
Comment: &issues_model.Comment{ID: 2},
}
saved := *loaded
list := activities_model.NotificationList{first}
var wantFailures []int
for i := range db.DefaultMaxInSize {
id := unittest.NonexistentID - int64(i)
wantFailures = append(wantFailures, len(list))
list = append(list, &activities_model.Notification{RepoID: id, IssueID: id, UserID: id, CommentID: id})
}
withoutIssue := &activities_model.Notification{RepoID: 1}
list = append(list, last, loaded, withoutIssue)
repos, failures, err := list.LoadRepos(t.Context())
require.NoError(t, err)
assert.Equal(t, wantFailures, failures)
require.Len(t, repos, 2)
assert.Equal(t, int64(1), repos[0].ID)
assert.Equal(t, int64(2), repos[1].ID)
failures, err = list.LoadIssues(t.Context())
require.NoError(t, err)
assert.Equal(t, wantFailures, failures)
failures, err = list.LoadUsers(t.Context())
require.NoError(t, err)
assert.Empty(t, failures)
failures, err = list.LoadComments(t.Context())
require.NoError(t, err)
assert.Empty(t, failures)
for _, notification := range []*activities_model.Notification{first, last} {
require.NotNil(t, notification.Repository)
assert.Equal(t, notification.RepoID, notification.Repository.ID)
require.NotNil(t, notification.Issue)
assert.Equal(t, notification.IssueID, notification.Issue.ID)
assert.Same(t, notification.Repository, notification.Issue.Repo)
require.NotNil(t, notification.User)
assert.Equal(t, notification.UserID, notification.User.ID)
require.NotNil(t, notification.Comment)
assert.Equal(t, notification.CommentID, notification.Comment.ID)
assert.Same(t, notification.Issue, notification.Comment.Issue)
}
assert.Same(t, saved.Repository, loaded.Repository)
assert.Same(t, saved.Issue, loaded.Issue)
assert.Same(t, saved.User, loaded.User)
assert.Same(t, saved.Comment, loaded.Comment)
for _, notification := range list[1 : db.DefaultMaxInSize+1] {
assert.Nil(t, notification.Repository)
assert.Nil(t, notification.Issue)
assert.Nil(t, notification.User)
assert.Nil(t, notification.Comment)
}
assert.Nil(t, withoutIssue.Issue)
assert.Nil(t, withoutIssue.User)
assert.Nil(t, withoutIssue.Comment)
}
func TestNotification_GetRepo(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{RepoID: 1})
@@ -196,17 +130,23 @@ func TestNotification_GetIssue(t *testing.T) {
func TestGetNotificationCount(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
for count, status := range []activities_model.NotificationStatus{
activities_model.NotificationStatusRead,
activities_model.NotificationStatusUnread,
} {
cnt, err := db.Count[activities_model.Notification](t.Context(), activities_model.FindNotificationOptions{
UserID: user.ID,
Status: []activities_model.NotificationStatus{status},
})
assert.NoError(t, err)
assert.EqualValues(t, count, cnt)
}
cnt, err := db.Count[activities_model.Notification](t.Context(), activities_model.FindNotificationOptions{
UserID: user.ID,
Status: []activities_model.NotificationStatus{
activities_model.NotificationStatusRead,
},
})
assert.NoError(t, err)
assert.EqualValues(t, 0, cnt)
cnt, err = db.Count[activities_model.Notification](t.Context(), activities_model.FindNotificationOptions{
UserID: user.ID,
Status: []activities_model.NotificationStatus{
activities_model.NotificationStatusUnread,
},
})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
}
func TestSetNotificationStatus(t *testing.T) {
+9 -3
View File
@@ -83,17 +83,23 @@ func GetActivityStats(ctx context.Context, repo *repo_model.Repository, timeFrom
// GetActivityStatsTopAuthors returns top author stats for git commits for all branches
func GetActivityStatsTopAuthors(ctx context.Context, repo *repo_model.Repository, timeFrom time.Time, count int) ([]*ActivityAuthorData, error) {
authors, err := git.GetCodeActivityAuthors(ctx, repo, timeFrom)
gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo)
if err != nil {
return nil, fmt.Errorf("OpenRepository: %w", err)
}
defer closer.Close()
code, err := gitRepo.GetCodeActivityStats(ctx, timeFrom, "")
if err != nil {
return nil, fmt.Errorf("FillFromGit: %w", err)
}
if authors == nil {
if code.Authors == nil {
return nil, nil
}
users := make(map[int64]*ActivityAuthorData)
var unknownUserID int64
unknownUserAvatarLink := user_model.NewGhostUser().AvatarLink(ctx)
for _, v := range authors {
for _, v := range code.Authors {
if len(v.Email) == 0 {
continue
}
+1 -1
View File
@@ -178,7 +178,7 @@ type ErrGPGKeyIDAlreadyUsed struct {
KeyID string
}
// IsErrGPGKeyIDAlreadyUsed checks if an error is a ErrGPGKeyIDAlreadyUsed.
// IsErrGPGKeyIDAlreadyUsed checks if an error is a ErrKeyNameAlreadyUsed.
func IsErrGPGKeyIDAlreadyUsed(err error) bool {
_, ok := err.(ErrGPGKeyIDAlreadyUsed)
return ok
+37 -4
View File
@@ -18,6 +18,43 @@ import (
"github.com/stretchr/testify/require"
)
func TestCheckArmoredGPGKeyString(t *testing.T) {
testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBFh91QoBCADciaDd7aqegYkn4ZIG7J0p1CRwpqMGjxFroJEMg6M1ZiuEVTRv
z49P4kcr1+98NvFmcNc+x5uJgvPCwr/N8ZW5nqBUs2yrklbFF4MeQomyZJJegP8m
/dsRT3BwIT8YMUtJuCj0iqD9vuKYfjrztcMgC1sYwcE9E9OlA0pWBvUdU2i0TIB1
vOq6slWGvHHa5l5gPfm09idlVxfH5+I+L1uIMx5ovbiVVU5x2f1AR1T18f0t2TVN
0agFTyuoYE1ATmvJHmMcsfgM1Gpd9hIlr9vlupT2kKTPoNzVzsJsOU6Ku/Lf/bac
mF+TfSbRCtmG7dkYZ4metLj7zG/WkW8IvJARABEBAAG0HUFudG9pbmUgR0lSQVJE
IDxzYXBrQHNhcGsuZnI+iQFUBBMBCAA+FiEEEIOwJg/1vpF1itJ4roJVuKDYKOQF
Alh91QoCGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQroJVuKDY
KORreggAlIkC2QjHP5tb7b0+LksB2JMXdY+UzZBcJxtNmvA7gNQaGvWRrhrbePpa
MKDP+3A4BPDBsWFbbB7N56vQ5tROpmWbNKuFOVER4S1bj0JZV0E+xkDLqt9QwQtQ
ojd7oIZJwDUwdud1PvCza2mjgBqqiFE+twbc3i9xjciCGspMniUul1eQYLxRJ0w+
sbvSOUnujnq5ByMSz9ij00O6aiPfNQS5oB5AALfpjYZDvWAAljLVrtmlQJWZ6dZo
T/YNwsW2dECPuti8+Nmu5FxPGDTXxdbnRaeJTQ3T6q1oUVAv7yTXBx5NXfXkMa5i
iEayQIH8Joq5Ev5ja/lRGQQhArMQ2bkBDQRYfdUKAQgAv7B3coLSrOQbuTZSlgWE
QeT+7DWbmqE1LAQA1pQPcUPXLBUVd60amZJxF9nzUYcY83ylDi0gUNJS+DJGOXpT
pzX2IOuOMGbtUSeKwg5s9O4SUO7f2yCc3RGaegER5zgESxelmOXG+b/hoNt7JbdU
JtxcnLr91Jw2PBO/Xf0ZKJ01CQG2Yzdrrj6jnrHyx94seHy0i6xH1o0OuvfVMLfN
/Vbb/ZHh6ym2wHNqRX62b0VAbchcJXX/MEehXGknKTkO6dDUd+mhRgWMf9ZGRFWx
ag4qALimkf1FXtAyD0vxFYeyoWUQzrOvUsm2BxIN/986R08fhkBQnp5nz07mrU02
cQARAQABiQE8BBgBCAAmFiEEEIOwJg/1vpF1itJ4roJVuKDYKOQFAlh91QoCGwwF
CQPCZwAACgkQroJVuKDYKOT32wf/UZqMdPn5OhyhffFzjQx7wolrf92WkF2JkxtH
6c3Htjlt/p5RhtKEeErSrNAxB4pqB7dznHaJXiOdWEZtRVXXjlNHjrokGTesqtKk
lHWtK62/MuyLdr+FdCl68F3ewuT2iu/MDv+D4HPqA47zma9xVgZ9ZNwJOpv3fCOo
RfY66UjGEnfgYifgtI5S84/mp2jaSc9UNvlZB6RSf8cfbJUL74kS2lq+xzSlf0yP
Av844q/BfRuVsJsK1NDNG09LC30B0l3LKBqlrRmRTUMHtgchdX2dY+p7GPOoSzlR
MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
=i9b7
-----END PGP PUBLIC KEY BLOCK-----`
key, err := CheckArmoredGPGKeyString(testGPGArmor)
assert.NoError(t, err, "Could not parse a valid GPG public armored rsa key", key)
// TODO verify value of key
}
func TestCheckArmoredbrainpoolP256r1GPGKeyString(t *testing.T) {
testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2
@@ -71,10 +108,6 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
=i9b7
-----END PGP PUBLIC KEY BLOCK-----`
keys, err := CheckArmoredGPGKeyString(testGPGArmor)
assert.NoError(t, err, "Could not parse a valid GPG public armored rsa key", keys)
// TODO verify value of key
keys, err = CheckArmoredGPGKeyString(testGPGArmor)
require.NotEmpty(t, keys)
ekey := keys[0]
+1 -2
View File
@@ -15,7 +15,6 @@ import (
"sync"
"gitea.dev/models/db"
"gitea.dev/modules/consts"
"gitea.dev/modules/log"
"gitea.dev/modules/setting"
"gitea.dev/modules/util"
@@ -136,7 +135,7 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
defer f.Close()
// Note: chmod command does not support in Windows.
if !consts.IsWindows {
if !setting.IsWindows {
fi, err := f.Stat()
if err != nil {
return err
+37 -15
View File
@@ -23,18 +23,18 @@ import (
func Test_SSHParsePublicKey(t *testing.T) {
testCases := []struct {
name string
keyType string
length int
fp string
content string
name string
skipSSHKeygen bool
keyType string
length int
content string
}{
{"rsa-1024", "rsa", 1024, "SHA256:vSnDkvRh/xM6kMxPidLgrUhq3mCN7CDaronCEm2joyQ", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"},
{"rsa-2048", "rsa", 2048, "SHA256:ZHD//a1b9VuTq9XSunAeYjKeU1xDa2tBFZYrFr2Okkg", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"},
{"ecdsa-256", "ecdsa", 256, "SHA256:Bqx/xgWqRKLtkZ0Lr4iZpgb+5lYsFpSwXwVZbPwuTRw", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"},
{"ecdsa-384", "ecdsa", 384, "SHA256:4qfJOgJDtUd8BrEjyVNdI8IgjiZKouztVde43aDhe1E", "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"},
{"ecdsa-sk", "ecdsa-sk", 256, "SHA256:4wcIu4z+53gHc+db85OPfy8IydyNzPLCr6kHIs625LQ", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"},
{"ed25519-sk", "ed25519-sk", 256, "SHA256:RB4ku1OeWKN7fLMrjxz38DK0mp1BnOPBx4BItjTvJ0g", "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"},
{"rsa-1024", false, "rsa", 1024, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"},
{"rsa-2048", false, "rsa", 2048, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"},
{"ecdsa-256", false, "ecdsa", 256, "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"},
{"ecdsa-384", false, "ecdsa", 384, "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"},
{"ecdsa-sk", true, "ecdsa-sk", 256, "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"},
{"ed25519-sk", true, "ed25519-sk", 256, "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"},
}
for _, tc := range testCases {
@@ -44,10 +44,6 @@ func Test_SSHParsePublicKey(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, tc.keyType, keyTypeN)
assert.Equal(t, tc.length, lengthN)
fpN, err := calcFingerprintNative(tc.content)
assert.NoError(t, err)
assert.Equal(t, tc.fp, fpN)
})
})
}
@@ -147,6 +143,32 @@ AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf
}
}
func Test_calcFingerprint(t *testing.T) {
testCases := []struct {
name string
skipSSHKeygen bool
fp string
content string
}{
{"rsa-1024", false, "SHA256:vSnDkvRh/xM6kMxPidLgrUhq3mCN7CDaronCEm2joyQ", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"},
{"rsa-2048", false, "SHA256:ZHD//a1b9VuTq9XSunAeYjKeU1xDa2tBFZYrFr2Okkg", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"},
{"ecdsa-256", false, "SHA256:Bqx/xgWqRKLtkZ0Lr4iZpgb+5lYsFpSwXwVZbPwuTRw", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"},
{"ecdsa-384", false, "SHA256:4qfJOgJDtUd8BrEjyVNdI8IgjiZKouztVde43aDhe1E", "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"},
{"ecdsa-sk", true, "SHA256:4wcIu4z+53gHc+db85OPfy8IydyNzPLCr6kHIs625LQ", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"},
{"ed25519-sk", true, "SHA256:RB4ku1OeWKN7fLMrjxz38DK0mp1BnOPBx4BItjTvJ0g", "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Run("Native", func(t *testing.T) {
fpN, err := calcFingerprintNative(tc.content)
assert.NoError(t, err)
assert.Equal(t, tc.fp, fpN)
})
})
}
}
var (
// Generated with "ssh-keygen -C test@rekor.dev -f id_rsa"
sshPrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
-6
View File
@@ -84,14 +84,12 @@ var (
UserTwoFactorRegenerate = define("user:twofactor:regenerate", "Regenerated two-factor authentication secret for user {scope}.")
UserTwoFactorDisable = define("user:twofactor:disable", "Disabled two-factor authentication for user {scope}.")
UserWebAuthAdd = define("user:webauth:add", "Added WebAuthn key {credential} for user {scope}.")
UserWebAuthRename = define("user:webauth:rename", "Renamed WebAuthn key {previous_credential} of user {scope} to {credential}.")
UserWebAuthRemove = define("user:webauth:remove", "Removed WebAuthn key {credential} from user {scope}.")
UserExternalLoginAdd = define("user:externallogin:add", "Added external login {external_id} for user {scope} using provider {provider}.")
UserExternalLoginRemove = define("user:externallogin:remove", "Removed external login from authentication source {auth_source_id} for user {scope}.")
UserOpenIDAdd = define("user:openid:add", "Associated OpenID {openid} to user {scope}.")
UserOpenIDRemove = define("user:openid:remove", "Removed OpenID {openid} from user {scope}.")
UserAccessTokenAdd = define("user:accesstoken:add", "Added access token {token} for user {scope} with scope {token_scope}.")
UserAccessTokenRegenerate = define("user:accesstoken:regenerate", "Regenerated access token {token} of user {scope}.")
UserAccessTokenRemove = define("user:accesstoken:remove", "Removed access token {token} from user {scope}.")
UserOAuth2ApplicationAdd = define("user:oauth2application:add", "Added OAuth2 application {oauth2_application} for user {scope}.")
UserOAuth2ApplicationUpdate = define("user:oauth2application:update", "Updated OAuth2 application {oauth2_application} of user {scope}.")
@@ -186,10 +184,6 @@ var (
ProjectUpdate = define("project:update", "Updated project {project} in {scope}.")
ProjectDelete = define("project:delete", "Deleted project {project} from {scope}.")
ProjectWorkflowCreate = define("project:workflow:create", "Created workflow {workflow_id} for {event} in project {project} of {scope}.")
ProjectWorkflowUpdate = define("project:workflow:update", "Updated workflow {workflow_id} for {event} in project {project} of {scope}.")
ProjectWorkflowDelete = define("project:workflow:delete", "Deleted workflow {workflow_id} for {event} from project {project} of {scope}.")
WikiPageCreate = define("wiki:page:create", "Created wiki page {page} in repository {scope}.")
WikiPageUpdate = define("wiki:page:update", "Updated wiki page {page} in repository {scope}.")
WikiPageDelete = define("wiki:page:delete", "Deleted wiki page {page} from repository {scope}.")
+22 -16
View File
@@ -17,29 +17,35 @@ import (
func TestFindEventsScopeFilters(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
for _, event := range []*Event{
events := []*Event{
{Action: UserCreate, ScopeType: ScopeUser, ScopeID: 5, Origin: OriginUI, TimestampUnix: timeutil.TimeStamp(1)},
{Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 5, Origin: OriginAPI, TimestampUnix: timeutil.TimeStamp(1)},
{Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 6, Origin: OriginCLI, TimestampUnix: timeutil.TimeStamp(1)},
{Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 7, Origin: OriginSystem, TimestampUnix: timeutil.TimeStamp(1)},
} {
}
for _, event := range events {
require.NoError(t, InsertEvent(t.Context(), event))
}
for _, test := range []struct {
options EventSearchOptions
count int
}{
{EventSearchOptions{ScopeType: ScopeRepository}, 3},
{EventSearchOptions{ScopeID: 5}, 2},
{EventSearchOptions{ScopeType: ScopeRepository, ScopeID: 5}, 1},
{EventSearchOptions{Origin: OriginAPI}, 1},
{EventSearchOptions{Origin: OriginSystem}, 1},
} {
events, _, err := FindEvents(t.Context(), &test.options)
require.NoError(t, err)
assert.Len(t, events, test.count)
}
byType, _, err := FindEvents(t.Context(), &EventSearchOptions{ScopeType: ScopeRepository})
require.NoError(t, err)
assert.Len(t, byType, 3)
byID, _, err := FindEvents(t.Context(), &EventSearchOptions{ScopeID: 5})
require.NoError(t, err)
assert.Len(t, byID, 2)
byScope, _, err := FindEvents(t.Context(), &EventSearchOptions{ScopeType: ScopeRepository, ScopeID: 5})
require.NoError(t, err)
assert.Len(t, byScope, 1)
byOrigin, _, err := FindEvents(t.Context(), &EventSearchOptions{Origin: OriginAPI})
require.NoError(t, err)
assert.Len(t, byOrigin, 1)
bySystemOrigin, _, err := FindEvents(t.Context(), &EventSearchOptions{Origin: OriginSystem})
require.NoError(t, err)
assert.Len(t, bySystemOrigin, 1)
}
func TestFindEventsActionPrefixFilter(t *testing.T) {
Loaded 100 of 1592 files, more files were not shown because too many files have changed in this diff. Show more