Compare commits

..
40 Commits
Author SHA1 Message Date
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
983 changed files with 15829 additions and 21635 deletions

No files matched your search

+1
View File
@@ -63,6 +63,7 @@ cpu.out
/public/assets/fonts /public/assets/fonts
/public/assets/img/avatar /public/assets/img/avatar
/vendor /vendor
/VERSION
/.air /.air
/.go-licenses /.go-licenses
/Dockerfile /Dockerfile
+1 -1
View File
@@ -34,7 +34,7 @@ runs:
env: env:
# pgsql is chosen to be the unlucky one to run with the slow "race detector", it is about 60% slower. # 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 GOTEST_FLAGS: -race -timeout=40m
TAGS: bindata TAGS: bindata gogit
TEST_LDAP: 1 TEST_LDAP: 1
TEST_SHARD: ${{ inputs.shard }} TEST_SHARD: ${{ inputs.shard }}
TEST_TOTAL_SHARDS: ${{ inputs.total-shards }} TEST_TOTAL_SHARDS: ${{ inputs.total-shards }}
+8 -1
View File
@@ -37,15 +37,22 @@ jobs:
- uses: ./.github/actions/go-setup - uses: ./.github/actions/go-setup
- run: make deps-backend deps-tools - run: make deps-backend deps-tools
- run: TAGS="bindata" make backend - run: TAGS="bindata" make backend
- run: TAGS="bindata gogit" make backend
- name: warm test compile cache (bindata) - name: warm test compile cache (bindata)
env: env:
TAGS: bindata TAGS: bindata
GOTEST_FLAGS: -race -list=^$$ -count=1 GOTEST_FLAGS: -race -list=^$$ -count=1
run: make test-backend 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 - name: warm integration compile cache
run: | run: |
TAGS="bindata" make test-integration-compile 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: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
+2 -2
View File
@@ -9,7 +9,7 @@ concurrency:
group: cron-renovate group: cron-renovate
env: env:
RENOVATE_VERSION: 44.143.0 # renovate: datasource=npm depName=renovate RENOVATE_VERSION: 44.109.1 # renovate: datasource=npm depName=renovate
permissions: permissions:
contents: read contents: read
@@ -21,7 +21,7 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: renovatebot/github-action@230ce922b08968d0a4f6f70f295601daff09ef1d # v46.3.7 - uses: renovatebot/github-action@9fea9f0fbf80401026d11d03911d62b1f70fef1f # v46.3.3
with: with:
renovate-version: ${{ env.RENOVATE_VERSION }} renovate-version: ${{ env.RENOVATE_VERSION }}
configurationFile: renovate.json5 configurationFile: renovate.json5
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
contents: write contents: write
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: crowdin/github-action@9c23991700c0ec5256fd41089b9d9d7d540e424e # v3.3.0 - uses: crowdin/github-action@df474cdfb9f41d6ae777118749477c2cdf7cacc8 # v3.2.0
with: with:
upload_sources: true upload_sources: true
upload_translations: false upload_translations: false
+1 -1
View File
@@ -56,7 +56,7 @@ jobs:
pull-requests: write pull-requests: write
statuses: write statuses: write
steps: steps:
- uses: go-gitea/giteabot@8033358e118c0bd97f5e9902dc5d6633eadc6aeb # v1.2.1 - uses: go-gitea/giteabot@f48c6a15e0d384f037aea19cc05ff5e9551096b9 # v1.1.0
with: with:
github_token: ${{ secrets.GITEABOT_TOKEN }} github_token: ${{ secrets.GITEABOT_TOKEN }}
checks: ${{ github.event.inputs.checks || 'labels,merge_queue,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions' }} checks: ${{ github.event.inputs.checks || 'labels,merge_queue,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions' }}
+4 -3
View File
@@ -27,7 +27,6 @@ jobs:
- run: TAGS="bindata" make generate-go lint-backend # lint-go can lint with "bindata" tags - run: TAGS="bindata" make generate-go lint-backend # lint-go can lint with "bindata" tags
lint-on-demand: 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 needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -39,6 +38,8 @@ jobs:
with: with:
cache: "false" 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' - if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true' || needs.files-changed.outputs.actions == 'true'
uses: ./.github/actions/python-setup uses: ./.github/actions/python-setup
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true' - if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true'
@@ -92,13 +93,13 @@ jobs:
env: env:
GOOS: linux GOOS: linux
GOARCH: arm64 GOARCH: arm64
TAGS: bindata TAGS: bindata gogit
- name: build-backend-windows - name: build-backend-windows
run: go build -ldflags '-s -w' -o gitea-windows run: go build -ldflags '-s -w' -o gitea-windows
env: env:
GOOS: windows GOOS: windows
GOARCH: amd64 GOARCH: amd64
TAGS: bindata TAGS: bindata gogit
- name: build-backend-386 - name: build-backend-386
run: go build -ldflags '-s -w' -o gitea-linux-386 run: go build -ldflags '-s -w' -o gitea-linux-386
env: env:
+12 -9
View File
@@ -25,8 +25,6 @@ jobs:
env: env:
POSTGRES_DB: test POSTGRES_DB: test
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
options: >-
--mount type=tmpfs,destination=/var/lib/postgresql/data
ports: ports:
- "5432:5432" - "5432:5432"
ldap: ldap:
@@ -63,8 +61,6 @@ jobs:
env: env:
POSTGRES_DB: test POSTGRES_DB: test
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
options: >-
--mount type=tmpfs,destination=/var/lib/postgresql/data
ports: ports:
- "5432:5432" - "5432:5432"
ldap: ldap:
@@ -99,17 +95,17 @@ jobs:
- run: make deps-backend - run: make deps-backend
- run: make backend - run: make backend
env: env:
TAGS: bindata TAGS: bindata gogit
- run: GITEA_TEST_DATABASE=sqlite make test-migration - run: GITEA_TEST_DATABASE=sqlite make test-migration
env: env:
TAGS: bindata TAGS: bindata gogit
- name: run tests - name: run tests
run: GITEA_TEST_DATABASE=sqlite make test-integration run: GITEA_TEST_DATABASE=sqlite make test-integration
timeout-minutes: 50 timeout-minutes: 50
env: env:
# sqlite driver can contain large amount of Golang code, so don't use race detector for it, otherwise, extremely slow # sqlite driver can contain large amount of Golang code, so don't use race detector for it, otherwise, extremely slow
GOTEST_FLAGS: -timeout=40m GOTEST_FLAGS: -timeout=40m
TAGS: bindata TAGS: bindata gogit
test-unit: test-unit:
if: needs.files-changed.outputs.backend == 'true' if: needs.files-changed.outputs.backend == 'true'
@@ -117,7 +113,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
elasticsearch: elasticsearch:
image: elasticsearch:8.19.23@sha256:8b1751877c984f3e4f59bafb15fc890e01969a744255fd5f6860c060b62cff36 image: elasticsearch:8.19.21@sha256:ae1f398ec8734a63eb7f719cecc2ea33e2bb46e23bdf24641dec86fd256bf594
env: env:
discovery.type: single-node discovery.type: single-node
xpack.security.enabled: false xpack.security.enabled: false
@@ -167,6 +163,13 @@ jobs:
GOTEST_FLAGS: -race -timeout=20m GOTEST_FLAGS: -race -timeout=20m
TAGS: bindata TAGS: bindata
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }} 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 - run: make test-check
test-mysql: test-mysql:
@@ -185,7 +188,7 @@ jobs:
options: >- options: >-
--mount type=tmpfs,destination=/bitnami/mysql/data --mount type=tmpfs,destination=/bitnami/mysql/data
elasticsearch: elasticsearch:
image: elasticsearch:8.19.23@sha256:8b1751877c984f3e4f59bafb15fc890e01969a744255fd5f6860c060b62cff36 image: elasticsearch:8.19.21@sha256:ae1f398ec8734a63eb7f719cecc2ea33e2bb46e23bdf24641dec86fd256bf594
env: env:
discovery.type: single-node discovery.type: single-node
xpack.security.enabled: false xpack.security.enabled: false
@@ -37,15 +37,8 @@ jobs:
# retry snapcraft uploads which can be flaky # retry snapcraft uploads which can be flaky
- name: Publish snap - name: Publish snap
run: | 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 for attempt in 1 2 3 4 5; do
snapcraft upload "$SNAP" --release "$channel" && exit 0 snapcraft upload "$SNAP" --release latest/edge && exit 0
echo "::warning::snap upload attempt $attempt failed, retrying in 15s" echo "::warning::snap upload attempt $attempt failed, retrying in 15s"
sleep 15 sleep 15
done done
@@ -76,10 +76,6 @@ jobs:
run: | 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 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
echo "range=$previous..$GITHUB_SHA" >> "$GITHUB_OUTPUT" echo "range=$previous..$GITHUB_SHA" >> "$GITHUB_OUTPUT"
- name: skip reverts and reverted commits in changelog
env:
RANGE: ${{ steps.range.outputs.range }}
run: ./tools/generate-cliffignore.sh "$RANGE" | sort -u > .cliffignore
- uses: orhun/git-cliff-action@a9a95522b26fe6403f7bb24031f21fb573d0f5ff # v4.9.1 - uses: orhun/git-cliff-action@a9a95522b26fe6403f7bb24031f21fb573d0f5ff # v4.9.1
with: with:
args: --tag ${{ github.ref_name }} ${{ steps.range.outputs.range }} args: --tag ${{ github.ref_name }} ${{ steps.range.outputs.range }}
-1
View File
@@ -12,7 +12,6 @@
- In `options/locale`, only edit `locale_en-US.json`, other locales are synced automatically - 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 TS, use `!` instead of `?.`/`??` when a value always exists
- In Go, prefer to use modern language features wherever possible - 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` - 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` - 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` - 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`
+1 -1
View File
@@ -175,7 +175,7 @@ PR titles must follow the [Conventional Commits](https://www.conventionalcommits
type(scope)!: subject 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: Use one of these types:
+23 -5
View File
@@ -6,8 +6,9 @@ SHASUM ?= shasum -a 256
AIR_PACKAGE ?= github.com/air-verse/air@v1.67.4 # renovate: datasource=go 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 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.14.0 # renovate: datasource=go GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2 # renovate: datasource=go
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.17 # 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 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.8.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 ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 # renovate: datasource=go
@@ -74,7 +75,8 @@ STORED_VERSION_FILE := VERSION
GITHUB_REF_TYPE ?= branch GITHUB_REF_TYPE ?= branch
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) 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" # GITEA_VERSION: the Gitea's internal version for display, e.g. "1.28.0+dev-356-ge47d0b66ea"
ifeq ($(GITHUB_REF_TYPE),tag) ifeq ($(GITHUB_REF_TYPE),tag)
# convert tag "v1.2.3" to "1.2.3" # convert tag "v1.2.3" to "1.2.3"
@@ -99,6 +101,11 @@ else
$(error unsupported ref type $(GITHUB_REF_TYPE)) $(error unsupported ref type $(GITHUB_REF_TYPE))
endif 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)" LDFLAGS := $(LDFLAGS) -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
RELEASE_ENV = GO="$(GO)" TAGS="$(TAGS)" LDFLAGS="$(LDFLAGS)" DIST="$(DIST)" VERSION="$(VERSION)" RELEASE_ENV = GO="$(GO)" TAGS="$(TAGS)" LDFLAGS="$(LDFLAGS)" DIST="$(DIST)" VERSION="$(VERSION)"
@@ -125,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) 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 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 ESLINT_FILES := web_src/js tools *.ts tests/e2e
STYLELINT_FILES := web_src/css web_src/js/components/*.vue 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 EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.json
GO_SOURCES := $(wildcard *.go) GO_SOURCES := $(wildcard *.go)
@@ -264,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 checks-backend: tidy-check swagger-check openapi3-check fmt-check swagger-validate security-check ## check backend files
.PHONY: lint .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 .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 .PHONY: lint-frontend
lint-frontend: lint-js lint-css ## lint frontend files lint-frontend: lint-js lint-css ## lint frontend files
@@ -311,6 +320,14 @@ lint-md: node_modules ## lint markdown files
lint-md-fix: node_modules ## lint markdown files and fix issues lint-md-fix: node_modules ## lint markdown files and fix issues
pnpm exec markdownlint --fix *.md 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 .PHONY: lint-go
lint-go: ## lint go files lint-go: ## lint go files
GO=$(GO) GOLANGCI_LINT_PACKAGE=$(GOLANGCI_LINT_PACKAGE) $(GO) run ./tools/lint-go-all.go GO=$(GO) GOLANGCI_LINT_PACKAGE=$(GOLANGCI_LINT_PACKAGE) $(GO) run ./tools/lint-go-all.go
@@ -539,7 +556,7 @@ release-compress: | $(DIST_DIRS)
.PHONY: release-sources .PHONY: release-sources
release-sources: | $(DIST_DIRS) release-sources: | $(DIST_DIRS)
echo $(GITEA_VERSION) > $(STORED_VERSION_FILE) echo $(VERSION) > $(STORED_VERSION_FILE)
# bsdtar needs a ^ to prevent matching subdirectories # bsdtar needs a ^ to prevent matching subdirectories
$(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./) $(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 # use transform to a add a release-folder prefix; in bsdtar the transform parameter equivalent is -s
@@ -566,6 +583,7 @@ deps-tools: ## install tool dependencies
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) & \ $(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) & \
$(GO) install $(GOLANGCI_LINT_PACKAGE) & \ $(GO) install $(GOLANGCI_LINT_PACKAGE) & \
$(GO) install $(GXZ_PACKAGE) & \ $(GO) install $(GXZ_PACKAGE) & \
$(GO) install $(MISSPELL_PACKAGE) & \
$(GO) install $(SWAGGER_PACKAGE) & \ $(GO) install $(SWAGGER_PACKAGE) & \
$(GO) install $(GOVULNCHECK_PACKAGE) & \ $(GO) install $(GOVULNCHECK_PACKAGE) & \
$(GO) install $(ACTIONLINT_PACKAGE) & \ $(GO) install $(ACTIONLINT_PACKAGE) & \
+60
View File
@@ -14,6 +14,26 @@
"path": "filippo.io/edwards25519/LICENSE", "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" "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", "name": "gitea.com/lunny/dingtalk_webhook",
"path": "gitea.com/lunny/dingtalk_webhook/LICENSE", "path": "gitea.com/lunny/dingtalk_webhook/LICENSE",
@@ -54,6 +74,11 @@
"path": "github.com/Azure/go-ntlmssp/LICENSE", "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" "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", "name": "github.com/Necoro/html2text",
"path": "github.com/Necoro/html2text/LICENSE", "path": "github.com/Necoro/html2text/LICENSE",
@@ -309,6 +334,21 @@
"path": "github.com/coder/websocket/LICENSE.txt", "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" "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", "name": "github.com/cpuguy83/go-md2man/v2",
"path": "github.com/cpuguy83/go-md2man/v2/LICENSE.md", "path": "github.com/cpuguy83/go-md2man/v2/LICENSE.md",
@@ -489,6 +529,11 @@
"path": "github.com/golang/snappy/LICENSE", "path": "github.com/golang/snappy/LICENSE",
"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" "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/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", "name": "github.com/google/go-github/v92",
"path": "github.com/google/go-github/v92/LICENSE", "path": "github.com/google/go-github/v92/LICENSE",
@@ -879,6 +924,11 @@
"path": "github.com/santhosh-tekuri/jsonschema/v6/LICENSE", "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 "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", "name": "github.com/sergi/go-diff",
"path": "github.com/sergi/go-diff/LICENSE", "path": "github.com/sergi/go-diff/LICENSE",
@@ -944,6 +994,11 @@
"path": "github.com/ulikunitz/xz/LICENSE", "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" "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", "name": "github.com/urfave/cli-docs/v3",
"path": "github.com/urfave/cli-docs/v3/LICENSE", "path": "github.com/urfave/cli-docs/v3/LICENSE",
@@ -969,6 +1024,11 @@
"path": "github.com/x448/float16/LICENSE", "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" "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", "name": "github.com/yohcop/openid-go",
"path": "github.com/yohcop/openid-go/LICENSE", "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
+8 -2
View File
@@ -13,11 +13,13 @@ import (
"gitea.dev/models/db" "gitea.dev/models/db"
"gitea.dev/modules/dump" "gitea.dev/modules/dump"
"gitea.dev/modules/json"
"gitea.dev/modules/log" "gitea.dev/modules/log"
"gitea.dev/modules/setting" "gitea.dev/modules/setting"
"gitea.dev/modules/storage" "gitea.dev/modules/storage"
"gitea.dev/modules/util" "gitea.dev/modules/util"
"gitea.com/go-chi/session"
"github.com/urfave/cli/v3" "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) log.Info("Packing data directory...%s", setting.AppDataPath)
var excludes []string var excludes []string
if setting.SessionConfig.Provider == "file" { if setting.SessionConfig.OriginalProvider == "file" {
excludes = append(excludes, setting.SessionConfig.ProviderConfig) 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") { if cmd.IsSet("skip-index") && cmd.Bool("skip-index") {
+36
View File
@@ -169,6 +169,10 @@ func parseGitHookCommitRefLine(line string) (oldCommitID, newCommitID string, re
} }
func runHookPreReceive(ctx context.Context, c *cli.Command) error { 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")) setup(ctx, c.Bool("debug"))
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
@@ -209,6 +213,7 @@ Gitea or set your environment appropriately.`, "")
refFullNames := make([]git.RefName, hookBatchSize) refFullNames := make([]git.RefName, hookBatchSize)
count := 0 count := 0
total := 0 total := 0
lastline := 0
out := io.Discard out := io.Discard
if setting.Git.VerbosePush { if setting.Git.VerbosePush {
@@ -221,6 +226,8 @@ Gitea or set your environment appropriately.`, "")
} }
} }
supportProcReceive := git.DefaultFeatures().SupportProcReceive
for scanner.Scan() { for scanner.Scan() {
// TODO: support news feeds for wiki // TODO: support news feeds for wiki
if isWiki { if isWiki {
@@ -233,6 +240,12 @@ Gitea or set your environment appropriately.`, "")
} }
total++ total++
lastline++
// 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 oldCommitIDs[count] = oldCommitID
newCommitIDs[count] = newCommitID newCommitIDs[count] = newCommitID
refFullNames[count] = refFullName refFullNames[count] = refFullName
@@ -250,6 +263,14 @@ Gitea or set your environment appropriately.`, "")
return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error) return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error)
} }
count = 0 count = 0
lastline = 0
}
} else {
fmt.Fprintf(out, ".")
}
if lastline >= hookBatchSize {
fmt.Fprintf(out, "\n")
lastline = 0
} }
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
@@ -267,6 +288,8 @@ Gitea or set your environment appropriately.`, "")
if extra.HasError() { if extra.HasError() {
return fail(ctx, extra.UserMsg, "HookPreReceive(last) failed: %v", extra.Error) 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) 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 // 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 // invoked for every ref update which does not like pre-receive and post-receive
func runHookUpdate(_ context.Context, c *cli.Command) error { 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 // Update is empty and is kept only for backwards compatibility
if len(os.Args) < 3 { if len(os.Args) < 3 {
return nil 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) 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 len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
if setting.OnlyAllowPushIfGiteaEnvironmentSet { if setting.OnlyAllowPushIfGiteaEnvironmentSet {
return fail(ctx, `Rejecting changes as Gitea environment not set. return fail(ctx, `Rejecting changes as Gitea environment not set.
@@ -443,6 +475,10 @@ Gitea or set your environment appropriately.`, "")
return nil 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) reader := bufio.NewReader(os.Stdin)
repoUser := os.Getenv(repo_module.EnvRepoUsername) repoUser := os.Getenv(repo_module.EnvRepoUsername)
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki)) isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
+2
View File
@@ -195,11 +195,13 @@ func runServ(ctx context.Context, c *cli.Command) error {
} }
if len(sshCmdArgs) < 2 { if len(sshCmdArgs) < 2 {
if git.DefaultFeatures().SupportProcReceive {
// for AGit Flow // for AGit Flow
if cmd == "ssh_info" { if cmd == "ssh_info" {
cprintf(c, "%s", agit.SshInfoJson) cprintf(c, "%s", agit.SshInfoJson)
return nil return nil
} }
}
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd) return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
} }
+33 -33
View File
@@ -155,7 +155,7 @@
;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER. ;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
;BUILTIN_SSH_SERVER_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 = ;SSH_DOMAIN =
;; ;;
;; Port number to be exposed in clone URL. ;; 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 ;; 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 ;; 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 ;; 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 ;; Enable SSH Authorized Key Backup when rewriting all keys, default is false
;SSH_AUTHORIZED_KEYS_BACKUP = false ;SSH_AUTHORIZED_KEYS_BACKUP = false
@@ -237,7 +237,7 @@
;SSH_PER_WRITE_PER_KB_TIMEOUT = 30s ;SSH_PER_WRITE_PER_KB_TIMEOUT = 30s
;; ;;
;; Indicate whether to check minimum key size with corresponding type ;; 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 ;; TLS Settings: Either ACME or manual
;; (Other common TLS configuration are found before) ;; (Other common TLS configuration are found before)
@@ -459,7 +459,7 @@ INTERNAL_TOKEN =
;LOGIN_REMEMBER_DAYS = 31 ;LOGIN_REMEMBER_DAYS = 31
;; ;;
;; Name of cookie used to store authentication information. ;; 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. ;; 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. ;; Use this, if needed, when authentication is handled by a reverse proxy or SSO.
@@ -638,8 +638,7 @@ ENABLED = true
;; * https://github.com/git-ecosystem/git-credential-manager ;; * https://github.com/git-ecosystem/git-credential-manager
;; * https://gitea.com/gitea/tea ;; * https://gitea.com/gitea/tea
;; * Gitea App (the official Gitea mobile app) ;; * Gitea App (the official Gitea mobile app)
;; * https://gitea.com/gitea/gitea-vscode ;DEFAULT_APPLICATIONS = git-credential-oauth, git-credential-manager, tea, gitea-app
;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. ;; 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. ;; If you need to use other schemes (e.g. for desktop applications), you can specify them here as a comma-separated list.
@@ -783,6 +782,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) ;; 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 ;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 ;; Set to true to forcibly set core.protectNTFS=false
;DISABLE_CORE_PROTECT_NTFS=false ;DISABLE_CORE_PROTECT_NTFS=false
;; Disable the usage of using partial clones for git. ;; Disable the usage of using partial clones for git.
@@ -809,6 +810,7 @@ LEVEL = Info
;[git.config] ;[git.config]
;diff.algorithm = histogram ;diff.algorithm = histogram
;core.logAllRefUpdates = true ;core.logAllRefUpdates = true
;gc.reflogExpire = 90
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -836,7 +838,7 @@ LEVEL = Info
;EMAIL_DOMAIN_BLOCKLIST = ;EMAIL_DOMAIN_BLOCKLIST =
;; ;;
;; Disallow registration, only allow admins to create accounts. ;; 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 registration only using gitea itself, it works only when DISABLE_REGISTRATION is false
;ALLOW_ONLY_INTERNAL_REGISTRATION = false ;ALLOW_ONLY_INTERNAL_REGISTRATION = false
@@ -968,11 +970,12 @@ LEVEL = Info
;; Value for the domain part of the user's email address in the git log if user ;; 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 ;; 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 ;; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. Default
;; value is "noreply." + the domain part of ROOT_URL ;; value is "noreply." + DOMAIN, where DOMAIN resolves to the value from server.DOMAIN
;NO_REPLY_ADDRESS = ;; 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
;SHOW_REGISTRATION_BUTTON = false ;SHOW_REGISTRATION_BUTTON = true
;; ;;
;; Show milestones dashboard page - a view of all the user's milestones ;; Show milestones dashboard page - a view of all the user's milestones
;SHOW_MILESTONES_DASHBOARD_PAGE = true ;SHOW_MILESTONES_DASHBOARD_PAGE = true
@@ -1095,9 +1098,8 @@ LEVEL = Info
;; Force ssh:// clone url instead of scp-style uri when default SSH port is used ;; Force ssh:// clone url instead of scp-style uri when default SSH port is used
;USE_COMPAT_SSH_URI = false ;USE_COMPAT_SSH_URI = false
;; ;;
;; Scheme of the returned URL for the "go get" response. ;; Value for the "go get" request returns the repository url as https or ssh, default is https
;; Default is "https" if DISABLE_HTTP_GIT=false or SSH is disabled, otherwise "ssh". ;GO_GET_CLONE_URL_PROTOCOL = https
;GO_GET_CLONE_URL_PROTOCOL =
;; ;;
;; Close issues as long as a commit on any branch marks it as fixed ;; Close issues as long as a commit on any branch marks it as fixed
;DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false ;DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
@@ -1218,9 +1220,8 @@ LEVEL = Info
;; In default merge messages only include approvers who are official ;; In default merge messages only include approvers who are official
;DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY = true ;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) ;; In default squash-merge messages include the commit message of all commits comprising the pull request.
;; Repositories store the current value when created or when their settings are next saved, and can change it there. ;POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES = false
;DEFAULT_SQUASH_MERGE_MESSAGE = PR_BODY
;; ;;
;; Add co-authored-by and co-committed-by trailers if committer does not match author ;; Add co-authored-by and co-committed-by trailers if committer does not match author
;ADD_CO_COMMITTER_TRAILERS = true ;ADD_CO_COMMITTER_TRAILERS = true
@@ -1434,14 +1435,11 @@ LEVEL = Info
;; The icon theme for folders (basic/material) ;; The icon theme for folders (basic/material)
;FOLDER_ICON_THEME = basic ;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. ;; 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 ;; 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 = +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). ;; Change the number of users that are displayed in reactions tooltip (triggered by mouse hover).
;REACTION_MAX_USER_NUM = 10 ;REACTION_MAX_USER_NUM = 10
;; ;;
@@ -1674,13 +1672,13 @@ LEVEL = Info
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; General queue type, currently support: level, channel, redis, dummy ;; General queue queue type, currently support: persistable-channel, channel, level, redis, dummy
;; default to level ;; default to persistable-channel
;TYPE = level ;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" ;; Relative paths will be made absolute against "APP_DATA_PATH"
;DATADIR = queues/common ;DATADIR = queues/
;; ;;
;; Default queue length before a channel queue will block ;; Default queue length before a channel queue will block
;LENGTH = 100000 ;LENGTH = 100000
@@ -1688,7 +1686,7 @@ LEVEL = Info
;; Batch size to send for batched queues ;; Batch size to send for batched queues
;BATCH_LENGTH = 20 ;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`. ;; 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. ;; When `TYPE` is `redis` and this is left empty, it falls back to the shared [redis] CONN_STR.
;CONN_STR = ;CONN_STR =
@@ -1756,6 +1754,7 @@ LEVEL = Info
;ENABLE_OPENID_SIGNIN = false ;ENABLE_OPENID_SIGNIN = false
;; ;;
;; Whether to allow registering via OpenID ;; Whether to allow registering via OpenID
;; Do not include to rely on rhw DISABLE_REGISTRATION setting
;;ENABLE_OPENID_SIGNUP = false ;;ENABLE_OPENID_SIGNUP = false
;; ;;
;; Allowed URI patterns (POSIX regexp). ;; Allowed URI patterns (POSIX regexp).
@@ -1986,7 +1985,7 @@ LEVEL = Info
;; Either "memory", "redis", "memcache", or "twoqueue". default is "memory" ;; Either "memory", "redis", "memcache", or "twoqueue". default is "memory"
;ADAPTER = 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 ;INTERVAL = 60
;; ;;
;; For "redis" and "memcache", connection host address ;; For "redis" and "memcache", connection host address
@@ -2017,17 +2016,19 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Either "memory", "file", "redis" or "db", default is "file". "db" will reuse the configuration in [database] ;; Either "memory", "file", "redis", "db", "mysql", "couchbase", "memcache" or "postgres"
;PROVIDER = file ;; Default is "memory". "db" will reuse the configuration in [database]
;PROVIDER = memory
;; ;;
;; Provider config options ;; Provider config options
;; memory: doesn't have any config yet ;; memory: doesn't have any config yet
;; file: session file path, e.g. `data/sessions`, relative paths will be made absolute against _`AppWorkPath`_. ;; file: session file path, e.g. `data/sessions`, relative paths will be made absolute against _`AppWorkPath`_.
;; redis: default to [redis] CONN_STR ;; redis: default to [redis] CONN_STR
;; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
;PROVIDER_CONFIG = ;PROVIDER_CONFIG =
;; ;;
;; Session cookie name ;; 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. ;; 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 = ;COOKIE_SECURE =
@@ -2053,8 +2054,7 @@ LEVEL = Info
;; How Gitea deals with missing repository avatars ;; 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 ;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
;REPOSITORY_AVATAR_FALLBACK = none ;REPOSITORY_AVATAR_FALLBACK = none
;; Image URL for the "image" fallback, used as-is, defaults to Gitea's builtin repository avatar ;REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png
;REPOSITORY_AVATAR_FALLBACK_IMAGE =
;; ;;
;; Max Width and Height of uploaded avatars. ;; Max Width and Height of uploaded avatars.
;; This is to limit the amount of RAM used when resizing the image. ;; 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. - `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). - `pam`: Enable support for PAM (Linux Pluggable Authentication Modules).
Can be used to authenticate local users or extend authentication to methods available to PAM. 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: To include all assets, use the `bindata` tag:
@@ -32,6 +33,13 @@ To include all assets, use the `bindata` tag:
TAGS="bindata" make build 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 ## Changing default paths
Gitea will search for a number of things from the _`CustomPath`_. Gitea will search for a number of things from the _`CustomPath`_.
-4
View File
@@ -48,10 +48,6 @@ Run a single integration test with the `#` selector:
make test-integration#TestName 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 If you hit errors such as a mismatched database version or SSH push failures, try a
clean rebuild first: clean rebuild first:
+29 -53
View File
@@ -17,7 +17,6 @@ import unescapedHtmlLiteral from './tools/eslint-rules/unescaped-html-literal.ts
const jsExts = ['js', 'mjs', 'cjs'] as const; const jsExts = ['js', 'mjs', 'cjs'] as const;
const tsExts = ['ts', 'mts', 'cts'] as const; const tsExts = ['ts', 'mts', 'cts'] as const;
const vueExts = ['vue'] as const;
const restrictedGlobals = [ const restrictedGlobals = [
{name: 'localStorage', message: 'Use `modules/user-settings.ts` instead.'}, {name: 'localStorage', message: 'Use `modules/user-settings.ts` instead.'},
@@ -37,7 +36,7 @@ export default defineConfig([
'public/assets/js', 'public/assets/js',
]), ]),
{ {
files: [`**/*.{${[...jsExts, ...tsExts, ...vueExts].join(',')}}`], files: [`**/*.{${[...jsExts, ...tsExts].join(',')}}`],
ignores: ['dist/*'], ignores: ['dist/*'],
languageOptions: { languageOptions: {
ecmaVersion: 'latest', ecmaVersion: 'latest',
@@ -49,9 +48,7 @@ export default defineConfig([
ecmaFeatures: { ecmaFeatures: {
impliedStrict: true, impliedStrict: true,
}, },
parser: typescriptParser,
project: true, project: true,
extraFileExtensions: vueExts.map((ext) => `.${ext}`),
}, },
}, },
linterOptions: { linterOptions: {
@@ -69,11 +66,8 @@ export default defineConfig([
wc, wc,
}, },
settings: { settings: {
'import-x/extensions': [...jsExts, ...tsExts, ...vueExts].map((ext) => `.${ext}`), 'import-x/extensions': [...jsExts, ...tsExts].map((ext) => `.${ext}`),
'import-x/parsers': { 'import-x/parsers': {'@typescript-eslint/parser': [...jsExts, ...tsExts].map((ext) => `.${ext}`)},
'@typescript-eslint/parser': [...jsExts, ...tsExts].map((ext) => `.${ext}`),
'vue-eslint-parser': vueExts.map((ext) => `.${ext}`),
},
'import-x/resolver': {'typescript': true}, 'import-x/resolver': {'typescript': true},
}, },
rules: { rules: {
@@ -239,7 +233,6 @@ export default defineConfig([
'@typescript-eslint/no-unsafe-assignment': [0], '@typescript-eslint/no-unsafe-assignment': [0],
'@typescript-eslint/no-unsafe-call': [0], '@typescript-eslint/no-unsafe-call': [0],
'@typescript-eslint/no-unsafe-declaration-merging': [2], '@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-enum-comparison': [0],
'@typescript-eslint/no-unsafe-function-type': [2], '@typescript-eslint/no-unsafe-function-type': [2],
'@typescript-eslint/no-unsafe-member-access': [0], '@typescript-eslint/no-unsafe-member-access': [0],
@@ -311,7 +304,6 @@ export default defineConfig([
'func-names': [0], 'func-names': [0],
'func-style': [0], 'func-style': [0],
'getter-return': [2], 'getter-return': [2],
'gitea/unescaped-html-literal': [2],
'grouped-accessor-pairs': [2], 'grouped-accessor-pairs': [2],
'guard-for-in': [0], 'guard-for-in': [0],
'id-denylist': [0], 'id-denylist': [0],
@@ -712,7 +704,6 @@ export default defineConfig([
'unicorn/better-dom-traversing': [2], 'unicorn/better-dom-traversing': [2],
'unicorn/catch-error-name': [0], 'unicorn/catch-error-name': [0],
'unicorn/class-reference-in-static-methods': [2], 'unicorn/class-reference-in-static-methods': [2],
'unicorn/comma-spacing': [0], // only applies to json language
'unicorn/comment-content': [0], 'unicorn/comment-content': [0],
'unicorn/consistent-arrow-return-style': [0], 'unicorn/consistent-arrow-return-style': [0],
'unicorn/consistent-assert': [0], 'unicorn/consistent-assert': [0],
@@ -743,10 +734,8 @@ export default defineConfig([
'unicorn/filename-case': [0], 'unicorn/filename-case': [0],
'unicorn/id-match': [2], 'unicorn/id-match': [2],
'unicorn/import-style': [0], 'unicorn/import-style': [0],
'unicorn/indent': [0], // only applies to json and css languages
'unicorn/isolated-functions': [2, {functions: []}], 'unicorn/isolated-functions': [2, {functions: []}],
'unicorn/iteration-fallback-style': [2, 'fallback'], '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/logical-assignment-operators': [0],
'unicorn/max-nested-calls': [0], 'unicorn/max-nested-calls': [0],
'unicorn/name-replacements': [0], 'unicorn/name-replacements': [0],
@@ -779,20 +768,21 @@ export default defineConfig([
'unicorn/no-chained-comparison': [2], 'unicorn/no-chained-comparison': [2],
'unicorn/no-collection-bracket-access': [2], 'unicorn/no-collection-bracket-access': [2],
'unicorn/no-computed-property-existence-check': [0], 'unicorn/no-computed-property-existence-check': [0],
'unicorn/no-conflicting-constraints': [2],
'unicorn/no-confusing-array-splice': [2], 'unicorn/no-confusing-array-splice': [2],
'unicorn/no-confusing-array-with': [2], 'unicorn/no-confusing-array-with': [2],
'unicorn/no-console-spaces': [0], 'unicorn/no-console-spaces': [0],
'unicorn/no-constant-zero-expression': [2], 'unicorn/no-constant-zero-expression': [2],
'unicorn/no-declarations-before-early-exit': [0], 'unicorn/no-declarations-before-early-exit': [0],
'unicorn/no-deprecated-css-features': [0],
'unicorn/no-document-cookie': [2], 'unicorn/no-document-cookie': [2],
'unicorn/no-double-comparison': [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-if-branches': [2],
'unicorn/no-duplicate-logical-operands': [2], 'unicorn/no-duplicate-logical-operands': [2],
'unicorn/no-duplicate-loops': [0], 'unicorn/no-duplicate-loops': [0],
'unicorn/no-duplicate-set-values': [2], 'unicorn/no-duplicate-set-values': [2],
'unicorn/no-empty-file': [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-error-property-assignment': [2],
'unicorn/no-exports-in-scripts': [2], 'unicorn/no-exports-in-scripts': [2],
'unicorn/no-for-each': [2], 'unicorn/no-for-each': [2],
@@ -800,34 +790,21 @@ export default defineConfig([
'unicorn/no-global-object-property-assignment': [0], 'unicorn/no-global-object-property-assignment': [0],
'unicorn/no-immediate-mutation': [0], 'unicorn/no-immediate-mutation': [0],
'unicorn/no-impossible-length-comparison': [2], 'unicorn/no-impossible-length-comparison': [2],
'unicorn/no-incomplete-accessor-override': [2],
'unicorn/no-incorrect-query-selector': [2], 'unicorn/no-incorrect-query-selector': [2],
'unicorn/no-incorrect-template-string-interpolation': [0], 'unicorn/no-incorrect-template-string-interpolation': [0],
'unicorn/no-ineffective-csp-directives': [2],
'unicorn/no-instanceof-builtins': [2], 'unicorn/no-instanceof-builtins': [2],
'unicorn/no-invalid-argument-count': [2], 'unicorn/no-invalid-argument-count': [2],
'unicorn/no-invalid-boolean-attribute-value': [2],
'unicorn/no-invalid-character-comparison': [2], 'unicorn/no-invalid-character-comparison': [2],
'unicorn/no-invalid-dom-token': [2],
'unicorn/no-invalid-fetch-options': [2], 'unicorn/no-invalid-fetch-options': [2],
'unicorn/no-invalid-file-input-accept': [2], 'unicorn/no-invalid-file-input-accept': [2],
'unicorn/no-invalid-integrity': [2], 'unicorn/no-invalid-media-features': [0],
'unicorn/no-invalid-intl-options': [2],
'unicorn/no-invalid-property-descriptor': [2],
'unicorn/no-invalid-remove-event-listener': [2], '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-invalid-well-known-symbol-methods': [2],
'unicorn/no-javascript-url': [0], // only applies to markdown language
'unicorn/no-keyword-prefix': [0], 'unicorn/no-keyword-prefix': [0],
'unicorn/no-late-current-target-access': [2], 'unicorn/no-late-current-target-access': [2],
'unicorn/no-late-event-control': [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-lonely-if': [2],
'unicorn/no-loop-iterable-mutation': [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-magic-array-flat-depth': [0],
'unicorn/no-manually-wrapped-comments': [0], // too opinionated 'unicorn/no-manually-wrapped-comments': [0], // too opinionated
'unicorn/no-mismatched-map-key': [2], 'unicorn/no-mismatched-map-key': [2],
@@ -840,6 +817,7 @@ export default defineConfig([
'unicorn/no-negated-condition': [0], 'unicorn/no-negated-condition': [0],
'unicorn/no-negation-in-equality-check': [2], 'unicorn/no-negation-in-equality-check': [2],
'unicorn/no-nested-ternary': [0], 'unicorn/no-nested-ternary': [0],
'unicorn/no-nesting-with-mixed-specificity': [0],
'unicorn/no-new-array': [0], 'unicorn/no-new-array': [0],
'unicorn/no-new-buffer': [2], 'unicorn/no-new-buffer': [2],
'unicorn/no-non-function-verb-prefix': [0], 'unicorn/no-non-function-verb-prefix': [0],
@@ -848,9 +826,9 @@ export default defineConfig([
'unicorn/no-object-as-default-parameter': [0], 'unicorn/no-object-as-default-parameter': [0],
'unicorn/no-object-methods-with-collections': [2], 'unicorn/no-object-methods-with-collections': [2],
'unicorn/no-optional-chaining-on-undeclared-variable': [2], 'unicorn/no-optional-chaining-on-undeclared-variable': [2],
'unicorn/no-prevent-default-in-passive-listener': [2],
'unicorn/no-process-exit': [0], 'unicorn/no-process-exit': [0],
'unicorn/no-redundant-comparison': [2], 'unicorn/no-redundant-comparison': [2],
'unicorn/no-redundant-nested-style-rules': [0],
'unicorn/no-return-array-push': [2], 'unicorn/no-return-array-push': [2],
'unicorn/no-selector-as-dom-name': [2], 'unicorn/no-selector-as-dom-name': [2],
'unicorn/no-shorthand-property-overrides': [0], // only applies to css language 'unicorn/no-shorthand-property-overrides': [0], // only applies to css language
@@ -866,6 +844,8 @@ export default defineConfig([
'unicorn/no-typeof-undefined': [2], 'unicorn/no-typeof-undefined': [2],
'unicorn/no-uncalled-method': [2], 'unicorn/no-uncalled-method': [2],
'unicorn/no-undeclared-class-members': [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-depth': [2],
'unicorn/no-unnecessary-array-flat-map': [2], 'unicorn/no-unnecessary-array-flat-map': [2],
'unicorn/no-unnecessary-array-splice-count': [2], 'unicorn/no-unnecessary-array-splice-count': [2],
@@ -874,7 +854,6 @@ export default defineConfig([
'unicorn/no-unnecessary-fetch-options': [0], 'unicorn/no-unnecessary-fetch-options': [0],
'unicorn/no-unnecessary-global-this': [0], 'unicorn/no-unnecessary-global-this': [0],
'unicorn/no-unnecessary-nested-ternary': [2], 'unicorn/no-unnecessary-nested-ternary': [2],
'unicorn/no-unnecessary-parameters': [0],
'unicorn/no-unnecessary-polyfills': [2], 'unicorn/no-unnecessary-polyfills': [2],
'unicorn/no-unnecessary-slice-end': [2], 'unicorn/no-unnecessary-slice-end': [2],
'unicorn/no-unnecessary-splice': [2], 'unicorn/no-unnecessary-splice': [2],
@@ -886,15 +865,14 @@ export default defineConfig([
'unicorn/no-unreadable-object-destructuring': [0], 'unicorn/no-unreadable-object-destructuring': [0],
'unicorn/no-unsafe-buffer-conversion': [2], 'unicorn/no-unsafe-buffer-conversion': [2],
'unicorn/no-unsafe-dom-html': [0], 'unicorn/no-unsafe-dom-html': [0],
'unicorn/no-unsafe-json-serialization': [2],
'unicorn/no-unsafe-promise-all-settled-values': [2], 'unicorn/no-unsafe-promise-all-settled-values': [2],
'unicorn/no-unsafe-property-key': [0], 'unicorn/no-unsafe-property-key': [0],
'unicorn/no-unsafe-sqlite-interpolation': [2], 'unicorn/no-unsafe-sqlite-interpolation': [2],
'unicorn/no-unsafe-string-replacement': [2], 'unicorn/no-unsafe-string-replacement': [2],
'unicorn/no-unscoped-css-nesting-selector': [0],
'unicorn/no-unused-builtin-method-return': [2], 'unicorn/no-unused-builtin-method-return': [2],
'unicorn/no-unused-iterator-helper': [2], 'unicorn/no-unused-iterator-helper': [2],
'unicorn/no-unused-properties': [2], 'unicorn/no-unused-properties': [2],
'unicorn/no-url-in-search-params': [2],
'unicorn/no-useless-boolean-cast': [2], 'unicorn/no-useless-boolean-cast': [2],
'unicorn/no-useless-coercion': [2], 'unicorn/no-useless-coercion': [2],
'unicorn/no-useless-collection-argument': [2], 'unicorn/no-useless-collection-argument': [2],
@@ -931,12 +909,12 @@ export default defineConfig([
'unicorn/prefer-array-find': [0], // handled by @typescript-eslint/prefer-find 'unicorn/prefer-array-find': [0], // handled by @typescript-eslint/prefer-find
'unicorn/prefer-array-flat': [2], 'unicorn/prefer-array-flat': [2],
'unicorn/prefer-array-flat-map': [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-map': [2],
'unicorn/prefer-array-from-range': [2], 'unicorn/prefer-array-from-range': [2],
'unicorn/prefer-array-index-of': [2], 'unicorn/prefer-array-index-of': [2],
'unicorn/prefer-array-iterable-methods': [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-slice': [2],
'unicorn/prefer-array-some': [2], 'unicorn/prefer-array-some': [2],
'unicorn/prefer-at': [0], 'unicorn/prefer-at': [0],
@@ -953,7 +931,7 @@ export default defineConfig([
'unicorn/prefer-date-now': [2], 'unicorn/prefer-date-now': [2],
'unicorn/prefer-default-parameters': [0], 'unicorn/prefer-default-parameters': [0],
'unicorn/prefer-direct-iteration': [2], '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-append': [2],
'unicorn/prefer-dom-node-html-methods': [0], 'unicorn/prefer-dom-node-html-methods': [0],
'unicorn/prefer-dom-node-remove': [2], 'unicorn/prefer-dom-node-remove': [2],
@@ -962,14 +940,14 @@ export default defineConfig([
'unicorn/prefer-early-return': [0], 'unicorn/prefer-early-return': [0],
'unicorn/prefer-else-if': [2], 'unicorn/prefer-else-if': [2],
'unicorn/prefer-error-is-error': [0], 'unicorn/prefer-error-is-error': [0],
'unicorn/prefer-escaped-irregular-whitespace': [2],
'unicorn/prefer-event-target': [2], 'unicorn/prefer-event-target': [2],
'unicorn/prefer-explicit-viewport-units': [0], // only applies to css language
'unicorn/prefer-export-from': [0], 'unicorn/prefer-export-from': [0],
'unicorn/prefer-flat-math-min-max': [2], '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-number-constants': [2],
'unicorn/prefer-global-this': [0], '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-has-check': [2],
'unicorn/prefer-hoisting-branch-code': [2], 'unicorn/prefer-hoisting-branch-code': [2],
'unicorn/prefer-https': [0], // false-positives on namespace and schema URIs 'unicorn/prefer-https': [0], // false-positives on namespace and schema URIs
@@ -980,12 +958,11 @@ export default defineConfig([
'unicorn/prefer-iterable-in-constructor': [2], 'unicorn/prefer-iterable-in-constructor': [2],
'unicorn/prefer-iterator-concat': [0], // too opinionated 'unicorn/prefer-iterator-concat': [0], // too opinionated
'unicorn/prefer-iterator-helpers': [0], '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-to-array-at-end': [2],
'unicorn/prefer-iterator-zip': [0], 'unicorn/prefer-iterator-zip': [0],
'unicorn/prefer-json-import': [0], 'unicorn/prefer-json-import': [0],
'unicorn/prefer-keyboard-event-key': [2], 'unicorn/prefer-keyboard-event-key': [2],
'unicorn/prefer-literal-ascii': [2],
'unicorn/prefer-location-assign': [2], 'unicorn/prefer-location-assign': [2],
'unicorn/prefer-logical-operator-over-ternary': [0], 'unicorn/prefer-logical-operator-over-ternary': [0],
'unicorn/prefer-map-from-entries': [0], 'unicorn/prefer-map-from-entries': [0],
@@ -993,6 +970,7 @@ export default defineConfig([
'unicorn/prefer-math-constants': [2], 'unicorn/prefer-math-constants': [2],
'unicorn/prefer-math-min-max': [2], 'unicorn/prefer-math-min-max': [2],
'unicorn/prefer-math-trunc': [2], 'unicorn/prefer-math-trunc': [2],
'unicorn/prefer-media-feature-range-syntax': [0],
'unicorn/prefer-minimal-ternary': [0], 'unicorn/prefer-minimal-ternary': [0],
'unicorn/prefer-modern-dom-apis': [0], 'unicorn/prefer-modern-dom-apis': [0],
'unicorn/prefer-modern-math-apis': [2], 'unicorn/prefer-modern-math-apis': [2],
@@ -1011,9 +989,8 @@ export default defineConfig([
'unicorn/prefer-optional-catch-binding': [2], 'unicorn/prefer-optional-catch-binding': [2],
'unicorn/prefer-path2d': [2], 'unicorn/prefer-path2d': [2],
'unicorn/prefer-private-class-fields': [0], 'unicorn/prefer-private-class-fields': [0],
'unicorn/prefer-promise-static-methods': [2], 'unicorn/prefer-promise-try': [2],
'unicorn/prefer-promise-try': [0], // Promise.try requires ES2025 'unicorn/prefer-promise-with-resolvers': [2],
'unicorn/prefer-promise-with-resolvers': [0], // Promise.withResolvers requires ES2024
'unicorn/prefer-prototype-methods': [2], 'unicorn/prefer-prototype-methods': [2],
'unicorn/prefer-query-selector': [2], 'unicorn/prefer-query-selector': [2],
'unicorn/prefer-queue-microtask': [2], 'unicorn/prefer-queue-microtask': [2],
@@ -1026,7 +1003,6 @@ export default defineConfig([
'unicorn/prefer-set-methods': [0], 'unicorn/prefer-set-methods': [0],
'unicorn/prefer-set-size': [2], 'unicorn/prefer-set-size': [2],
'unicorn/prefer-short-arrow-method': [2], 'unicorn/prefer-short-arrow-method': [2],
'unicorn/prefer-short-escape-sequences': [2],
'unicorn/prefer-simple-condition-first': [0], 'unicorn/prefer-simple-condition-first': [0],
'unicorn/prefer-simple-sort-comparator': [2], 'unicorn/prefer-simple-sort-comparator': [2],
'unicorn/prefer-simplified-conditions': [2], 'unicorn/prefer-simplified-conditions': [2],
@@ -1048,7 +1024,7 @@ export default defineConfig([
'unicorn/prefer-structured-clone': [2], 'unicorn/prefer-structured-clone': [2],
'unicorn/prefer-switch': [0], 'unicorn/prefer-switch': [0],
'unicorn/prefer-temporal': [0], 'unicorn/prefer-temporal': [0],
'unicorn/prefer-temporal-conversion': [0], // Temporal requires ES2027 'unicorn/prefer-temporal-conversion': [2],
'unicorn/prefer-ternary': [0], 'unicorn/prefer-ternary': [0],
'unicorn/prefer-then-catch': [2], 'unicorn/prefer-then-catch': [2],
'unicorn/prefer-toggle-attribute': [2], 'unicorn/prefer-toggle-attribute': [2],
@@ -1074,7 +1050,6 @@ export default defineConfig([
'unicorn/require-passive-events': [2], 'unicorn/require-passive-events': [2],
'unicorn/require-post-message-target-origin': [0], 'unicorn/require-post-message-target-origin': [0],
'unicorn/require-proxy-trap-boolean-return': [2], 'unicorn/require-proxy-trap-boolean-return': [2],
'unicorn/require-text-decoder-streaming': [2],
'unicorn/single-line-block-comment-style': [0], 'unicorn/single-line-block-comment-style': [0],
'unicorn/string-content': [0], 'unicorn/string-content': [0],
'unicorn/switch-case-braces': [0], 'unicorn/switch-case-braces': [0],
@@ -1121,13 +1096,17 @@ export default defineConfig([
}, },
}, },
{ {
files: vueExts.map((ext) => `**/*.${ext}`), files: ['**/*.vue'],
languageOptions: {
parserOptions: {
parser: '@typescript-eslint/parser',
},
},
extends: [ extends: [
vue.configs['flat/recommended'], vue.configs['flat/recommended'],
vueScopedCss.configs.recommended, vueScopedCss.configs.recommended,
], ],
rules: { 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/attributes-order': [0],
'vue/html-closing-bracket-spacing': [2, {startTag: 'never', endTag: 'never', selfClosingTag: 'never'}], 'vue/html-closing-bracket-spacing': [2, {startTag: 'never', endTag: 'never', selfClosingTag: 'never'}],
'vue/max-attributes-per-line': [0], 'vue/max-attributes-per-line': [0],
@@ -1240,9 +1219,6 @@ export default defineConfig([
{ {
files: ['*', 'tools/**/*', 'tests/**/*'], files: ['*', 'tools/**/*', 'tests/**/*'],
languageOptions: {globals: globals.nodeBuiltin}, languageOptions: {globals: globals.nodeBuiltin},
rules: {
'gitea/unescaped-html-literal': [0],
},
}, },
{ {
files: ['web_src/**/*'], files: ['web_src/**/*'],
+51 -26
View File
@@ -2,36 +2,40 @@ module gitea.dev
go 1.27 go 1.27
toolchain go1.27.3 toolchain go1.27.1
require ( require (
connectrpc.com/connect v1.21.0 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/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
gitea.dev/actionslib v1.4.0 gitea.dev/actionslib v1.2.1
gitea.dev/sdk v1.3.0 gitea.dev/sdk v1.2.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/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
github.com/Azure/go-ntlmssp v0.1.1 github.com/Azure/go-ntlmssp v0.1.1
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 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/PuerkitoBio/goquery v1.13.0
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0 github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0
github.com/alecthomas/chroma/v2 v2.27.0 github.com/alecthomas/chroma/v2 v2.27.0
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.6.1 github.com/blevesearch/bleve/v2 v2.6.1
github.com/bohde/codel v0.2.0 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/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/charmbracelet/git-lfs-transfer v0.1.1-0.20260812203852-971c0284dc33
github.com/chi-middleware/proxy v1.1.1 github.com/chi-middleware/proxy v1.1.1
github.com/coder/websocket v1.8.15 github.com/coder/websocket v1.8.15
github.com/dlclark/regexp2/v2 v2.8.3 github.com/dlclark/regexp2/v2 v2.8.0
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
github.com/dustin/go-humanize v1.1.0 github.com/dustin/go-humanize v1.1.0
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5 github.com/editorconfig/editorconfig-core-go/v2 v2.6.5
github.com/emersion/go-imap v1.2.1 github.com/emersion/go-imap v1.2.1
github.com/emirpasic/gods v1.18.1
github.com/felixge/fgprof v0.9.5 github.com/felixge/fgprof v0.9.5
github.com/fsnotify/fsnotify v1.10.1 github.com/fsnotify/fsnotify v1.10.1
github.com/getkin/kin-openapi v0.149.0 github.com/getkin/kin-openapi v0.149.0
@@ -39,6 +43,8 @@ require (
github.com/go-chi/cors v1.2.2 github.com/go-chi/cors v1.2.2
github.com/go-co-op/gocron/v2 v2.22.0 github.com/go-co-op/gocron/v2 v2.22.0
github.com/go-enry/go-enry/v2 v2.9.6 github.com/go-enry/go-enry/v2 v2.9.6
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-ldap/ldap/v3 v3.4.14
github.com/go-redsync/redsync/v4 v4.18.0 github.com/go-redsync/redsync/v4 v4.18.0
github.com/go-sql-driver/mysql v1.10.1 github.com/go-sql-driver/mysql v1.10.1
@@ -53,10 +59,10 @@ require (
github.com/gorilla/sessions v1.4.0 github.com/gorilla/sessions v1.4.0
github.com/hashicorp/go-version v1.9.0 github.com/hashicorp/go-version v1.9.0
github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/huandu/xstrings v1.6.2 github.com/huandu/xstrings v1.6.1
github.com/jhillyerd/enmime/v2 v2.5.0 github.com/jhillyerd/enmime/v2 v2.5.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.20.1 github.com/klauspost/compress v1.20.0
github.com/lib/pq v1.12.3 github.com/lib/pq v1.12.3
github.com/markbates/goth v1.82.0 github.com/markbates/goth v1.82.0
github.com/mattn/go-isatty v0.0.24 github.com/mattn/go-isatty v0.0.24
@@ -73,33 +79,34 @@ require (
github.com/pquerna/otp v1.5.0 github.com/pquerna/otp v1.5.0
github.com/prometheus/client_golang v1.24.1 github.com/prometheus/client_golang v1.24.1
github.com/quasoft/websspi v1.1.2 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/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 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/sergi/go-diff v1.4.0
github.com/stretchr/testify v1.12.1 github.com/stretchr/testify v1.12.1
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
github.com/ulikunitz/xz v0.5.17 github.com/ulikunitz/xz v0.5.17
github.com/urfave/cli-docs/v3 v3.1.0 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/wneessen/go-mail v0.8.1
github.com/yohcop/openid-go v1.0.1 github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.8.6 github.com/yuin/goldmark v1.8.6
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gitlab.com/gitlab-org/api/client-go/v3 v3.16.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 go.yaml.in/yaml/v4 v4.0.0-rc.6
golang.org/x/crypto v0.57.0 golang.org/x/crypto v0.57.0
golang.org/x/image v0.46.0 golang.org/x/image v0.46.0
golang.org/x/mod v0.41.0 golang.org/x/mod v0.41.0
golang.org/x/net v0.60.0 golang.org/x/net v0.59.0
golang.org/x/oauth2 v0.37.0 golang.org/x/oauth2 v0.37.0
golang.org/x/sync v0.24.0 golang.org/x/sync v0.23.0
golang.org/x/sys v0.49.0 golang.org/x/sys v0.48.0
golang.org/x/text v0.42.0 golang.org/x/text v0.42.0
google.golang.org/grpc v1.84.0 google.golang.org/grpc v1.84.0
google.golang.org/protobuf v1.36.12 google.golang.org/protobuf v1.36.12
gopkg.in/ini.v1 v1.67.3 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 mvdan.cc/xurls/v2 v2.6.0
xorm.io/builder v0.3.13 xorm.io/builder v0.3.13
xorm.io/xorm v1.4.3 xorm.io/xorm v1.4.3
@@ -107,7 +114,10 @@ require (
require ( require (
cloud.google.com/go/compute/metadata v0.9.0 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.2.0 // 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/RoaringBitmap/roaring/v2 v2.18.2 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect github.com/STARRY-S/zip v0.2.3 // indirect
github.com/andybalholm/brotli v1.2.1 // indirect github.com/andybalholm/brotli v1.2.1 // indirect
@@ -133,18 +143,23 @@ require (
github.com/blevesearch/zapx/v15 v15.4.3 // indirect github.com/blevesearch/zapx/v15 v15.4.3 // indirect
github.com/blevesearch/zapx/v16 v16.3.4 // indirect github.com/blevesearch/zapx/v16 v16.3.4 // indirect
github.com/blevesearch/zapx/v17 v17.2.3 // indirect github.com/blevesearch/zapx/v17 v17.2.3 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.2 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.4 // indirect github.com/bodgit/sevenzip v1.6.4 // indirect
github.com/bodgit/windows v1.0.1 // indirect github.com/bodgit/windows v1.0.1 // indirect
github.com/boombuler/barcode v1.1.0 // 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/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.6.3 // 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/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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
@@ -153,6 +168,7 @@ require (
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect 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-asn1-ber/asn1-ber v1.5.8 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // 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/jsonpointer v0.23.1 // indirect
github.com/go-openapi/swag/jsonname v0.26.1 // indirect github.com/go-openapi/swag/jsonname v0.26.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
@@ -160,7 +176,9 @@ require (
github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-json v0.10.6 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // 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/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-querystring v1.2.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
@@ -170,8 +188,10 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/inbucket/html2text v1.0.0 // 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/jonboulle/clockwork v0.5.0 // indirect
github.com/json-iterator/go v1.1.12 // 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/cpuid/v2 v2.4.0 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect github.com/klauspost/crc32 v1.3.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect github.com/klauspost/pgzip v1.2.6 // indirect
@@ -179,8 +199,8 @@ require (
github.com/markbates/going v1.0.3 // indirect github.com/markbates/going v1.0.3 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-runewidth v0.0.24 // indirect github.com/mattn/go-runewidth v0.0.24 // indirect
github.com/mholt/acmez/v3 v3.1.7 // indirect github.com/mholt/acmez/v3 v3.1.6 // indirect
github.com/miekg/dns v1.1.73 // indirect github.com/miekg/dns v1.1.72 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/crc64nvme v1.1.1 // indirect github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
@@ -199,9 +219,9 @@ require (
github.com/olekukonko/ll v0.1.8 // indirect github.com/olekukonko/ll v0.1.8 // indirect
github.com/olekukonko/tablewriter v1.1.4 // indirect github.com/olekukonko/tablewriter v1.1.4 // indirect
github.com/onsi/ginkgo v1.16.5 // 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/philhofer/fwd v1.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.27 // 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/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
@@ -212,25 +232,30 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.10.2 // 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/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect github.com/spf13/afero v1.15.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/stangelandcl/ppmd v0.1.1 // indirect github.com/stangelandcl/ppmd v0.1.1 // indirect
github.com/tinylib/msgp v1.6.4 // 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/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/blake3 v0.2.4 // indirect
github.com/zeebo/xxh3 v1.1.0 // indirect github.com/zeebo/xxh3 v1.1.0 // indirect
go.etcd.io/bbolt v1.4.3 // 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/multierr v1.11.0 // indirect
go.uber.org/zap v1.28.0 // indirect go.uber.org/zap v1.28.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect
go4.org v0.0.0-20260112195520-a5071408f32f // indirect go4.org v0.0.0-20260112195520-a5071408f32f // indirect
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
golang.org/x/time v0.15.0 // indirect golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.49.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // 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/mathutil v1.7.1 // indirect
modernc.org/memory v1.12.1 // indirect modernc.org/memory v1.12.1 // indirect
) )
+172 -52
View File
@@ -4,20 +4,30 @@ code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=
code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM= code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
connectrpc.com/connect v1.21.0 h1:LhqSJt7jHf5NJBo9Jq/t/9FjcYAideif0mg+qe2jCUs= connectrpc.com/connect v1.21.0 h1:LhqSJt7jHf5NJBo9Jq/t/9FjcYAideif0mg+qe2jCUs=
connectrpc.com/connect v1.21.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4= 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 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= 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 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw=
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY= 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 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU= 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 h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitea.dev/actionslib v1.4.0 h1:k31smj22tnUZrrM5kkntXN8lKAe7dkoTEoiP1Fk5KJc= gitea.dev/actionslib v1.2.1 h1:GL//K/0zIZV6h1OOksv1awvFVg0rg7fug2xWcG7mkG0=
gitea.dev/actionslib v1.4.0/go.mod h1:svCefEsavx4jmn8vJ4wTG7Q0KBcGfrFmGUjrhxuNUAQ= 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.2.0 h1:avRtJl/nKCGispgSalo9czoZM9Rto1awnE0caNAoXGo=
gitea.dev/sdk v1.3.0/go.mod h1:85EBqSAqUn1KlFCk40y3sTjWjBsB/KouL3slGXMA/2o= gitea.dev/sdk v1.2.0/go.mod h1:rfh5oNdIK24cbCREwIn1tqWKQW+IICXFGWJyebuOAOE=
github.com/42wim/httpsig v1.3.0 h1:HK2bieeZ99X9IvOoc1ST6yrhLek0f4MqlEGES2JCP3g= github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=
github.com/42wim/httpsig v1.3.0/go.mod h1:lgC3Yrvp4lxi1odEF1ge3kQBu/VM+SRcZQu5YwiR3dU= 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 h1:3Fcz1QzlS7Jv4FT2KI3cHNSZL+KPN3dXxurn9f3YL/Y=
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432/go.mod h1:BLWe6Nol65Xxncvaw07yYMxiyk02We1lBrbRYsMYsjE= 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= 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/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 h1:Nljr4q1GRA/5vCrMONS+g4u4LRHNgOXVSh3O43J2CnI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI= 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 h1:3JhDl+JysaO8nhNU1XMaw35VSGjV4IEQAefaG4Lyok4=
github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347/go.mod h1:2ErI0aycD43Ufr6CFK5lT/NrHGmoZuVbn1nlPThw69o= 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.0 h1:sKmuvjOgsnrtpMvZ+84MCnTJCpjxZ1qCFn076lz1yT0=
github.com/ProtonMail/go-crypto v1.5.2/go.mod h1:/RaSu30DaKO4RY+XdV/ACcCcZkGr7AhUIduq5sjzzCo= 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 h1:mqHbjD7Jmnul4DTR24LKTjo1uUmHUh072kteGV+xpFM=
github.com/PuerkitoBio/goquery v1.13.0/go.mod h1:Hip5mdBL8K2wEGKJdr27sRaNwIdDajmCwB/ExUPwW+g= 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= 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/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 h1:vM2lgh0Vru9Vwyfm4cQqWP2HHMW0u0+2PAW7Q38Qufg=
github.com/andybalholm/cascadia v1.3.4/go.mod h1:BLRmbRjpEtNKieZOCCvYj4RqN+KRA41GBe/5O+G93kM= 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 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 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/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 h1:UYYJPAt5b2tVxldx5h0jmv23RMsg8/UZKFVya7v92po=
github.com/blevesearch/zapx/v17 v17.2.3/go.mod h1:r7mb4QWbDQSkbAnOjCb9iCfkcrzajB4yBdJpuBIo/fE= github.com/blevesearch/zapx/v17 v17.2.3/go.mod h1:r7mb4QWbDQSkbAnOjCb9iCfkcrzajB4yBdJpuBIo/fE=
github.com/bmatcuk/doublestar/v4 v4.10.2 h1:eF7W7HWKg3z9NrWV9pTLnNeoXaqq3Tq9DNKXVMfoCnw= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= 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/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 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
@@ -128,10 +147,10 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 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 h1:gs9WhKUwxK27YHf3hijj4tG4wuDLfCANYbRPd5ltMdI=
github.com/buildkite/terminal-to-html/v3 v3.17.1/go.mod h1:0pv+Z75FC+NvIju2o+Cw+YHIDkueeudIoeyfVm8i8GQ= 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.4 h1:8eIXh0HC3MsGnNo8One+BCxMGTbe5zb/oz+2KsxBFQg=
github.com/caddyserver/certmagic v0.25.6/go.mod h1:xq6cRNimqW+Tv91XNc5lNHs2XYTsLhQhYiH01H4AySs= github.com/caddyserver/certmagic v0.25.4/go.mod h1:YVs43D5+H/Dckt4bTga1KSO/xYfFBfVZainGDywYPAA=
github.com/caddyserver/zerossl v0.1.6 h1:1yrhTx5DWi43wOJPQ+Y4mVl++EPmSqqT/4REW//lsas= github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE=
github.com/caddyserver/zerossl v0.1.6/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= 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 h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8= 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 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -154,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/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 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= 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 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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.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.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -168,8 +196,8 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8= github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2/v2 v2.8.3 h1:4iBHVtmyAsQOOthVo+0v0RxktjLPDOTKx9Nuhf+PpD0= github.com/dlclark/regexp2/v2 v2.8.0 h1:CekDhPLGfm+GAJmPVFIG+5dqMIQPkyHJll7BbdneDfw=
github.com/dlclark/regexp2/v2 v2.8.3/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= 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 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= 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= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
@@ -177,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/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 h1:MTcuJQkIFRLfNn9FfAvdO0p3FhNyaCf+IGTwy0TxX6E=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.5/go.mod h1:SizrS3EM1vFF0v/JZlJ5LfK6tXhwln2823YZwNh/vBE= 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 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY= 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= github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
@@ -184,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 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= 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/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 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
@@ -198,6 +230,8 @@ github.com/getkin/kin-openapi v0.149.0 h1:ZbhmVJ4yq5RZDUsyP8lcBcGMsjsaTqXEFt6isd
github.com/getkin/kin-openapi v0.149.0/go.mod h1:1+BHDzstro+P5CKtPy1X4PfofnFgmRe6uvMy9+r9fKY= 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 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A= 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 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=
github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
@@ -211,6 +245,14 @@ 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-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 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4= 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 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs= github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=
@@ -255,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/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 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= 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.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/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -270,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/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 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=
github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw= 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.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.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -296,6 +343,8 @@ github.com/google/pprof v0.0.0-20260906184651-6331bc6350fe h1:QAinXoAFJdGQYztXn3
github.com/google/pprof v0.0.0-20260906184651-6331bc6350fe/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= 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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
@@ -327,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 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.6.2 h1:+X5X6N46b40cmDw7FFJFU6Eoq0yJS8lbYigT2EFau4c= github.com/huandu/xstrings v1.6.1 h1:yYdKSd6Sjv3fNZt9BDjl1FDsPNfQEaYi19L5LzK2JKs=
github.com/huandu/xstrings v1.6.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 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/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 h1:N5kza++4uBBDJ2Z3KUnTRyPNoBcW+YfOgNiNmNB+sgs=
github.com/inbucket/html2text v1.0.0/go.mod h1:5TrhXQKGU+LXurODaSm55Y9eXoPBRnYiOz4x2XfUoJU= 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 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= 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= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
@@ -351,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/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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 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.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ= github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= 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 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.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
@@ -376,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/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 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk= 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.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
github.com/letsencrypt/pebble/v2 v2.10.1/go.mod h1:KtYhQ4YTjT5MtoCZ6RTCXlbrrz6cKyXROCuTpIUDJFY= 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 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
@@ -397,16 +453,16 @@ github.com/mattn/go-sqlite3 v1.14.52 h1:wVbm2Qnf4OXkqhBTSPuCRZDRnxfbVrrmiCEroVdo
github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= 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 h1:Yx1aTY5jDgtbStPVkhJTDoLnZTy5sejQSPyjfNMy6e4=
github.com/meilisearch/meilisearch-go v0.36.3/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM= 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.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk=
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/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY=
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ= github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4= 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 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= 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 h1:FCgeBIK8um2+X4tbun6Q71N1KsfyCDPKY41e1yGVjSE=
github.com/microsoft/go-mssqldb v1.11.2/go.mod h1:CYgwG5AMXFojbjTg+GNP5G/y6uz1BhTyZaPqQWzkGnQ= 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.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ= 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 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= 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 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
@@ -470,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/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 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= 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 h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -489,8 +547,8 @@ github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= 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 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk= 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.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0=
github.com/redis/go-redis/v9 v9.23.0/go.mod h1:EWP2UUk+XDaI1s5nQB5mablZN6vccXlmBQTyfnF87D0= 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 h1:ZR41bgJcm7oRFb3aSDPrRhC0eonDSrPzjvvZvHIlNjM=
github.com/redis/rueidis v1.0.77/go.mod h1:L8mnCQJJaSNL6I4pIR6Rz732HTGS9vmuXm0yT9dRvjo= github.com/redis/rueidis v1.0.77/go.mod h1:L8mnCQJJaSNL6I4pIR6Rz732HTGS9vmuXm0yT9dRvjo=
github.com/redis/rueidis/rueidiscompat v1.0.77 h1:S1xiLQsFv8XQ46e0LelC8ihGqbDzVRocXXZLzVAeh64= github.com/redis/rueidis/rueidiscompat v1.0.77 h1:S1xiLQsFv8XQ46e0LelC8ihGqbDzVRocXXZLzVAeh64=
@@ -507,14 +565,24 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= 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/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.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 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 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 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 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 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q= 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 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU= github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
@@ -538,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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
@@ -550,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.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.17 h1:flR0y/x1hgM8EGV1AW3Xll6T413G0glV8UfBwR617V4= 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/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 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw=
github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to= 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.13.0 h1:Dr6jqMfIyyFsRVn7Nz5mqLsMY+ZMpfh3a0aMs+umPVY=
github.com/urfave/cli/v3 v3.14.0/go.mod h1:vXn6HxPNccJSzQr2QvwVncOKrgYGIHU0HY5h8B2nQj4= 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 h1:tVcncj02/QySVFw3zr/kXOzZcuFQqBNT6K+Rbgm/pcM=
github.com/wneessen/go-mail v0.8.1/go.mod h1:dWZ61zadzCIyvB4y1/YzC5O7MrbbzBfPkARmbosdf8w= 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 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 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 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js= github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
@@ -577,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/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 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
gitlab.com/gitlab-org/api/client-go/v3 v3.16.0 h1:LyxbaO5Pep+gREHptHoYDmYPs4dw9EzGTx6B29+kB7I= gitlab.com/gitlab-org/api/client-go/v3 v3.13.0 h1:CTUXvcL6OrGjYNafbws5C1lJwlXrxGJHWQGMF+hCdz0=
gitlab.com/gitlab-org/api/client-go/v3 v3.16.0/go.mod h1:975X4id//8WM4sQyh9kqODM3fHPHdIRb4Uag2+3GAo8= 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 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= 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.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= 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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
@@ -597,8 +672,8 @@ go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
go.yaml.in/yaml/v4 v4.0.0-rc.6.0.20261013184558-04dd63131525 h1:FtzHH3s777Nx50YcPAcodAPm1mNakc228nMYbr3f+90= go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4=
go.yaml.in/yaml/v4 v4.0.0-rc.6.0.20261013184558-04dd63131525/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
go4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH9QQjfb0Sw= go4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH9QQjfb0Sw=
go4.org v0.0.0-20260112195520-a5071408f32f/go.mod h1:ZRJnO5ZI4zAwMFp+dS1+V6J6MSyAowhRqAE+DPa1Xp0= go4.org v0.0.0-20260112195520-a5071408f32f/go.mod h1:ZRJnO5ZI4zAwMFp+dS1+V6J6MSyAowhRqAE+DPa1Xp0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -606,6 +681,11 @@ 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-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-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-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.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
@@ -614,6 +694,10 @@ 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/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.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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
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 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -622,10 +706,16 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 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-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-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.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.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= 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.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98= golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58= golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -633,30 +723,50 @@ 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-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-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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= 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.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-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-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-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-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-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-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-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-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-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-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.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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= 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.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.48.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-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.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.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.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -664,6 +774,11 @@ 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.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.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.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.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
@@ -672,8 +787,11 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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.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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0= 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.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -699,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/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 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 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.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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -708,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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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.2 h1:h6+9ciCnPKutf4I03CvheAvDLX7+IHlqR6Iy6J+cgd8=
modernc.org/cc/v4 v4.29.7/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= modernc.org/cc/v4 v4.29.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.36.1 h1:ZNIUZAryN0UgnJwtyxrdEzcFc3yD4Cu4AzjfPXsLsIE= modernc.org/ccgo/v4 v4.35.0 h1:F+TUsmw09QxLzmi3aeYYGxjAXarmZaKgj3mKQHNaA8w=
modernc.org/ccgo/v4 v4.36.1/go.mod h1:rrtGc2QkS239nYb/mQNuBMyjq3/y3ZXWbBjPoV3wqzA= 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 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
@@ -720,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/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 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.77.1 h1:Ct8j47QtiZ1Enj2DtFXQtUqrPCAjdCmPjtCuvrYQ0Hs= modernc.org/libc v1.75.7 h1:o3DTP9/0p9pKmY2WCKQaySW6wIiZhNM7wc2lUoyhfew=
modernc.org/libc v1.77.1/go.mod h1:87/pZ4L6nD1zqW4nItuS12YO7hN1igAah34xjnQo/W0= 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 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g= modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
@@ -730,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/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.60.1 h1:/blz53O951KWFOso4QQvEs/Fq6cDBKLtMVrYNSeJVKw= modernc.org/sqlite v1.59.0 h1:X1es1GpqBlS/5T+vbM4HLUdaa8OtQx468DF2vrx+38A=
modernc.org/sqlite v1.60.1/go.mod h1:1dIoEagfDE72QytD5scH1lxARtaUgKgHC/NuApA27r0= 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 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= 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 { 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) 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)
}
+1 -7
View File
@@ -431,15 +431,9 @@ func prepareMigrationTasks() []*migration {
newMigration(354, "Add Actions job queue indexes", v28.AddActionQueueIndexes), newMigration(354, "Add Actions job queue indexes", v28.AddActionQueueIndexes),
newMigration(355, "Add AutoMerge merged_commit_id column", v28.AddAutoMergeMergedCommitID), newMigration(355, "Add AutoMerge merged_commit_id column", v28.AddAutoMergeMergedCommitID),
// Gitea 28.0.0 ends at migration ID number 355 (database version 356) // 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(356, "Add index on action_run commit_sha", v29.AddActionRunCommitSHAIndex),
newMigration(357, "Normalize legacy team authorize values", v29.NormalizeLegacyTeamAuthorize), 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),
} }
return preparedMigrations 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 { func mainTest(m *testing.M) int {
testlogger.Init() testlogger.Init()
workPath, err := setting.PrepareIntegrationTestConfig() err := setting.PrepareIntegrationTestConfig()
if err != nil { if err != nil {
return testlogger.MainErrorf("Unable to prepare integration test config: %v", err) 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.Database.SlowQueryThreshold = 0
setting.LoadDBSetting() setting.LoadDBSetting()
setting.InitLoggersForTest() setting.InitLoggersForTest()
exitCode := m.Run() return m.Run()
if exitCode == 0 {
_ = os.RemoveAll(workPath)
}
return exitCode
} }
func MainTest(m *testing.M) { func MainTest(m *testing.M) {
+1 -1
View File
@@ -11,7 +11,7 @@ import (
// NormalizeLegacyTeamAuthorize sets leftover read/write authorize values to none. // 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). // 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. // to none preserves their existing team_unit-scoped access.
func NormalizeLegacyTeamAuthorize(_ context.Context, x base.EngineMigration) error { func NormalizeLegacyTeamAuthorize(_ context.Context, x base.EngineMigration) error {
// AccessModeNone=0, AccessModeRead=1, AccessModeWrite=2, AccessModeAdmin=3 // 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))
}
-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 return *cfg.MaxTokenPermissions
} }
// Default max is write for everything // 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 // ClampPermissions ensures that the given permissions don't exceed the maximum
+12 -16
View File
@@ -15,7 +15,6 @@ import (
"gitea.dev/models/db" "gitea.dev/models/db"
repo_model "gitea.dev/models/repo" repo_model "gitea.dev/models/repo"
user_model "gitea.dev/models/user" user_model "gitea.dev/models/user"
"gitea.dev/modules/container"
"gitea.dev/modules/git" "gitea.dev/modules/git"
"gitea.dev/modules/json" "gitea.dev/modules/json"
"gitea.dev/modules/log" "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) { 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 return runs, err
} }
@@ -409,27 +406,26 @@ func CancelPreviousJobsByRunConcurrency(ctx context.Context, attempt *ActionRunA
return nil, nil return nil, nil
} }
var jobsToCancel []*ActionRunJob
attempts, jobs, err := getConcurrencyEntriesToReplace(ctx, attempt.RepoID, attempt.ConcurrencyGroup, attempt.ConcurrencyCancel) attempts, jobs, err := getConcurrencyEntriesToReplace(ctx, attempt.RepoID, attempt.ConcurrencyGroup, attempt.ConcurrencyCancel)
if err != nil { if err != nil {
return nil, fmt.Errorf("find concurrent runs and jobs: %w", err) return nil, fmt.Errorf("find concurrent runs and jobs: %w", err)
} }
jobs = slices.DeleteFunc(jobs, func(job *ActionRunJob) bool { return job.RunID == attempt.RunID }) jobs = slices.DeleteFunc(jobs, func(job *ActionRunJob) bool { return job.RunID == attempt.RunID })
jobsToCancel = append(jobsToCancel, jobs...)
// cancel runs in the same concurrency group // cancel runs in the same concurrency group
attempts = slices.DeleteFunc(attempts, func(concurrentAttempt *ActionRunAttempt) bool { return concurrentAttempt.RunID == attempt.RunID }) for _, concurrentAttempt := range attempts {
attemptJobs, err := getRunJobsByAttempts(ctx, attempts) if concurrentAttempt.RunID == attempt.RunID {
if err != nil { continue
return nil, fmt.Errorf("find concurrent attempt jobs: %w", err)
} }
return CancelJobs(ctx, append(jobs, attemptJobs...), false) 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...)
} }
func getRunJobsByAttempts(ctx context.Context, attempts []*ActionRunAttempt) (jobs []*ActionRunJob, _ error) { return CancelJobs(ctx, jobsToCancel, false)
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
}
}
return jobs, nil
} }
+1 -1
View File
@@ -179,7 +179,7 @@ func UpdateRunAttempt(ctx context.Context, attempt *ActionRunAttempt, cols ...st
return err return err
} }
if run.LatestAttemptID != attempt.ID { 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 return nil
} }
+50 -47
View File
@@ -110,8 +110,6 @@ type ActionRunJob struct {
// ReusableWorkflowContent is the content of the reusable workflow specified by "uses:". // ReusableWorkflowContent is the content of the reusable workflow specified by "uses:".
// Only set when IsReusableCaller is true. // Only set when IsReusableCaller is true.
ReusableWorkflowContent []byte `xorm:"LONGBLOB"` 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: // CallSecrets encodes the reusable workflow caller's "secrets:" section:
// - "" : no "secrets:" section (children only see auto-generated tokens). // - "" : no "secrets:" section (children only see auto-generated tokens).
// - "inherit" : the caller wrote "secrets: inherit". // - "inherit" : the caller wrote "secrets: inherit".
@@ -273,24 +271,6 @@ func GetLatestAttemptJobsByRun(ctx context.Context, run *ActionRun) (ActionJobLi
return jobs, nil 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. // GetAllRunJobsByRepoAndRunID returns all jobs for a run across all attempts.
func GetAllRunJobsByRepoAndRunID(ctx context.Context, repoID, runID int64) (ActionJobList, error) { func GetAllRunJobsByRepoAndRunID(ctx context.Context, repoID, runID int64) (ActionJobList, error) {
var jobs []*ActionRunJob var jobs []*ActionRunJob
@@ -705,18 +685,43 @@ func hasFailFastMatrixFailure(jobs []*ActionRunJob) bool {
// It's useful when a new run is triggered, and all previous runs needn't be continued anymore. // 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) { 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 // 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, RepoID: repoID,
Ref: ref, Ref: ref,
WorkflowID: workflowID, WorkflowID: workflowID,
TriggerEvent: event, TriggerEvent: event,
Status: []Status{StatusRunning, StatusWaiting, StatusBlocked, StatusCancelling}, Status: []Status{StatusRunning, StatusWaiting, StatusBlocked, StatusCancelling},
}.ToConds()) })
var jobs []*ActionRunJob if err != nil {
if err := db.GetEngine(ctx).Where(builder.In("run_id", runIDs)).Find(&jobs); err != nil {
return nil, err 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. // GetAncestorCallerIDs returns the IDs of the reusable workflow callers the job is nested in.
@@ -733,7 +738,7 @@ func GetAncestorCallerIDs(ctx context.Context, job *ActionRunJob) (container.Set
return ids, nil 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 == "" { if job.RawConcurrency == "" {
return nil, nil return nil, nil
} }
@@ -753,14 +758,21 @@ func CancelPreviousJobsByJobConcurrency(ctx context.Context, job *ActionRunJob)
return nil, err return nil, err
} }
jobs = slices.DeleteFunc(jobs, func(j *ActionRunJob) bool { return j.ID == job.ID || callerIDs.Contains(j.ID) }) 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 // cancel runs in the same concurrency group
attempts = slices.DeleteFunc(attempts, func(attempt *ActionRunAttempt) bool { return attempt.ID == job.RunAttemptID }) for _, attempt := range attempts {
attemptJobs, err := getRunJobsByAttempts(ctx, attempts) if attempt.ID == job.RunAttemptID {
if err != nil { continue
return nil, fmt.Errorf("find concurrent attempt jobs: %w", err)
} }
return CancelJobs(ctx, append(jobs, attemptJobs...), false) 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, jobsToCancel, false)
} }
// CancelJobs cancels every cancellable job it is given, force skipping the graceful cancelling // CancelJobs cancels every cancellable job it is given, force skipping the graceful cancelling
@@ -841,34 +853,25 @@ func cancelReusableCaller(ctx context.Context, caller *ActionRunJob, force bool)
} }
// Cancel descendants deepest-first, then the caller: a caller's status is aggregated from its children, // Cancel descendants deepest-first, then the caller: a caller's status is aggregated from its children,
// so each child must be cancelled before its parent caller is re-read. // so each child must reach its final state before its parent caller is re-aggregated.
// A child's ID always exceeds its parent's, so descending ID is a valid deepest-first order. // A child's ID always exceeds its parent's, so descending ID is a valid deepest-first order.
descendants := CollectAllDescendantJobs(caller, attemptJobs) descendants := CollectAllDescendantJobs(caller, attemptJobs)
slices.SortFunc(descendants, func(a, b *ActionRunJob) int { return cmp.Compare(b.ID, a.ID) }) slices.SortFunc(descendants, func(a, b *ActionRunJob) int { return cmp.Compare(b.ID, a.ID) })
callersWithChildren := make(container.Set[int64])
for _, d := range descendants {
callersWithChildren.Add(d.ParentJobID)
}
for _, job := range append(descendants, caller) { for _, c := range descendants {
if !callersWithChildren.Contains(job.ID) { cancelled, err := cancelOneJob(ctx, c, force)
cancelled, err := cancelOneJob(ctx, job, force)
if err != nil { if err != nil {
return cancelledJobs, err return cancelledJobs, err
} }
if cancelled != nil { if cancelled != nil {
cancelledJobs = append(cancelledJobs, cancelled) cancelledJobs = append(cancelledJobs, cancelled)
} }
continue
} }
// the job is a caller and its children's cascade already re-aggregated it
reloaded, err := GetRunJobByRunAndID(ctx, job.RunID, job.ID) if c, err := cancelOneJob(ctx, caller, force); err != nil {
if err != nil {
return cancelledJobs, err return cancelledJobs, err
} } else if c != nil {
if reloaded.Status != job.Status { cancelledJobs = append(cancelledJobs, c)
cancelledJobs = append(cancelledJobs, reloaded)
}
} }
return cancelledJobs, nil return cancelledJobs, nil
} }
-115
View File
@@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"testing" "testing"
runnerv1 "gitea.dev/actionslib/runner/v1"
"gitea.dev/models/db" "gitea.dev/models/db"
"gitea.dev/models/unittest" "gitea.dev/models/unittest"
"gitea.dev/modules/timeutil" "gitea.dev/modules/timeutil"
@@ -398,117 +397,3 @@ func TestForceCancelJobs(t *testing.T) {
assert.Equal(t, StatusCancelled, callerAfter.Status) 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)
}
func TestBatchLoadAndCancelRunJobs(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.LatestAttemptID = latestAttempt.ID
require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id"))
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])
}
attempts, err := runs.GetLatestAttempts(ctx)
require.NoError(t, err)
require.Len(t, attempts, 1)
assert.EqualValues(t, 2, attempts[latestAttempt.ID].TriggerUser.ID)
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)
}
-17
View File
@@ -5,8 +5,6 @@ package actions
import ( import (
"context" "context"
"maps"
"slices"
"gitea.dev/models/db" "gitea.dev/models/db"
repo_model "gitea.dev/models/repo" repo_model "gitea.dev/models/repo"
@@ -44,21 +42,6 @@ func (runs RunList) LoadTriggerUser(ctx context.Context) error {
return nil 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 { func (runs RunList) LoadRepos(ctx context.Context) error {
repoIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) { repoIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
return run.RepoID, run.Repo == nil 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" // 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 // See https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idruns-on
func (r *ActionRunner) CanMatchLabels(jobRunsOn []string) bool { 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() { func init() {
db.RegisterModel(&ActionRunner{}) 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 // FindRunnerOptions
// ownerID == 0 and repoID == 0 means any runner including global runners // ownerID == 0 and repoID == 0 means any runner including global runners
// repoID != 0 and WithAvailable == false means any runner for the given repo // repoID != 0 and WithAvailable == false means any runner for the given repo
-1
View File
@@ -86,5 +86,4 @@ func TestCanMatchLabelsCaseInsensitive(t *testing.T) {
runner := &ActionRunner{AgentLabels: []string{"self-hosted", "Linux", "X64"}} runner := &ActionRunner{AgentLabels: []string{"self-hosted", "Linux", "X64"}}
assert.True(t, runner.CanMatchLabels([]string{"SELF-HOSTED", "linux"})) assert.True(t, runner.CanMatchLabels([]string{"SELF-HOSTED", "linux"}))
assert.False(t, runner.CanMatchLabels([]string{"linux", "arm64"})) assert.False(t, runner.CanMatchLabels([]string{"linux", "arm64"}))
assert.False(t, runner.CanMatchLabels(nil))
} }
-10
View File
@@ -5,7 +5,6 @@ package actions
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"gitea.dev/models/db" "gitea.dev/models/db"
@@ -120,12 +119,3 @@ func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRu
} }
return &runnerToken, nil 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
}
+3 -4
View File
@@ -11,7 +11,6 @@ import (
"gitea.dev/models/db" "gitea.dev/models/db"
repo_model "gitea.dev/models/repo" repo_model "gitea.dev/models/repo"
user_model "gitea.dev/models/user" user_model "gitea.dev/models/user"
"gitea.dev/modules/git"
"gitea.dev/modules/timeutil" "gitea.dev/modules/timeutil"
"gitea.dev/modules/util" "gitea.dev/modules/util"
webhook_module "gitea.dev/modules/webhook" webhook_module "gitea.dev/modules/webhook"
@@ -105,18 +104,18 @@ func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) ([
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks // 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 // 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 { 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 // cancel running cron jobs of this repository and delete old schedules
jobs, err := CancelPreviousJobs( jobs, err := CancelPreviousJobs(
ctx, ctx,
repo.ID, repo.ID,
git.RefNameFromBranch(repo.DefaultBranch).String(), repo.DefaultBranch,
"", "",
webhook_module.HookEventSchedule, webhook_module.HookEventSchedule,
) )
if err != nil { if err != nil {
return jobs, fmt.Errorf("CleanRepoScheduleTasks: %w", err) return jobs, fmt.Errorf("CancelPreviousJobs: %v", err)
} }
return jobs, nil return jobs, nil
} }
+4
View File
@@ -59,6 +59,10 @@ func (s Status) HasRun() bool {
return s.In(StatusSuccess, StatusFailure) return s.In(StatusSuccess, StatusFailure)
} }
func (s Status) IsUnknown() bool {
return s == StatusUnknown
}
func (s Status) IsSuccess() bool { func (s Status) IsSuccess() bool {
return s == StatusSuccess return s == StatusSuccess
} }
+24 -16
View File
@@ -7,7 +7,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"slices"
"strings" "strings"
"time" "time"
@@ -237,13 +236,13 @@ func GetRunningTaskByToken(ctx context.Context, token string) (*ActionTask, erro
func makeTaskStepDisplayName(step *jobparser.Step, limit int) (name string) { func makeTaskStepDisplayName(step *jobparser.Step, limit int) (name string) {
if step.Name != "" { if step.Name != "" {
name = step.Name // the step has an explicit name name = string(step.Name) // the step has an explicit name
} else { } else {
// for unnamed step, its "String()" method tries to get a display name by its "name", "uses", // for unnamed step, its "String()" method tries to get a display name by its "name", "uses",
// "run" or "id" (last fallback), we add the "Run " prefix for unnamed steps for better display // "run" or "id" (last fallback), we add the "Run " prefix for unnamed steps for better display
// for multi-line "run" scripts, only use the first line to match GitHub's behavior // for multi-line "run" scripts, only use the first line to match GitHub's behavior
// https://github.com/actions/runner/blob/66800900843747f37591b077091dd2c8cf2c1796/src/Runner.Worker/Handlers/ScriptHandler.cs#L45-L58 // https://github.com/actions/runner/blob/66800900843747f37591b077091dd2c8cf2c1796/src/Runner.Worker/Handlers/ScriptHandler.cs#L45-L58
runStr, _, _ := strings.Cut(strings.TrimSpace(step.Run), "\n") runStr, _, _ := strings.Cut(strings.TrimSpace(string(step.Run)), "\n")
name = "Run " + util.IfZero(strings.TrimSpace(runStr), step.String()) name = "Run " + util.IfZero(strings.TrimSpace(runStr), step.String())
} }
return util.EllipsisDisplayString(name, limit) // database column has a length limit return util.EllipsisDisplayString(name, limit) // database column has a length limit
@@ -483,11 +482,6 @@ func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.Task
for _, v := range state.Steps { for _, v := range state.Steps {
stepStates[v.Id] = v 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. // 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. // Otherwise, concurrent requests with transaction will make the SQL read stale job state and result in wrong final state.
@@ -544,14 +538,22 @@ func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.Task
return err 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 { 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 { if _, err := db.GetEngine(ctx).ID(step.ID).Update(step); err != nil {
return err return err
} }
@@ -635,7 +637,13 @@ func StopTask(ctx context.Context, taskID int64, status Status) error {
} }
for _, step := range task.Steps { 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 { if _, err := e.ID(step.ID).Update(step); err != nil {
return err return err
} }
-24
View File
@@ -9,7 +9,6 @@ import (
"gitea.dev/models/db" "gitea.dev/models/db"
"gitea.dev/modules/container" "gitea.dev/modules/container"
"gitea.dev/modules/timeutil" "gitea.dev/modules/timeutil"
"gitea.dev/modules/util"
"xorm.io/builder" "xorm.io/builder"
) )
@@ -42,29 +41,6 @@ func (tasks TaskList) LoadJobs(ctx context.Context) error {
return jobsList.LoadAttributes(ctx, true) 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 { func (tasks TaskList) LoadAttributes(ctx context.Context) error {
return tasks.LoadJobs(ctx) return tasks.LoadJobs(ctx)
} }
-144
View File
@@ -5,14 +5,10 @@ package actions
import ( import (
"context" "context"
"fmt"
"time" "time"
runnerv1 "gitea.dev/actionslib/runner/v1"
"gitea.dev/models/db" "gitea.dev/models/db"
"gitea.dev/modules/container"
"gitea.dev/modules/timeutil" "gitea.dev/modules/timeutil"
"gitea.dev/modules/util"
) )
// ActionTaskStep represents a step of ActionTask // ActionTaskStep represents a step of ActionTask
@@ -25,8 +21,6 @@ type ActionTaskStep struct {
Status Status `xorm:"index"` Status Status `xorm:"index"`
LogIndex int64 LogIndex int64
LogLength 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 Started timeutil.TimeStamp
Stopped timeutil.TimeStamp Stopped timeutil.TimeStamp
Created timeutil.TimeStamp `xorm:"created"` 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) 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() { func init() {
db.RegisterModel(new(ActionTaskStep)) db.RegisterModel(new(ActionTaskStep))
} }
@@ -66,120 +39,3 @@ func GetTaskStepsByTaskID(ctx context.Context, taskID int64) ([]*ActionTaskStep,
var steps []*ActionTaskStep var steps []*ActionTaskStep
return steps, db.GetEngine(ctx).Where("task_id=?", taskID).OrderBy("`index` ASC").Find(&steps) 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
}
+2 -110
View File
@@ -94,14 +94,14 @@ func TestMakeTaskStepDisplayName(t *testing.T) {
{ {
name: "very long name truncated", name: "very long name truncated",
jobStep: &jobparser.Step{ jobStep: &jobparser.Step{
Name: strings.Repeat("a", 300), Name: jobparser.BlockSafeString(strings.Repeat("a", 300)),
}, },
expected: strings.Repeat("a", 252) + "…", expected: strings.Repeat("a", 252) + "…",
}, },
{ {
name: "very long run truncated", name: "very long run truncated",
jobStep: &jobparser.Step{ jobStep: &jobparser.Step{
Run: strings.Repeat("a", 300), Run: jobparser.BlockSafeString(strings.Repeat("a", 300)),
}, },
expected: "Run " + strings.Repeat("a", 248) + "…", expected: "Run " + strings.Repeat("a", 248) + "…",
}, },
@@ -407,114 +407,6 @@ func TestUpdateTaskByStateIsAtomic(t *testing.T) {
assert.GreaterOrEqual(t, unittest.AssertExistsAndLoadBean(t, &ActionTaskStep{TaskID: task.ID}).Started, before) 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, // newRunningTaskForCancelling inserts a running run/job/task assigned to a fresh runner,
// which is the state every cancellation test starts from. // which is the state every cancellation test starts from.
func newRunningTaskForCancelling(t *testing.T, name string, hasCancellingSupport bool) (*ActionTask, *ActionRunJob) { 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) 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 -11
View File
@@ -48,7 +48,13 @@ func init() {
db.RegisterModel(new(ActionVariable)) 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 { if utf8.RuneCountInString(data) > VariableDataMaxLength {
return nil, util.NewInvalidArgumentErrorf("data too long") 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) description = util.TruncateRunes(description, VariableDescriptionMaxLength)
variable := &ActionVariable{ variable := &ActionVariable{
OwnerID: scope.internalOwnerID, OwnerID: ownerID,
RepoID: scope.internalRepoID, RepoID: repoID,
Name: strings.ToUpper(name), Name: strings.ToUpper(name),
Data: data, Data: data,
Description: description, Description: description,
@@ -68,9 +74,9 @@ func InsertVariable(ctx context.Context, scope BelongingScope, name, data, descr
type FindVariablesOpts struct { type FindVariablesOpts struct {
db.ListOptions db.ListOptions
IDs []int64 IDs []int64
RepoID int64
OwnerID int64 // it will be ignored if RepoID is set
Name string Name string
BelongingScope BelongingScope
} }
func (opts FindVariablesOpts) ToOrders() string { func (opts FindVariablesOpts) ToOrders() string {
@@ -90,13 +96,12 @@ func (opts FindVariablesOpts) ToConds() builder.Cond {
// Since we now support instance-level variables, // Since we now support instance-level variables,
// there is no need to check for null values for `owner_id` and `repo_id` // 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": opts.RepoID})
cond = cond.And(builder.Eq{"repo_id": repoID}) if opts.RepoID != 0 { // if RepoID is set
if repoID != 0 { // if RepoID is set
// ignore OwnerID and treat it as 0 // ignore OwnerID and treat it as 0
cond = cond.And(builder.Eq{"owner_id": 0}) cond = cond.And(builder.Eq{"owner_id": 0})
} else { } else {
cond = cond.And(builder.Eq{"owner_id": ownerID}) cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
} }
if opts.Name != "" { if opts.Name != "" {
@@ -147,14 +152,14 @@ func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string,
} }
// Org / User level // 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 { if err != nil {
log.Error("find variables of org: %d, error: %v", run.Repo.OwnerID, err) log.Error("find variables of org: %d, error: %v", run.Repo.OwnerID, err)
return nil, err return nil, err
} }
// Repo level // 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 { if err != nil {
log.Error("find variables of repo: %d, error: %v", run.RepoID, err) log.Error("find variables of repo: %d, error: %v", run.RepoID, err)
return nil, err return nil, err
+10 -4
View File
@@ -436,6 +436,12 @@ type GetFeedsOptions struct {
DontCount bool // do counting in GetFeeds 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 // ActivityReadable return whether doer can read activities of user
func ActivityReadable(user, doer *user_model.User) bool { func ActivityReadable(user, doer *user_model.User) bool {
return !user.KeepActivityPrivate || return !user.KeepActivityPrivate ||
@@ -472,13 +478,13 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
} }
// check activity visibility for actor ( similar to activityReadable() ) // 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", cond = cond.And(builder.In("act_user_id",
builder.Select("`user`.id").Where( builder.Select("`user`.id").Where(
builder.Eq{"keep_activity_private": false, "visibility": structs.VisibleTypePublic}, builder.Eq{"keep_activity_private": false, "visibility": structs.VisibleTypePublic},
).From("`user`"), ).From("`user`"),
)) ))
} else if !user_model.DoerIsSiteAdminFullAccess(opts.Actor) { } else if !opts.Actor.IsAdmin {
uidCond := builder.Select("`user`.id").From("`user`").Where( uidCond := builder.Select("`user`.id").From("`user`").Where(
builder.Eq{"keep_activity_private": false}. builder.Eq{"keep_activity_private": false}.
And(builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))). 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 // 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))) 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}) cond = cond.And(builder.Eq{"`action`.is_private": false})
} }
if !opts.IncludeDeleted { 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 err error
var cond builder.Cond var cond builder.Cond
// if the actor is the requested user or is an administrator, we can skip the ActivityQueryCondition // 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{ cond = builder.Eq{
"user_id": opts.RequestedUser.ID, "user_id": opts.RequestedUser.ID,
}.And( }.And(
+88 -28
View File
@@ -228,17 +228,32 @@ func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.Repository
repoIDs := nl.getPendingRepoIDs() repoIDs := nl.getPendingRepoIDs()
repos := make(map[int64]*repo_model.Repository, len(repoIDs)) repos := make(map[int64]*repo_model.Repository, len(repoIDs))
err := db.QueryAndProcessByChunk(repoIDs, func(chunk []int64) (out []*repo_model.Repository, err error) { left := len(repoIDs)
err = db.GetEngine(ctx).In("id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(repo *repo_model.Repository) error { rows, err := db.GetEngine(ctx).
repos[repo.ID] = repo In("id", repoIDs[:limit]).
return nil Rows(new(repo_model.Repository))
})
if err != nil { if err != nil {
return nil, nil, err 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{} failed := []int{}
reposList := make(repo_model.RepositoryList, 0, len(repoIDs)) reposList := make(repo_model.RepositoryList, 0, len(repoIDs))
@@ -284,17 +299,32 @@ func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) {
issueIDs := nl.getPendingIssueIDs() issueIDs := nl.getPendingIssueIDs()
issues := make(map[int64]*issues_model.Issue, len(issueIDs)) issues := make(map[int64]*issues_model.Issue, len(issueIDs))
err := db.QueryAndProcessByChunk(issueIDs, func(chunk []int64) (out []*issues_model.Issue, err error) { left := len(issueIDs)
err = db.GetEngine(ctx).In("id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(issue *issues_model.Issue) error { rows, err := db.GetEngine(ctx).
issues[issue.ID] = issue In("id", issueIDs[:limit]).
return nil Rows(new(issues_model.Issue))
})
if err != nil { if err != nil {
return nil, err 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{} failures := []int{}
for i, notification := range nl { for i, notification := range nl {
@@ -361,17 +391,32 @@ func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) {
userIDs := nl.getUserIDs() userIDs := nl.getUserIDs()
users := make(map[int64]*user_model.User, len(userIDs)) users := make(map[int64]*user_model.User, len(userIDs))
err := db.QueryAndProcessByChunk(userIDs, func(chunk []int64) (out []*user_model.User, err error) { left := len(userIDs)
err = db.GetEngine(ctx).In("id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(user *user_model.User) error { rows, err := db.GetEngine(ctx).
users[user.ID] = user In("id", userIDs[:limit]).
return nil Rows(new(user_model.User))
})
if err != nil { if err != nil {
return nil, err 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{} failures := []int{}
for i, notification := range nl { for i, notification := range nl {
if notification.UserID > 0 && notification.User == nil && users[notification.UserID] != nil { if notification.UserID > 0 && notification.User == nil && users[notification.UserID] != nil {
@@ -394,17 +439,32 @@ func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) {
commentIDs := nl.getPendingCommentIDs() commentIDs := nl.getPendingCommentIDs()
comments := make(map[int64]*issues_model.Comment, len(commentIDs)) comments := make(map[int64]*issues_model.Comment, len(commentIDs))
err := db.QueryAndProcessByChunk(commentIDs, func(chunk []int64) (out []*issues_model.Comment, err error) { left := len(commentIDs)
err = db.GetEngine(ctx).In("id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(comment *issues_model.Comment) error { rows, err := db.GetEngine(ctx).
comments[comment.ID] = comment In("id", commentIDs[:limit]).
return nil Rows(new(issues_model.Comment))
})
if err != nil { if err != nil {
return nil, err 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{} failures := []int{}
for i, notification := range nl { for i, notification := range nl {
if notification.CommentID > 0 && notification.Comment == nil && comments[notification.CommentID] != nil { if notification.CommentID > 0 && notification.Comment == nil && comments[notification.CommentID] != nil {
-66
View File
@@ -15,7 +15,6 @@ import (
user_model "gitea.dev/models/user" user_model "gitea.dev/models/user"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestCreateOrUpdateIssueNotifications(t *testing.T) { 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) { func TestNotification_GetRepo(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{RepoID: 1}) notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{RepoID: 1})
+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 // 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) { 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 { if err != nil {
return nil, fmt.Errorf("FillFromGit: %w", err) return nil, fmt.Errorf("FillFromGit: %w", err)
} }
if authors == nil { if code.Authors == nil {
return nil, nil return nil, nil
} }
users := make(map[int64]*ActivityAuthorData) users := make(map[int64]*ActivityAuthorData)
var unknownUserID int64 var unknownUserID int64
unknownUserAvatarLink := user_model.NewGhostUser().AvatarLink(ctx) unknownUserAvatarLink := user_model.NewGhostUser().AvatarLink(ctx)
for _, v := range authors { for _, v := range code.Authors {
if len(v.Email) == 0 { if len(v.Email) == 0 {
continue continue
} }
+1 -1
View File
@@ -199,7 +199,7 @@ func SearchPrincipalKey(ctx context.Context, principalKey string) (*PublicKey, e
// In the future, the existing principal keys should be migrated to use the fingerprint ("principal:{name}") instead of the content // In the future, the existing principal keys should be migrated to use the fingerprint ("principal:{name}") instead of the content
key := new(PublicKey) key := new(PublicKey)
has, err := db.GetEngine(ctx). has, err := db.GetEngine(ctx).
Where("content = ? AND type = ?", principalKey, KeyTypePrincipal). Where("content = ?", principalKey).
Get(key) Get(key)
if err != nil { if err != nil {
return nil, err return nil, err
+1 -2
View File
@@ -15,7 +15,6 @@ import (
"sync" "sync"
"gitea.dev/models/db" "gitea.dev/models/db"
"gitea.dev/modules/consts"
"gitea.dev/modules/log" "gitea.dev/modules/log"
"gitea.dev/modules/setting" "gitea.dev/modules/setting"
"gitea.dev/modules/util" "gitea.dev/modules/util"
@@ -136,7 +135,7 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
defer f.Close() defer f.Close()
// Note: chmod command does not support in Windows. // Note: chmod command does not support in Windows.
if !consts.IsWindows { if !setting.IsWindows {
fi, err := f.Stat() fi, err := f.Stat()
if err != nil { if err != nil {
return err return err
-16
View File
@@ -12,13 +12,11 @@ import (
"strings" "strings"
"testing" "testing"
"gitea.dev/models/db"
"gitea.dev/models/unittest" "gitea.dev/models/unittest"
"gitea.dev/modules/setting" "gitea.dev/modules/setting"
"github.com/42wim/sshsig" "github.com/42wim/sshsig"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func Test_SSHParsePublicKey(t *testing.T) { func Test_SSHParsePublicKey(t *testing.T) {
@@ -483,20 +481,6 @@ func TestPublicKeysAreExternallyManaged(t *testing.T) {
assert.False(t, externals[0]) assert.False(t, externals[0])
} }
func TestSearchPrincipalKeyIgnoresOtherKeyTypes(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
userKey := unittest.AssertExistsAndLoadBean(t, &PublicKey{ID: 1})
_, err := SearchPrincipalKey(t.Context(), userKey.Content)
assert.True(t, IsErrKeyNotExist(err))
principal := &PublicKey{OwnerID: 1, Name: userKey.Content, Content: userKey.Content, Type: KeyTypePrincipal}
require.NoError(t, db.Insert(t.Context(), principal))
key, err := SearchPrincipalKey(t.Context(), userKey.Content)
require.NoError(t, err)
assert.Equal(t, principal.ID, key.ID)
}
// TestCheckPublicKeyStringOversized tests if oversized SSH2 public key strings are rejected before triggering costly operations. // TestCheckPublicKeyStringOversized tests if oversized SSH2 public key strings are rejected before triggering costly operations.
func TestCheckPublicKeyStringOversized(t *testing.T) { func TestCheckPublicKeyStringOversized(t *testing.T) {
_, err := parseKeyString(strings.Repeat("a", maxKeyContentBytes+1)) _, err := parseKeyString(strings.Repeat("a", maxKeyContentBytes+1))
-1
View File
@@ -84,7 +84,6 @@ var (
UserTwoFactorRegenerate = define("user:twofactor:regenerate", "Regenerated two-factor authentication secret for user {scope}.") 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}.") UserTwoFactorDisable = define("user:twofactor:disable", "Disabled two-factor authentication for user {scope}.")
UserWebAuthAdd = define("user:webauth:add", "Added WebAuthn key {credential} 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}.") 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}.") 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}.") UserExternalLoginRemove = define("user:externallogin:remove", "Removed external login from authentication source {auth_source_id} for user {scope}.")
+7 -6
View File
@@ -54,7 +54,6 @@ type AccessTokenScope string
// for all categories, write implies read // for all categories, write implies read
const ( const (
AccessTokenScopeAll AccessTokenScope = "all" AccessTokenScopeAll AccessTokenScope = "all"
AccessTokenScopeNone AccessTokenScope = "no-token-scope" // no token permission scopes, so no API permissions
AccessTokenScopePublicOnly AccessTokenScope = "public-only" // limited to public orgs/repos AccessTokenScopePublicOnly AccessTokenScope = "public-only" // limited to public orgs/repos
AccessTokenScopeReadActivityPub AccessTokenScope = "read:activitypub" AccessTokenScopeReadActivityPub AccessTokenScope = "read:activitypub"
@@ -148,7 +147,6 @@ var allAccessTokenScopes = []AccessTokenScope{
// allAccessTokenScopeBits contains all access token scopes. // allAccessTokenScopeBits contains all access token scopes.
var allAccessTokenScopeBits = map[AccessTokenScope]accessTokenScopeBitmap{ var allAccessTokenScopeBits = map[AccessTokenScope]accessTokenScopeBitmap{
AccessTokenScopeAll: accessTokenScopeAllBits, AccessTokenScopeAll: accessTokenScopeAllBits,
AccessTokenScopeNone: 0,
AccessTokenScopePublicOnly: accessTokenScopePublicOnlyBits, AccessTokenScopePublicOnly: accessTokenScopePublicOnlyBits,
AccessTokenScopeReadActivityPub: accessTokenScopeReadActivityPubBits, AccessTokenScopeReadActivityPub: accessTokenScopeReadActivityPubBits,
AccessTokenScopeWriteActivityPub: accessTokenScopeWriteActivityPubBits, AccessTokenScopeWriteActivityPub: accessTokenScopeWriteActivityPubBits,
@@ -213,6 +211,11 @@ func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTok
return scopes return scopes
} }
// ContainsCategory checks if a list of categories contains a specific category
func ContainsCategory(categories []AccessTokenScopeCategory, category AccessTokenScopeCategory) bool {
return slices.Contains(categories, category)
}
// GetScopeLevelFromAccessMode converts permission access mode to scope level // GetScopeLevelFromAccessMode converts permission access mode to scope level
func GetScopeLevelFromAccessMode(mode perm.AccessMode) AccessTokenScopeLevel { func GetScopeLevelFromAccessMode(mode perm.AccessMode) AccessTokenScopeLevel {
switch mode { switch mode {
@@ -283,14 +286,12 @@ func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
if bitmap == 0 && slices.Contains(s.StringSlice(), string(AccessTokenScopeNone)) {
return AccessTokenScopeNone, nil
}
return bitmap.toScope(), nil return bitmap.toScope(), nil
} }
func (s AccessTokenScope) HasPermissionScope() bool { func (s AccessTokenScope) HasPermissionScope() bool {
return s != "" && s != AccessTokenScopeNone && s != AccessTokenScopePublicOnly return s != "" && s != AccessTokenScopePublicOnly
} }
// PublicOnly checks if this token scope is limited to public resources // PublicOnly checks if this token scope is limited to public resources
-16
View File
@@ -18,14 +18,8 @@ type scopeTestNormalize struct {
func TestAccessTokenScope_Normalize(t *testing.T) { func TestAccessTokenScope_Normalize(t *testing.T) {
assert.Equal(t, []string{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "repository", "user"}, GetAccessTokenCategories()) assert.Equal(t, []string{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "repository", "user"}, GetAccessTokenCategories())
assert.False(t, AccessTokenScopeNone.HasPermissionScope())
tests := []scopeTestNormalize{ tests := []scopeTestNormalize{
{"", "", nil}, {"", "", nil},
{"no-token-scope", "no-token-scope", nil},
{"no-token-scope,no-token-scope", "no-token-scope", nil},
{"no-token-scope,read:user", "read:user", nil},
{"no-token-scope,public-only", "public-only", nil},
{"no-token-scope,all", "all", nil},
{"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil}, {"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil},
{"all", "all", nil}, {"all", "all", nil},
{"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user", "all", nil}, {"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user", "all", nil},
@@ -64,20 +58,10 @@ func TestAccessTokenScope_HasScope(t *testing.T) {
{"all", "write:package", true, nil}, {"all", "write:package", true, nil},
{"write:package", "all", false, nil}, {"write:package", "all", false, nil},
{"public-only", "read:issue", false, nil}, {"public-only", "read:issue", false, nil},
{"no-token-scope", "all", false, nil},
{"no-token-scope", "no-token-scope", true, nil},
} }
for _, scope := range GetAccessTokenCategories() { for _, scope := range GetAccessTokenCategories() {
tests = append(tests, tests = append(tests,
scopeTestHasScope{
AccessTokenScopeNone,
AccessTokenScope("read:" + scope), false, nil,
},
scopeTestHasScope{
AccessTokenScopeNone,
AccessTokenScope("write:" + scope), false, nil,
},
scopeTestHasScope{ scopeTestHasScope{
AccessTokenScope("read:" + scope), AccessTokenScope("read:" + scope),
AccessTokenScope("read:" + scope), true, nil, AccessTokenScope("read:" + scope), true, nil,
+3 -7
View File
@@ -20,9 +20,6 @@ type AuthToken struct { //nolint:revive // export stutter
TokenHash string TokenHash string
UserID int64 `xorm:"INDEX"` UserID int64 `xorm:"INDEX"`
ExpiresUnix timeutil.TimeStamp `xorm:"INDEX"` ExpiresUnix timeutil.TimeStamp `xorm:"INDEX"`
PreviousTokenHash string // Identifies stale cookies without authenticating them.
RotatedUnix timeutil.TimeStamp // Bounds the grace window for stale cookies.
} }
func init() { func init() {
@@ -47,10 +44,9 @@ func GetAuthTokenByID(ctx context.Context, id string) (*AuthToken, error) {
return at, nil return at, nil
} }
func UpdateAuthTokenByID(ctx context.Context, t *AuthToken, oldHash string) (bool, error) { func UpdateAuthTokenByID(ctx context.Context, t *AuthToken) error {
affected, err := db.GetEngine(ctx).ID(t.ID).Where("token_hash = ?", oldHash). _, err := db.GetEngine(ctx).ID(t.ID).Cols("token_hash", "expires_unix").Update(t)
Cols("token_hash", "expires_unix", "previous_token_hash", "rotated_unix").Update(t) return err
return affected == 1, err
} }
func DeleteAuthTokenByID(ctx context.Context, id string) error { func DeleteAuthTokenByID(ctx context.Context, id string) error {
+11 -15
View File
@@ -25,6 +25,7 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"xorm.io/builder" "xorm.io/builder"
"xorm.io/xorm"
) )
// Authorization codes should expire within 10 minutes per https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2 // Authorization codes should expire within 10 minutes per https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2
@@ -87,16 +88,6 @@ func BuiltinApplications() map[string]*BuiltinOAuth2Application {
DisplayName: "Gitea App", DisplayName: "Gitea App",
RedirectURIs: []string{"com.gitea.app://oauth/callback"}, RedirectURIs: []string{"com.gitea.app://oauth/callback"},
} }
m["75a9ec3e-42b5-40cb-9144-9ea96ecd56a6"] = &BuiltinOAuth2Application{
ConfigName: "gitea-vscode",
DisplayName: "gitea-vscode",
RedirectURIs: []string{
"http://127.0.0.1",
"https://127.0.0.1",
"vscode://gitea.gitea-for-vscode/auth/callback",
"vscode-insiders://gitea.gitea-for-vscode/auth/callback",
},
}
return m return m
} }
@@ -596,17 +587,22 @@ func GetOAuth2GrantsByUserID(ctx context.Context, uid int64) ([]*OAuth2Grant, er
Grant *OAuth2Grant `xorm:"extends"` Grant *OAuth2Grant `xorm:"extends"`
Application *OAuth2Application `xorm:"extends"` Application *OAuth2Application `xorm:"extends"`
} }
var joinedOAuth2Grants []*joinedOAuth2Grant var results *xorm.Rows
err := db.GetEngine(ctx). var err error
if results, err = db.GetEngine(ctx).
Table("oauth2_grant"). Table("oauth2_grant").
Where("user_id = ?", uid). Where("user_id = ?", uid).
Join("INNER", "oauth2_application", "application_id = oauth2_application.id"). Join("INNER", "oauth2_application", "application_id = oauth2_application.id").
Find(&joinedOAuth2Grants) Rows(new(joinedOAuth2Grant)); err != nil {
if err != nil {
return nil, err return nil, err
} }
defer results.Close()
grants := make([]*OAuth2Grant, 0) grants := make([]*OAuth2Grant, 0)
for _, joinedGrant := range joinedOAuth2Grants { for results.Next() {
joinedGrant := new(joinedOAuth2Grant)
if err := results.Scan(joinedGrant); err != nil {
return nil, err
}
joinedGrant.Grant.Application = joinedGrant.Application joinedGrant.Grant.Application = joinedGrant.Application
grants = append(grants, joinedGrant.Grant) grants = append(grants, joinedGrant.Grant)
} }
+72 -29
View File
@@ -5,6 +5,7 @@ package auth
import ( import (
"context" "context"
"fmt"
"gitea.dev/models/db" "gitea.dev/models/db"
"gitea.dev/modules/timeutil" "gitea.dev/modules/timeutil"
@@ -12,46 +13,49 @@ import (
"xorm.io/builder" "xorm.io/builder"
) )
// Session represents a session compatible for go-chi session
type Session struct { type Session struct {
Key string `xorm:"pk CHAR(16)"` // the limit is from legacy go-chi/session Key string `xorm:"pk CHAR(16)"` // has to be Key to match with go-chi/session
Data []byte `xorm:"BLOB"` // on MySQL this has a maximum size of 64Kb Data []byte `xorm:"BLOB"` // on MySQL this has a maximum size of 64Kb - this may need to be increased
LastAccessTime timeutil.TimeStamp `xorm:"expiry"` // last access time, the field name is from legacy go-chi/session, we don't want to change it at the moment Expiry timeutil.TimeStamp // has to be Expiry to match with go-chi/session
} }
const DbSessionLastAccessTime = "expiry" // maybe we can make a deeper clean up in the future, just keep this PR focused
func init() { func init() {
db.RegisterModel(new(Session)) db.RegisterModel(new(Session))
} }
// UpdateSession stores the data of the session with provided id, creating the session only if create is set // UpdateSession updates the session with provided id
func UpdateSession(ctx context.Context, key string, data []byte, create bool) error { func UpdateSession(ctx context.Context, key string, data []byte) error {
session := &Session{Key: key, Data: data, LastAccessTime: timeutil.TimeStampNow()} _, err := db.GetEngine(ctx).ID(key).Update(&Session{
update := func() (int64, error) { Data: data,
return db.GetEngine(ctx).ID(key).Cols("data", DbSessionLastAccessTime).Update(session) Expiry: timeutil.TimeStampNow(),
} })
if updated, err := update(); err != nil || updated > 0 || !create {
return err
}
insertErr := db.Insert(ctx, session)
if insertErr == nil {
return nil
}
// the row exists if a concurrent request inserted it, or if MySQL reported an unchanged row as not updated
if exist, err := db.Exist[Session](ctx, builder.Eq{"`key`": key}); err != nil || !exist {
return insertErr
}
_, err := update()
return err return err
} }
func UpdateSessionLastAccessTime(ctx context.Context, key string) error { // ReadSession reads the data for the provided session
_, err := db.GetEngine(ctx).ID(key).Cols(DbSessionLastAccessTime).Update(&Session{LastAccessTime: timeutil.TimeStampNow()}) func ReadSession(ctx context.Context, key string) (*Session, error) {
return err return db.WithTx2(ctx, func(ctx context.Context) (*Session, error) {
session, exist, err := db.Get[Session](ctx, builder.Eq{"`key`": key})
if err != nil {
return nil, err
} else if !exist {
session = &Session{
Key: key,
Expiry: timeutil.TimeStampNow(),
}
if err := db.Insert(ctx, session); err != nil {
return nil, err
}
} }
func GetSession(ctx context.Context, key string) (*Session, bool, error) { return session, nil
return db.Get[Session](ctx, builder.Eq{"`key`": key}) })
}
// ExistSession checks if a session exists
func ExistSession(ctx context.Context, key string) (bool, error) {
return db.Exist[Session](ctx, builder.Eq{"`key`": key})
} }
// DestroySession destroys a session // DestroySession destroys a session
@@ -62,8 +66,47 @@ func DestroySession(ctx context.Context, key string) error {
return err return err
} }
// RegenerateSession regenerates a session from the old id
func RegenerateSession(ctx context.Context, oldKey, newKey string) (*Session, error) {
return db.WithTx2(ctx, func(ctx context.Context) (*Session, error) {
if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": newKey}); err != nil {
return nil, err
} else if has {
return nil, fmt.Errorf("session Key: %s already exists", newKey)
}
if has, err := db.Exist[Session](ctx, builder.Eq{"`key`": oldKey}); err != nil {
return nil, err
} else if !has {
if err := db.Insert(ctx, &Session{
Key: oldKey,
Expiry: timeutil.TimeStampNow(),
}); err != nil {
return nil, err
}
}
if _, err := db.Exec(ctx, "UPDATE `session` SET `key` = ? WHERE `key`=?", newKey, oldKey); err != nil {
return nil, err
}
s, _, err := db.Get[Session](ctx, builder.Eq{"`key`": newKey})
if err != nil {
// is not exist, it should be impossible
return nil, err
}
return s, nil
})
}
// CountSessions returns the number of sessions
func CountSessions(ctx context.Context) (int64, error) {
return db.GetEngine(ctx).Count(&Session{})
}
// CleanupSessions cleans up expired sessions // CleanupSessions cleans up expired sessions
func CleanupSessions(ctx context.Context, maxLifetime int64) error { func CleanupSessions(ctx context.Context, maxLifetime int64) error {
_, err := db.GetEngine(ctx).Where(DbSessionLastAccessTime+" <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{}) _, err := db.GetEngine(ctx).Where("expiry <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{})
return err return err
} }
+4 -22
View File
@@ -150,9 +150,9 @@ func GetWebAuthnCredentialByName(ctx context.Context, uid int64, name string) (*
} }
// GetWebAuthnCredentialByID returns WebAuthn credential by id // GetWebAuthnCredentialByID returns WebAuthn credential by id
func GetWebAuthnCredentialByID(ctx context.Context, uid, id int64) (*WebAuthnCredential, error) { func GetWebAuthnCredentialByID(ctx context.Context, id int64) (*WebAuthnCredential, error) {
cred := new(WebAuthnCredential) cred := new(WebAuthnCredential)
if found, err := db.GetEngine(ctx).Where("user_id = ?", uid).ID(id).Get(cred); err != nil { if found, err := db.GetEngine(ctx).ID(id).Get(cred); err != nil {
return nil, err return nil, err
} else if !found { } else if !found {
return nil, ErrWebAuthnCredentialNotExist{ID: id} return nil, ErrWebAuthnCredentialNotExist{ID: id}
@@ -195,26 +195,8 @@ func CreateCredential(ctx context.Context, userID int64, name string, cred *weba
return c, nil return c, nil
} }
// RenameCredential renames the user's WebAuthnCredential, names are unique per user regardless of letter case
func RenameCredential(ctx context.Context, uid, id int64, name string) (bool, error) {
used, err := db.GetEngine(ctx).Where("user_id = ? AND lower_name = ? AND id != ?", uid, strings.ToLower(name), id).Exist(&WebAuthnCredential{})
if err != nil {
return false, err
} else if used {
return false, util.ErrorWrapTranslatable(
util.NewAlreadyExistErrorf("WebAuthn credential name already exists [uid: %d, name: %s]", uid, name),
"settings.webauthn_nickname_been_used",
)
}
updated, err := db.GetEngine(ctx).ID(id).Where("user_id=? AND `name`<>?", uid, name).Cols("name", "lower_name").Update(&WebAuthnCredential{
Name: name,
LowerName: strings.ToLower(name),
})
return updated > 0, err
}
// DeleteCredential will delete WebAuthnCredential // DeleteCredential will delete WebAuthnCredential
func DeleteCredential(ctx context.Context, uid, id int64) (bool, error) { func DeleteCredential(ctx context.Context, id, userID int64) (bool, error) {
had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", uid).Delete(&WebAuthnCredential{}) had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", userID).Delete(&WebAuthnCredential{})
return had > 0, err return had > 0, err
} }
+2 -6
View File
@@ -16,15 +16,11 @@ import (
func TestGetWebAuthnCredentialByID(t *testing.T) { func TestGetWebAuthnCredentialByID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
res, err := auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 1) res, err := auth_model.GetWebAuthnCredentialByID(t.Context(), 1)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "WebAuthn credential", res.Name) assert.Equal(t, "WebAuthn credential", res.Name)
_, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 99999, 1) _, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 342432)
assert.Error(t, err)
assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err))
_, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 99999)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err)) assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err))
} }
+5 -9
View File
@@ -13,12 +13,8 @@ import (
"xorm.io/xorm/schemas" "xorm.io/xorm/schemas"
) )
type sqlTableInfoProvider interface { func iterateTableByColumn[Bean any](ctx context.Context, colName string, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error {
TableInfo(bean any) (*schemas.Table, error) table, err := xormEngine.TableInfo(new(Bean))
}
func IterateTableByColumn[Bean any](ctx context.Context, tableInfo sqlTableInfoProvider, sess SQLSession, colName string, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error {
table, err := tableInfo.TableInfo(new(Bean))
if err != nil { if err != nil {
return err return err
} }
@@ -40,7 +36,7 @@ func IterateTableByColumn[Bean any](ctx context.Context, tableInfo sqlTableInfoP
} }
beans := make([]*Bean, 0, batchSize) beans := make([]*Bean, 0, batchSize)
query := sess.Table(table.Name).Asc(colName) query := GetEngine(ctx).Table(table.Name).Asc(colName)
batchCond := cond batchCond := cond
if lastColValue != nil { if lastColValue != nil {
@@ -72,9 +68,9 @@ func IterateTableByColumn[Bean any](ctx context.Context, tableInfo sqlTableInfoP
} }
func IterateByColumn[Bean any](ctx context.Context, colName string, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error { func IterateByColumn[Bean any](ctx context.Context, colName string, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error {
return IterateTableByColumn(ctx, xormEngine, GetEngine(ctx), colName, cond, f) return iterateTableByColumn(ctx, colName, cond, f)
} }
func Iterate[Bean any](ctx context.Context, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error { func Iterate[Bean any](ctx context.Context, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error {
return IterateTableByColumn(ctx, xormEngine, GetEngine(ctx), "", cond, f) return iterateTableByColumn(ctx, "", cond, f)
} }
-16
View File
@@ -5,7 +5,6 @@ package db
import ( import (
"context" "context"
"slices"
"gitea.dev/modules/setting" "gitea.dev/modules/setting"
@@ -199,18 +198,3 @@ func FindAndCount[T any](ctx context.Context, opts FindOptions) ([]*T, int64, er
} }
return objects, cnt, nil return objects, cnt, nil
} }
func QueryAndProcessByChunk[IN, OUT any](in []IN, fnIn func(chunk []IN) ([]OUT, error), fnOut func(out OUT) error) error {
for chunkIn := range slices.Chunk(in, DefaultMaxInSize) {
chunkOut, err := fnIn(chunkIn)
if err != nil {
return err
}
for _, out := range chunkOut {
if err := fnOut(out); err != nil {
return err
}
}
}
return nil
}
+2 -2
View File
@@ -3,7 +3,7 @@
uid: 1 uid: 1
name: "Test" name: "Test"
client_id: "da7da3ba-9a13-4167-856f-3899de0b0138" client_id: "da7da3ba-9a13-4167-856f-3899de0b0138"
client_secret: "$2a$04$fk.C0LyaUcmxOFLcfcXUDO084yIA9o0nRwkSHSZb/tvZkCzLZjRea" # bcrypt of "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA= client_secret: "$2a$10$UYRgUSgekzBp6hYe8pAdc.cgB4Gn06QRKsORUnIYTYQADs.YR/uvi" # bcrypt of "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=
redirect_uris: '["https://example.com"]' redirect_uris: '["https://example.com"]'
created_unix: 1546869730 created_unix: 1546869730
updated_unix: 1546869730 updated_unix: 1546869730
@@ -13,7 +13,7 @@
uid: 2 uid: 2
name: "Test native app" name: "Test native app"
client_id: "ce5a1322-42a7-11ed-b878-0242ac120002" client_id: "ce5a1322-42a7-11ed-b878-0242ac120002"
client_secret: "$2a$04$VvPoNpflYJB94T8seLcZnOCTtQtB3Q2wXw7ZgrWIi2Oix3VWL8s7i" # bcrypt of "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA= client_secret: "$2a$10$UYRgUSgekzBp6hYe8pAdc.cgB4Gn06QRKsORUnIYTYQADs.YR/uvi" # bcrypt of "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=
redirect_uris: '["http://127.0.0.1"]' redirect_uris: '["http://127.0.0.1"]'
created_unix: 1546869730 created_unix: 1546869730
updated_unix: 1546869730 updated_unix: 1546869730
+54 -112
View File
@@ -35,50 +35,43 @@ type ProtectedBranch struct {
Priority int64 `xorm:"NOT NULL DEFAULT 0"` Priority int64 `xorm:"NOT NULL DEFAULT 0"`
globRule glob.Glob `xorm:"-"` globRule glob.Glob `xorm:"-"`
isPlainName bool `xorm:"-"` isPlainName bool `xorm:"-"`
ProtectedBranchConfig `xorm:"'config' JSON LONGTEXT"` CanPush bool `xorm:"NOT NULL DEFAULT false"`
EnableWhitelist bool
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"`
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
EnableBypassAllowlist bool `xorm:"NOT NULL DEFAULT false"`
BypassAllowlistUserIDs []int64 `xorm:"JSON TEXT"`
BypassAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
CanForcePush bool `xorm:"NOT NULL DEFAULT false"`
EnableForcePushAllowlist bool `xorm:"NOT NULL DEFAULT false"`
ForcePushAllowlistUserIDs []int64 `xorm:"JSON TEXT"`
ForcePushAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
ForcePushAllowlistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
StatusCheckContexts []string `xorm:"JSON TEXT"`
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
BlockOnCodeownerReviews bool `xorm:"NOT NULL DEFAULT false"`
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"`
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
IgnoreStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
ProtectedFilePatterns string `xorm:"TEXT"`
UnprotectedFilePatterns string `xorm:"TEXT"`
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false"`
CreatedUnix timeutil.TimeStamp `xorm:"created"` CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"` UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
} }
// ProtectedBranchConfig stores protection settings in a single JSON column.
type ProtectedBranchConfig struct {
CanPush bool `json:"can_push,omitzero"`
EnableWhitelist bool `json:"enable_whitelist,omitzero"`
WhitelistUserIDs []int64 `json:"whitelist_user_ids,omitzero"`
WhitelistTeamIDs []int64 `json:"whitelist_team_ids,omitzero"`
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitzero"`
MergeWhitelistUserIDs []int64 `json:"merge_whitelist_user_ids,omitzero"`
MergeWhitelistTeamIDs []int64 `json:"merge_whitelist_team_ids,omitzero"`
EnableBypassAllowlist bool `json:"enable_bypass_allowlist,omitzero"`
BypassAllowlistUserIDs []int64 `json:"bypass_allowlist_user_ids,omitzero"`
BypassAllowlistTeamIDs []int64 `json:"bypass_allowlist_team_ids,omitzero"`
CanForcePush bool `json:"can_force_push,omitzero"`
EnableForcePushAllowlist bool `json:"enable_force_push_allowlist,omitzero"`
ForcePushAllowlistUserIDs []int64 `json:"force_push_allowlist_user_ids,omitzero"`
ForcePushAllowlistTeamIDs []int64 `json:"force_push_allowlist_team_ids,omitzero"`
CanDelete bool `json:"can_delete,omitzero"`
EnableDeletionAllowlist bool `json:"enable_deletion_allowlist,omitzero"`
DeletionAllowlistUserIDs []int64 `json:"deletion_allowlist_user_ids,omitzero"`
DeletionAllowlistTeamIDs []int64 `json:"deletion_allowlist_team_ids,omitzero"`
EnableStatusCheck bool `json:"enable_status_check,omitzero"`
StatusCheckContexts []string `json:"status_check_contexts,omitzero"`
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitzero"`
ApprovalsWhitelistUserIDs []int64 `json:"approvals_whitelist_user_ids,omitzero"`
ApprovalsWhitelistTeamIDs []int64 `json:"approvals_whitelist_team_ids,omitzero"`
RequiredApprovals int64 `json:"required_approvals,omitzero"`
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitzero"`
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitzero"`
BlockOnCodeownerReviews bool `json:"block_on_codeowner_reviews,omitzero"`
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitzero"`
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitzero"`
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitzero"`
RequireSignedCommits bool `json:"require_signed_commits,omitzero"`
ProtectedFilePatterns string `json:"protected_file_patterns,omitzero"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitzero"`
BlockAdminMergeOverride bool `json:"block_admin_merge_override,omitzero"`
}
func init() { func init() {
db.RegisterModel(new(ProtectedBranch)) db.RegisterModel(new(ProtectedBranch))
} }
@@ -106,8 +99,8 @@ func (protectBranch *ProtectedBranch) loadGlob() {
var err error var err error
protectBranch.globRule, err = glob.Compile(protectBranch.RuleName, '/') protectBranch.globRule, err = glob.Compile(protectBranch.RuleName, '/')
if err != nil { if err != nil {
log.Debug("Invalid glob rule for ProtectedBranch[%d]: %s %v", protectBranch.ID, protectBranch.RuleName, err) log.Warn("Invalid glob rule for ProtectedBranch[%d]: %s %v", protectBranch.ID, protectBranch.RuleName, err)
protectBranch.globRule, _ = glob.Compile(glob.QuoteMeta(protectBranch.RuleName), '/') protectBranch.globRule = glob.MustCompile(glob.QuoteMeta(protectBranch.RuleName), '/')
} }
} }
@@ -117,9 +110,7 @@ func (protectBranch *ProtectedBranch) Match(branchName string) bool {
if protectBranch.isPlainName { if protectBranch.isPlainName {
return strings.EqualFold(protectBranch.RuleName, branchName) return strings.EqualFold(protectBranch.RuleName, branchName)
} }
if protectBranch.globRule == nil {
return false // in case of invalid glob rule, we don't match anything
}
return protectBranch.globRule.Match(branchName) return protectBranch.globRule.Match(branchName)
} }
@@ -184,36 +175,6 @@ func (protectBranch *ProtectedBranch) CanUserForcePush(ctx context.Context, user
return in && protectBranch.CanUserPush(ctx, user, permissionInRepo) return in && protectBranch.CanUserPush(ctx, user, permissionInRepo)
} }
// CanUserDelete returns if some user could delete this protected branch.
// Since deletion can be used to reset a branch, it also requires regular push access.
func (protectBranch *ProtectedBranch) CanUserDelete(ctx context.Context, user *user_model.User, permissionInRepo access_model.Permission) bool {
if user == nil || !permissionInRepo.CanWrite(unit.TypeCode) {
return false
}
if !protectBranch.CanDelete || !protectBranch.CanUserPush(ctx, user, permissionInRepo) {
return false
}
if !protectBranch.EnableDeletionAllowlist {
return true
}
if slices.Contains(protectBranch.DeletionAllowlistUserIDs, user.ID) {
return true
}
if len(protectBranch.DeletionAllowlistTeamIDs) == 0 {
return false
}
in, err := organization.IsUserInTeams(ctx, user.ID, protectBranch.DeletionAllowlistTeamIDs)
if err != nil {
log.Error("IsUserInTeams: %v", err)
return false
}
return in
}
// IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch // IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch
func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, userID int64, permissionInRepo access_model.Permission) bool { func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, userID int64, permissionInRepo access_model.Permission) bool {
if !protectBranch.EnableMergeWhitelist { if !protectBranch.EnableMergeWhitelist {
@@ -398,9 +359,6 @@ type WhitelistOptions struct {
ForcePushUserIDs []int64 ForcePushUserIDs []int64
ForcePushTeamIDs []int64 ForcePushTeamIDs []int64
DeletionUserIDs []int64
DeletionTeamIDs []int64
MergeUserIDs []int64 MergeUserIDs []int64
MergeTeamIDs []int64 MergeTeamIDs []int64
@@ -437,12 +395,6 @@ func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, prote
} }
protectBranch.ForcePushAllowlistUserIDs = whitelist protectBranch.ForcePushAllowlistUserIDs = whitelist
whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.DeletionAllowlistUserIDs, opts.DeletionUserIDs)
if err != nil {
return err
}
protectBranch.DeletionAllowlistUserIDs = whitelist
whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs) whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs)
if err != nil { if err != nil {
return err return err
@@ -474,12 +426,6 @@ func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, prote
} }
protectBranch.ForcePushAllowlistTeamIDs = whitelist protectBranch.ForcePushAllowlistTeamIDs = whitelist
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.DeletionAllowlistTeamIDs, opts.DeletionTeamIDs)
if err != nil {
return err
}
protectBranch.DeletionAllowlistTeamIDs = whitelist
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs) whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs)
if err != nil { if err != nil {
return err return err
@@ -577,17 +523,13 @@ func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, curre
whitelist = make([]int64, 0, len(newWhitelist)) whitelist = make([]int64, 0, len(newWhitelist))
for _, userID := range newWhitelist { for _, userID := range newWhitelist {
if userID == user_model.ActionsUserID || userID == user_model.DeployKeyUserID {
whitelist = append(whitelist, userID)
continue
}
user, err := user_model.GetUserByID(ctx, userID) user, err := user_model.GetUserByID(ctx, userID)
if err != nil { if err != nil {
return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %w", userID, repo.ID, err) return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
} }
perm, err := access_model.GetIndividualUserRepoPermission(ctx, repo, user) perm, err := access_model.GetIndividualUserRepoPermission(ctx, repo, user)
if err != nil { if err != nil {
return nil, fmt.Errorf("GetIndividualUserRepoPermission [user_id: %d, repo_id: %d]: %w", userID, repo.ID, err) return nil, fmt.Errorf("GetIndividualUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
} }
if !perm.CanWrite(unit.TypeCode) { if !perm.CanWrite(unit.TypeCode) {
@@ -645,14 +587,13 @@ func DeleteProtectedBranch(ctx context.Context, repo *repo_model.Repository, id
} }
// removeIDsFromProtectedBranch is a helper function to remove IDs from protected branch options // removeIDsFromProtectedBranch is a helper function to remove IDs from protected branch options
func removeIDsFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userID, teamID int64) error { func removeIDsFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userID, teamID int64, columnNames []string) error {
lenUserIDs, lenForcePushIDs, lenDeletionIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistUserIDs), len(p.ForcePushAllowlistUserIDs), len(p.DeletionAllowlistUserIDs), len(p.ApprovalsWhitelistUserIDs), len(p.MergeWhitelistUserIDs) lenUserIDs, lenForcePushIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistUserIDs), len(p.ForcePushAllowlistUserIDs), len(p.ApprovalsWhitelistUserIDs), len(p.MergeWhitelistUserIDs)
lenTeamIDs, lenForcePushTeamIDs, lenDeletionTeamIDs, lenApprovalTeamIDs, lenMergeTeamIDs := len(p.WhitelistTeamIDs), len(p.ForcePushAllowlistTeamIDs), len(p.DeletionAllowlistTeamIDs), len(p.ApprovalsWhitelistTeamIDs), len(p.MergeWhitelistTeamIDs) lenTeamIDs, lenForcePushTeamIDs, lenApprovalTeamIDs, lenMergeTeamIDs := len(p.WhitelistTeamIDs), len(p.ForcePushAllowlistTeamIDs), len(p.ApprovalsWhitelistTeamIDs), len(p.MergeWhitelistTeamIDs)
if userID > 0 { if userID > 0 {
p.WhitelistUserIDs = util.SliceRemoveAll(p.WhitelistUserIDs, userID) p.WhitelistUserIDs = util.SliceRemoveAll(p.WhitelistUserIDs, userID)
p.ForcePushAllowlistUserIDs = util.SliceRemoveAll(p.ForcePushAllowlistUserIDs, userID) p.ForcePushAllowlistUserIDs = util.SliceRemoveAll(p.ForcePushAllowlistUserIDs, userID)
p.DeletionAllowlistUserIDs = util.SliceRemoveAll(p.DeletionAllowlistUserIDs, userID)
p.ApprovalsWhitelistUserIDs = util.SliceRemoveAll(p.ApprovalsWhitelistUserIDs, userID) p.ApprovalsWhitelistUserIDs = util.SliceRemoveAll(p.ApprovalsWhitelistUserIDs, userID)
p.MergeWhitelistUserIDs = util.SliceRemoveAll(p.MergeWhitelistUserIDs, userID) p.MergeWhitelistUserIDs = util.SliceRemoveAll(p.MergeWhitelistUserIDs, userID)
} }
@@ -660,22 +601,19 @@ func removeIDsFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userI
if teamID > 0 { if teamID > 0 {
p.WhitelistTeamIDs = util.SliceRemoveAll(p.WhitelistTeamIDs, teamID) p.WhitelistTeamIDs = util.SliceRemoveAll(p.WhitelistTeamIDs, teamID)
p.ForcePushAllowlistTeamIDs = util.SliceRemoveAll(p.ForcePushAllowlistTeamIDs, teamID) p.ForcePushAllowlistTeamIDs = util.SliceRemoveAll(p.ForcePushAllowlistTeamIDs, teamID)
p.DeletionAllowlistTeamIDs = util.SliceRemoveAll(p.DeletionAllowlistTeamIDs, teamID)
p.ApprovalsWhitelistTeamIDs = util.SliceRemoveAll(p.ApprovalsWhitelistTeamIDs, teamID) p.ApprovalsWhitelistTeamIDs = util.SliceRemoveAll(p.ApprovalsWhitelistTeamIDs, teamID)
p.MergeWhitelistTeamIDs = util.SliceRemoveAll(p.MergeWhitelistTeamIDs, teamID) p.MergeWhitelistTeamIDs = util.SliceRemoveAll(p.MergeWhitelistTeamIDs, teamID)
} }
if (lenUserIDs != len(p.WhitelistUserIDs) || if (lenUserIDs != len(p.WhitelistUserIDs) ||
lenForcePushIDs != len(p.ForcePushAllowlistUserIDs) || lenForcePushIDs != len(p.ForcePushAllowlistUserIDs) ||
lenDeletionIDs != len(p.DeletionAllowlistUserIDs) ||
lenApprovalIDs != len(p.ApprovalsWhitelistUserIDs) || lenApprovalIDs != len(p.ApprovalsWhitelistUserIDs) ||
lenMergeIDs != len(p.MergeWhitelistUserIDs)) || lenMergeIDs != len(p.MergeWhitelistUserIDs)) ||
(lenTeamIDs != len(p.WhitelistTeamIDs) || (lenTeamIDs != len(p.WhitelistTeamIDs) ||
lenForcePushTeamIDs != len(p.ForcePushAllowlistTeamIDs) || lenForcePushTeamIDs != len(p.ForcePushAllowlistTeamIDs) ||
lenDeletionTeamIDs != len(p.DeletionAllowlistTeamIDs) ||
lenApprovalTeamIDs != len(p.ApprovalsWhitelistTeamIDs) || lenApprovalTeamIDs != len(p.ApprovalsWhitelistTeamIDs) ||
lenMergeTeamIDs != len(p.MergeWhitelistTeamIDs)) { lenMergeTeamIDs != len(p.MergeWhitelistTeamIDs)) {
if _, err := db.GetEngine(ctx).ID(p.ID).Cols("config").Update(p); err != nil { if _, err := db.GetEngine(ctx).ID(p.ID).Cols(columnNames...).Update(p); err != nil {
return fmt.Errorf("updateProtectedBranches: %v", err) return fmt.Errorf("updateProtectedBranches: %v", err)
} }
} }
@@ -684,18 +622,22 @@ func removeIDsFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userI
// RemoveUserIDFromProtectedBranch removes all user ids from protected branch options // RemoveUserIDFromProtectedBranch removes all user ids from protected branch options
func RemoveUserIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userID int64) error { func RemoveUserIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userID int64) error {
return removeIDsFromProtectedBranch(ctx, p, userID, 0) columnNames := []string{
"whitelist_user_i_ds",
"force_push_allowlist_user_i_ds",
"merge_whitelist_user_i_ds",
"approvals_whitelist_user_i_ds",
}
return removeIDsFromProtectedBranch(ctx, p, userID, 0, columnNames)
} }
// RemoveTeamIDFromProtectedBranch removes all team ids from protected branch options // RemoveTeamIDFromProtectedBranch removes all team ids from protected branch options
func RemoveTeamIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, teamID int64) error { func RemoveTeamIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, teamID int64) error {
return removeIDsFromProtectedBranch(ctx, p, 0, teamID) columnNames := []string{
"whitelist_team_i_ds",
"force_push_allowlist_team_i_ds",
"merge_whitelist_team_i_ds",
"approvals_whitelist_team_i_ds",
} }
return removeIDsFromProtectedBranch(ctx, p, 0, teamID, columnNames)
func (protectBranch *ProtectedBranch) HasDeployKeyPush() bool {
return slices.Contains(protectBranch.WhitelistUserIDs, user_model.DeployKeyUserID)
}
func (protectBranch *ProtectedBranch) HasDeployKeyForcePush() bool {
return slices.Contains(protectBranch.ForcePushAllowlistUserIDs, user_model.DeployKeyUserID)
} }
+6 -12
View File
@@ -9,7 +9,6 @@ import (
"gitea.dev/models/db" "gitea.dev/models/db"
"gitea.dev/modules/glob" "gitea.dev/modules/glob"
"gitea.dev/modules/log"
"gitea.dev/modules/optional" "gitea.dev/modules/optional"
) )
@@ -56,17 +55,10 @@ func FindRepoProtectedBranchRules(ctx context.Context, repoID int64) (ProtectedB
// FindAllMatchedBranches find all matched branches // FindAllMatchedBranches find all matched branches
func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string) ([]string, error) { func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string) ([]string, error) {
results := make([]string, 0, 10) results := make([]string, 0, 10)
rule, err := glob.Compile(ruleName)
if err != nil {
log.Debug("Failed to compile rule %s: %v", ruleName, err)
return results, nil
}
const pageSize = 100
for page := 1; ; page++ { for page := 1; ; page++ {
branchNames, err := FindBranchNames(ctx, FindBranchOptions{ brancheNames, err := FindBranchNames(ctx, FindBranchOptions{
ListOptions: db.ListOptions{ ListOptions: db.ListOptions{
PageSize: pageSize, PageSize: 100,
Page: page, Page: page,
}, },
RepoID: repoID, RepoID: repoID,
@@ -75,12 +67,14 @@ func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, branch := range branchNames { rule := glob.MustCompile(ruleName)
for _, branch := range brancheNames {
if rule.Match(branch) { if rule.Match(branch) {
results = append(results, branch) results = append(results, branch)
} }
} }
if len(branchNames) < pageSize { if len(brancheNames) < 100 {
break break
} }
} }
-74
View File
@@ -7,15 +7,11 @@ import (
"testing" "testing"
"gitea.dev/models/db" "gitea.dev/models/db"
"gitea.dev/models/perm"
access_model "gitea.dev/models/perm/access"
repo_model "gitea.dev/models/repo" repo_model "gitea.dev/models/repo"
"gitea.dev/models/unit"
"gitea.dev/models/unittest" "gitea.dev/models/unittest"
user_model "gitea.dev/models/user" user_model "gitea.dev/models/user"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestBranchRuleMatch(t *testing.T) { func TestBranchRuleMatch(t *testing.T) {
@@ -206,73 +202,3 @@ func TestCanBypassBranchProtection(t *testing.T) {
// User does not bypass when not in allowlisted teams. // User does not bypass when not in allowlisted teams.
testBypass(t, false, pb, user, false) testBypass(t, false, pb, user, false)
} }
func TestProtectedBranchConfig(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
ctx := t.Context()
pb := &ProtectedBranch{
RepoID: 1, RuleName: "config-test",
ProtectedBranchConfig: ProtectedBranchConfig{
CanDelete: true, EnableDeletionAllowlist: true,
DeletionAllowlistUserIDs: []int64{2, 4, user_model.ActionsUserID, user_model.DeployKeyUserID}, DeletionAllowlistTeamIDs: []int64{1, 2},
CanPush: true, WhitelistUserIDs: []int64{2, 4}, WhitelistTeamIDs: []int64{1, 2}, StatusCheckContexts: []string{},
},
}
require.NoError(t, db.Insert(ctx, pb))
loaded := unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID})
assert.Equal(t, pb.ProtectedBranchConfig, loaded.ProtectedBranchConfig)
require.NoError(t, RemoveUserIDFromProtectedBranch(ctx, loaded, 2))
require.NoError(t, RemoveTeamIDFromProtectedBranch(ctx, loaded, 1))
loaded = unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID})
assert.Equal(t, []int64{4}, loaded.WhitelistUserIDs)
assert.Equal(t, []int64{2}, loaded.WhitelistTeamIDs)
assert.True(t, loaded.CanPush)
assert.True(t, loaded.CanDelete)
assert.True(t, loaded.EnableDeletionAllowlist)
assert.Equal(t, []int64{4, user_model.ActionsUserID, user_model.DeployKeyUserID}, loaded.DeletionAllowlistUserIDs)
assert.Equal(t, []int64{2}, loaded.DeletionAllowlistTeamIDs)
loaded.ProtectedBranchConfig = ProtectedBranchConfig{}
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
require.NoError(t, UpdateProtectBranch(ctx, repo, loaded, WhitelistOptions{}))
loaded = unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID})
assert.Equal(t, ProtectedBranchConfig{}, loaded.ProtectedBranchConfig)
}
func TestProtectedBranchCanUserDelete(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
writePermission := access_model.Permission{}
writePermission.SetUnitsWithDefaultAccessMode([]*repo_model.RepoUnit{{Type: unit.TypeCode}}, perm.AccessModeWrite)
readPermission := access_model.Permission{}
readPermission.SetUnitsWithDefaultAccessMode([]*repo_model.RepoUnit{{Type: unit.TypeCode}}, perm.AccessModeRead)
for _, user := range []*user_model.User{{ID: 2, Name: "user2"}, user_model.NewActionsUser(), user_model.NewDeployKeyUser()} {
t.Run(user.GetDisplayName(), func(t *testing.T) {
for _, tt := range []struct {
name string
config ProtectedBranchConfig
want bool
}{
{name: "disabled by default", config: ProtectedBranchConfig{CanPush: true}},
{name: "push disabled", config: ProtectedBranchConfig{CanDelete: true}},
{name: "unrestricted", config: ProtectedBranchConfig{CanPush: true, CanDelete: true}, want: true},
{name: "empty push allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true}},
{name: "not in push allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistUserIDs: []int64{1}}},
{name: "empty deletion allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true}},
{name: "not in deletion allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{1}}},
{name: "in both allowlists", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistUserIDs: []int64{user.ID}, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}}, want: true},
{name: "deletion allowlist does not grant push", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}}},
{name: "team member", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistTeamIDs: []int64{1}}, want: user.ID == 2},
{name: "push team member", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistTeamIDs: []int64{1}}, want: user.ID == 2},
{name: "not in team", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistTeamIDs: []int64{3}}},
{name: "user or team", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}, DeletionAllowlistTeamIDs: []int64{3}}, want: true},
} {
t.Run(tt.name, func(t *testing.T) {
pb := &ProtectedBranch{ProtectedBranchConfig: tt.config}
assert.Equal(t, tt.want, pb.CanUserDelete(t.Context(), user, writePermission))
assert.False(t, pb.CanUserDelete(t.Context(), user, readPermission))
assert.False(t, pb.CanUserDelete(t.Context(), nil, writePermission))
})
}
})
}
}
+1 -1
View File
@@ -188,5 +188,5 @@ func MakeIDsFromAPIAssigneesToAdd(ctx context.Context, oneAssignee string, multi
} }
// Get the IDs of all assignees // Get the IDs of all assignees
return user_model.GetDoerUserIDsByNames(ctx, requestAssignees) return user_model.GetUserIDsByNames(ctx, requestAssignees, false)
} }
+51 -18
View File
@@ -65,7 +65,7 @@ type CommentType int
const CommentTypeUndefined CommentType = -1 const CommentTypeUndefined CommentType = -1
const ( const (
CommentTypeComment CommentType = iota // 0 Plain comment, can be associated with "commit_sha" and a non-zero "line" CommentTypeComment CommentType = iota // 0 Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0)
CommentTypeReopen // 1 CommentTypeReopen // 1
CommentTypeClose // 2 CommentTypeClose // 2
@@ -254,56 +254,45 @@ type CommentMetaData struct {
// Comment represents a comment in commit and issue page. // Comment represents a comment in commit and issue page.
type Comment struct { type Comment struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
Type CommentType Type CommentType `xorm:"INDEX"`
PosterID int64 `xorm:"INDEX"` PosterID int64 `xorm:"INDEX"`
Poster *user_model.User `xorm:"-"` Poster *user_model.User `xorm:"-"`
OriginalAuthor string OriginalAuthor string
OriginalAuthorID int64 OriginalAuthorID int64
IssueID int64 `xorm:"INDEX"` IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"` Issue *Issue `xorm:"-"`
LabelID int64 LabelID int64
Label *Label `xorm:"-"` Label *Label `xorm:"-"`
AddedLabels []*Label `xorm:"-"` AddedLabels []*Label `xorm:"-"`
RemovedLabels []*Label `xorm:"-"` RemovedLabels []*Label `xorm:"-"`
OldProjectID int64 OldProjectID int64
ProjectID int64 ProjectID int64
OldProject *project_model.Project `xorm:"-"` OldProject *project_model.Project `xorm:"-"`
Project *project_model.Project `xorm:"-"` Project *project_model.Project `xorm:"-"`
OldMilestoneID int64 OldMilestoneID int64
MilestoneID int64 MilestoneID int64
OldMilestone *Milestone `xorm:"-"` OldMilestone *Milestone `xorm:"-"`
Milestone *Milestone `xorm:"-"` Milestone *Milestone `xorm:"-"`
TimeID int64 TimeID int64
Time *TrackedTime `xorm:"-"` Time *TrackedTime `xorm:"-"`
AssigneeID int64 AssigneeID int64
RemovedAssignee bool RemovedAssignee bool
Assignee *user_model.User `xorm:"-"` Assignee *user_model.User `xorm:"-"`
AssigneeTeamID int64 `xorm:"NOT NULL DEFAULT 0"` AssigneeTeamID int64 `xorm:"NOT NULL DEFAULT 0"`
AssigneeTeam *organization.Team `xorm:"-"` AssigneeTeam *organization.Team `xorm:"-"`
ResolveDoerID int64 ResolveDoerID int64
ResolveDoer *user_model.User `xorm:"-"` ResolveDoer *user_model.User `xorm:"-"`
OldTitle string OldTitle string
NewTitle string NewTitle string
OldRef string OldRef string
NewRef string NewRef string
DependentIssueID int64 `xorm:"index"` // This is used by issue_service.deleteIssue DependentIssueID int64 `xorm:"index"` // This is used by issue_service.deleteIssue
DependentIssue *Issue `xorm:"-"` DependentIssue *Issue `xorm:"-"`
CommitID int64
Line int64 // - previous line / + proposed line Line int64 // - previous line / + proposed line
TreePath string `xorm:"VARCHAR(4000)"` // SQLServer only supports up to 4000 TreePath string `xorm:"VARCHAR(4000)"` // SQLServer only supports up to 4000
Content string `xorm:"LONGTEXT"` Content string `xorm:"LONGTEXT"`
ContentVersion int `xorm:"NOT NULL DEFAULT 0"` ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
RenderedContent template.HTML `xorm:"-"` RenderedContent template.HTML `xorm:"-"`
@@ -335,12 +324,12 @@ type Comment struct {
RefAction references.XRefAction `xorm:"SMALLINT"` // What happens if RefIssueID resolves RefAction references.XRefAction `xorm:"SMALLINT"` // What happens if RefIssueID resolves
RefIsPull bool RefIsPull bool
CommentMetaData *CommentMetaData `xorm:"JSON TEXT"` // put all non-index metadata in a single field
RefRepo *repo_model.Repository `xorm:"-"` RefRepo *repo_model.Repository `xorm:"-"`
RefIssue *Issue `xorm:"-"` RefIssue *Issue `xorm:"-"`
RefComment *Comment `xorm:"-"` RefComment *Comment `xorm:"-"`
CommentMetaData *CommentMetaData `xorm:"JSON TEXT"` // put all non-index metadata in a single field
Commits []*git_model.SignCommitWithStatuses `xorm:"-"` Commits []*git_model.SignCommitWithStatuses `xorm:"-"`
OldCommit string `xorm:"-"` OldCommit string `xorm:"-"`
NewCommit string `xorm:"-"` NewCommit string `xorm:"-"`
@@ -725,7 +714,10 @@ func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository
if c.Reactions != nil { if c.Reactions != nil {
return nil return nil
} }
c.Reactions, _, err = FindReactions(ctx, FindReactionsOptions{CommentID: c.ID}) c.Reactions, _, err = FindReactions(ctx, FindReactionsOptions{
IssueID: c.IssueID,
CommentID: c.ID,
})
if err != nil { if err != nil {
return err return err
} }
@@ -857,6 +849,7 @@ func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment,
RemovedAssignee: opts.RemovedAssignee, RemovedAssignee: opts.RemovedAssignee,
AssigneeID: opts.AssigneeID, AssigneeID: opts.AssigneeID,
AssigneeTeamID: opts.AssigneeTeamID, AssigneeTeamID: opts.AssigneeTeamID,
CommitID: opts.CommitID,
CommitSHA: opts.CommitSHA, CommitSHA: opts.CommitSHA,
Line: opts.LineNum, Line: opts.LineNum,
Content: opts.Content, Content: opts.Content,
@@ -1022,6 +1015,7 @@ type CreateCommentOptions struct {
NewTitle string NewTitle string
OldRef string OldRef string
NewRef string NewRef string
CommitID int64
CommitSHA string CommitSHA string
Patch string Patch string
LineNum int64 LineNum int64
@@ -1207,7 +1201,7 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
return err return err
} }
return DeleteCommentAllReactions(ctx, comment.ID) return DeleteReaction(ctx, &ReactionOptions{CommentID: comment.ID})
} }
// UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id // UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id
@@ -1264,6 +1258,45 @@ func (c *Comment) GetExternalName() string { return c.OriginalAuthor }
// GetExternalID ExternalUserRemappable interface // GetExternalID ExternalUserRemappable interface
func (c *Comment) GetExternalID() int64 { return c.OriginalAuthorID } func (c *Comment) GetExternalID() int64 { return c.OriginalAuthorID }
// CountCommentTypeLabelWithEmptyLabel count label comments with empty label
func CountCommentTypeLabelWithEmptyLabel(ctx context.Context) (int64, error) {
return db.GetEngine(ctx).Where(builder.Eq{"type": CommentTypeLabel, "label_id": 0}).Count(new(Comment))
}
// FixCommentTypeLabelWithEmptyLabel count label comments with empty label
func FixCommentTypeLabelWithEmptyLabel(ctx context.Context) (int64, error) {
return db.GetEngine(ctx).Where(builder.Eq{"type": CommentTypeLabel, "label_id": 0}).Delete(new(Comment))
}
// CountCommentTypeLabelWithOutsideLabels count label comments with outside label
func CountCommentTypeLabelWithOutsideLabels(ctx context.Context) (int64, error) {
return db.GetEngine(ctx).Where("comment.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))", CommentTypeLabel).
Table("comment").
Join("inner", "label", "label.id = comment.label_id").
Join("inner", "issue", "issue.id = comment.issue_id ").
Join("inner", "repository", "issue.repo_id = repository.id").
Count()
}
// FixCommentTypeLabelWithOutsideLabels count label comments with outside label
func FixCommentTypeLabelWithOutsideLabels(ctx context.Context) (int64, error) {
res, err := db.GetEngine(ctx).Exec(`DELETE FROM comment WHERE comment.id IN (
SELECT il_too.id FROM (
SELECT com.id
FROM comment AS com
INNER JOIN label ON com.label_id = label.id
INNER JOIN issue on issue.id = com.issue_id
INNER JOIN repository ON issue.repo_id = repository.id
WHERE
com.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))
) AS il_too)`, CommentTypeLabel)
if err != nil {
return 0, err
}
return res.RowsAffected()
}
// HasOriginalAuthor returns if a comment was migrated and has an original author. // HasOriginalAuthor returns if a comment was migrated and has an original author.
func (c *Comment) HasOriginalAuthor() bool { func (c *Comment) HasOriginalAuthor() bool {
return c.OriginalAuthor != "" && c.OriginalAuthorID != 0 return c.OriginalAuthor != "" && c.OriginalAuthorID != 0
+126 -49
View File
@@ -56,17 +56,30 @@ func (comments CommentList) loadLabels(ctx context.Context) error {
return nil return nil
} }
commentLabels := make(map[int64]*Label, len(labelIDs)) commentLabels := make(map[int64]*Label, len(labelIDs))
err := db.QueryAndProcessByChunk(labelIDs, func(chunk []int64) (out []*Label, err error) { left := len(labelIDs)
err = db.GetEngine(ctx).In("id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(label *Label) error { rows, err := db.GetEngine(ctx).
commentLabels[label.ID] = label In("id", labelIDs[:limit]).
return nil Rows(new(Label))
})
if err != nil { if err != nil {
return err return err
} }
for rows.Next() {
var label Label
err = rows.Scan(&label)
if err != nil {
_ = rows.Close()
return err
}
commentLabels[label.ID] = &label
}
_ = rows.Close()
left -= limit
labelIDs = labelIDs[limit:]
}
for _, comment := range comments { for _, comment := range comments {
comment.Label = commentLabels[comment.LabelID] comment.Label = commentLabels[comment.LabelID]
} }
@@ -90,16 +103,18 @@ func (comments CommentList) loadMilestones(ctx context.Context) error {
} }
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
err := db.QueryAndProcessByChunk(milestoneIDs, func(chunk []int64) (out []*Milestone, err error) { left := len(milestoneIDs)
err = db.GetEngine(ctx).In("id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(milestone *Milestone) error { err := db.GetEngine(ctx).
milestoneMaps[milestone.ID] = milestone In("id", milestoneIDs[:limit]).
return nil Find(&milestoneMaps)
})
if err != nil { if err != nil {
return err return err
} }
left -= limit
milestoneIDs = milestoneIDs[limit:]
}
for _, comment := range comments { for _, comment := range comments {
comment.Milestone = milestoneMaps[comment.MilestoneID] comment.Milestone = milestoneMaps[comment.MilestoneID]
@@ -124,16 +139,18 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error {
} }
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
err := db.QueryAndProcessByChunk(milestoneIDs, func(chunk []int64) (out []*Milestone, err error) { left := len(milestoneIDs)
err = db.GetEngine(ctx).In("id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(milestone *Milestone) error { err := db.GetEngine(ctx).
milestoneMaps[milestone.ID] = milestone In("id", milestoneIDs[:limit]).
return nil Find(&milestoneMaps)
})
if err != nil { if err != nil {
return err return err
} }
left -= limit
milestoneIDs = milestoneIDs[limit:]
}
for _, issue := range comments { for _, issue := range comments {
issue.OldMilestone = milestoneMaps[issue.MilestoneID] issue.OldMilestone = milestoneMaps[issue.MilestoneID]
@@ -157,17 +174,32 @@ func (comments CommentList) loadAssignees(ctx context.Context) error {
return nil return nil
} }
assignees := make(map[int64]*user_model.User, len(assigneeIDs)) assignees := make(map[int64]*user_model.User, len(assigneeIDs))
err := db.QueryAndProcessByChunk(assigneeIDs, func(chunk []int64) (out []*user_model.User, err error) { left := len(assigneeIDs)
err = db.GetEngine(ctx).In("id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(user *user_model.User) error { rows, err := db.GetEngine(ctx).
assignees[user.ID] = user In("id", assigneeIDs[:limit]).
return nil Rows(new(user_model.User))
})
if err != nil { if err != nil {
return err return err
} }
for rows.Next() {
var user user_model.User
err = rows.Scan(&user)
if err != nil {
rows.Close()
return err
}
assignees[user.ID] = &user
}
_ = rows.Close()
left -= limit
assigneeIDs = assigneeIDs[limit:]
}
for _, comment := range comments { for _, comment := range comments {
if comment.AssigneeID <= 0 { if comment.AssigneeID <= 0 {
continue continue
@@ -214,17 +246,32 @@ func (comments CommentList) LoadIssues(ctx context.Context) error {
issueIDs := comments.getIssueIDs() issueIDs := comments.getIssueIDs()
issues := make(map[int64]*Issue, len(issueIDs)) issues := make(map[int64]*Issue, len(issueIDs))
err := db.QueryAndProcessByChunk(issueIDs, func(chunk []int64) (out []*Issue, err error) { left := len(issueIDs)
err = db.GetEngine(ctx).In("id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(issue *Issue) error { rows, err := db.GetEngine(ctx).
issues[issue.ID] = issue In("id", issueIDs[:limit]).
return nil Rows(new(Issue))
})
if err != nil { if err != nil {
return err return err
} }
for rows.Next() {
var issue Issue
err = rows.Scan(&issue)
if err != nil {
rows.Close()
return err
}
issues[issue.ID] = &issue
}
_ = rows.Close()
left -= limit
issueIDs = issueIDs[limit:]
}
for _, comment := range comments { for _, comment := range comments {
if comment.Issue == nil { if comment.Issue == nil {
comment.Issue = issues[comment.IssueID] comment.Issue = issues[comment.IssueID]
@@ -247,22 +294,38 @@ func (comments CommentList) loadDependentIssues(ctx context.Context) error {
return nil return nil
} }
e := db.GetEngine(ctx)
issueIDs := comments.getDependentIssueIDs() issueIDs := comments.getDependentIssueIDs()
if len(issueIDs) == 0 { if len(issueIDs) == 0 {
return nil return nil
} }
issues := make(map[int64]*Issue, len(issueIDs)) issues := make(map[int64]*Issue, len(issueIDs))
err := db.QueryAndProcessByChunk(issueIDs, func(chunk []int64) (out []*Issue, err error) { left := len(issueIDs)
err = db.GetEngine(ctx).In("id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(issue *Issue) error { rows, err := e.
issues[issue.ID] = issue In("id", issueIDs[:limit]).
return nil Rows(new(Issue))
})
if err != nil { if err != nil {
return err return err
} }
for rows.Next() {
var issue Issue
err = rows.Scan(&issue)
if err != nil {
_ = rows.Close()
return err
}
issues[issue.ID] = &issue
}
_ = rows.Close()
left -= limit
issueIDs = issueIDs[limit:]
}
for _, comment := range comments { for _, comment := range comments {
if comment.DependentIssue == nil { if comment.DependentIssue == nil {
comment.DependentIssue = issues[comment.DependentIssueID] comment.DependentIssue = issues[comment.DependentIssueID]
@@ -313,17 +376,31 @@ func (comments CommentList) LoadAttachments(ctx context.Context) (err error) {
attachments := make(map[int64][]*repo_model.Attachment, len(comments)) attachments := make(map[int64][]*repo_model.Attachment, len(comments))
commentsIDs := comments.getAttachmentCommentIDs() commentsIDs := comments.getAttachmentCommentIDs()
err = db.QueryAndProcessByChunk(commentsIDs, func(chunk []int64) (out []*repo_model.Attachment, err error) { left := len(commentsIDs)
err = db.GetEngine(ctx).In("comment_id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(attachment *repo_model.Attachment) error { rows, err := db.GetEngine(ctx).
attachments[attachment.CommentID] = append(attachments[attachment.CommentID], attachment) In("comment_id", commentsIDs[:limit]).
return nil Rows(new(repo_model.Attachment))
})
if err != nil { if err != nil {
return err return err
} }
for rows.Next() {
var attachment repo_model.Attachment
err = rows.Scan(&attachment)
if err != nil {
_ = rows.Close()
return err
}
attachments[attachment.CommentID] = append(attachments[attachment.CommentID], &attachment)
}
_ = rows.Close()
left -= limit
commentsIDs = commentsIDs[limit:]
}
for _, comment := range comments { for _, comment := range comments {
comment.Attachments = attachments[comment.ID] comment.Attachments = attachments[comment.ID]
} }
+2 -48
View File
@@ -14,7 +14,6 @@ import (
user_model "gitea.dev/models/user" user_model "gitea.dev/models/user"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestCreateComment(t *testing.T) { func TestCreateComment(t *testing.T) {
@@ -60,62 +59,17 @@ func TestLoadAssigneeUserAndTeam_DeletedTeamBecomesGhostTeam(t *testing.T) {
} }
func TestCommentListLoadAttributesMixedAssignees(t *testing.T) { func TestCommentListLoadAttributesMixedAssignees(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
first := &issues_model.Comment{
ID: 1, PosterID: 1, IssueID: 1, LabelID: 1, MilestoneID: 1, OldMilestoneID: 1, AssigneeID: 1, DependentIssueID: 2,
}
last := &issues_model.Comment{
ID: 2, PosterID: 2, IssueID: 4, LabelID: 2, MilestoneID: 3, OldMilestoneID: 3, AssigneeID: 2, DependentIssueID: 1,
}
comments := issues_model.CommentList{ comments := issues_model.CommentList{
{AssigneeTeamID: 8}, {AssigneeTeamID: 8},
{AssigneeID: 999999}, {AssigneeID: 999999},
first,
} }
for i := range db.DefaultMaxInSize {
id := unittest.NonexistentID - int64(i)
comments = append(comments, &issues_model.Comment{
ID: id, PosterID: id, IssueID: id, LabelID: id, MilestoneID: id, OldMilestoneID: id, AssigneeID: id, DependentIssueID: id,
})
}
comments = append(comments, last)
require.NoError(t, comments.LoadAttributes(t.Context())) assert.NoError(t, comments.LoadAttributes(t.Context()))
assert.Nil(t, comments[0].Assignee) assert.Nil(t, comments[0].Assignee)
assert.Zero(t, comments[0].AssigneeID) assert.Zero(t, comments[0].AssigneeID)
assert.Equal(t, user_model.GhostUserID, comments[1].AssigneeID) assert.Equal(t, user_model.GhostUserID, comments[1].AssigneeID)
assert.Equal(t, user_model.GhostUserID, comments[1].Assignee.ID) assert.Equal(t, user_model.GhostUserID, comments[1].Assignee.ID)
for _, comment := range []*issues_model.Comment{first, last} {
require.NotNil(t, comment.Poster)
assert.Equal(t, comment.PosterID, comment.Poster.ID)
require.NotNil(t, comment.Issue)
assert.Equal(t, comment.IssueID, comment.Issue.ID)
require.NotNil(t, comment.Label)
assert.Equal(t, comment.LabelID, comment.Label.ID)
require.NotNil(t, comment.Milestone)
assert.Equal(t, comment.MilestoneID, comment.Milestone.ID)
require.NotNil(t, comment.OldMilestone)
assert.Equal(t, comment.OldMilestoneID, comment.OldMilestone.ID)
require.NotNil(t, comment.Assignee)
assert.Equal(t, comment.AssigneeID, comment.Assignee.ID)
require.NotNil(t, comment.DependentIssue)
assert.Equal(t, comment.DependentIssueID, comment.DependentIssue.ID)
require.NotNil(t, comment.DependentIssue.Repo)
assert.Equal(t, comment.DependentIssue.RepoID, comment.DependentIssue.Repo.ID)
require.Len(t, comment.Attachments, 2)
for _, attachment := range comment.Attachments {
assert.Equal(t, comment.ID, attachment.CommentID)
}
}
missing := comments[3]
assert.Equal(t, user_model.GhostUserID, missing.Poster.ID)
assert.Equal(t, user_model.GhostUserID, missing.Assignee.ID)
assert.Nil(t, missing.Issue)
assert.Nil(t, missing.Label)
assert.Nil(t, missing.Milestone)
assert.Nil(t, missing.OldMilestone)
assert.Nil(t, missing.DependentIssue)
assert.Empty(t, missing.Attachments)
} }
func Test_UpdateCommentAttachment(t *testing.T) { func Test_UpdateCommentAttachment(t *testing.T) {
+6 -4
View File
@@ -81,7 +81,7 @@ type Issue struct {
PullRequest *PullRequest `xorm:"-"` PullRequest *PullRequest `xorm:"-"`
NumComments int NumComments int
// TODO: ISSUE-SIDEBAR-REF-SELECTOR: see "repo/issue/branch_selector_field.tmpl" // TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl"
Ref string Ref string
PinOrder int `xorm:"-"` // 0 means not loaded, -1 means loaded but not pinned PinOrder int `xorm:"-"` // 0 means not loaded, -1 means loaded but not pinned
@@ -225,11 +225,13 @@ func (issue *Issue) loadCommentsByType(ctx context.Context, tp CommentType) (err
return err return err
} }
func (issue *Issue) loadAllReactions(ctx context.Context) (err error) { func (issue *Issue) loadReactions(ctx context.Context) (err error) {
if issue.Reactions != nil { if issue.Reactions != nil {
return nil return nil
} }
reactions, _, err := FindReactions(ctx, FindReactionsOptions{IssueIDWithComments: issue.ID}) reactions, _, err := FindReactions(ctx, FindReactionsOptions{
IssueID: issue.ID,
})
if err != nil { if err != nil {
return err return err
} }
@@ -338,7 +340,7 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) {
} }
} }
return issue.loadAllReactions(ctx) return issue.loadReactions(ctx)
} }
// IsPinned returns if a Issue is pinned // IsPinned returns if a Issue is pinned
+29
View File
@@ -282,6 +282,35 @@ func DeleteOrphanedIssueLabels(ctx context.Context) error {
return err return err
} }
// CountIssueLabelWithOutsideLabels count label comments with outside label
func CountIssueLabelWithOutsideLabels(ctx context.Context) (int64, error) {
return db.GetEngine(ctx).Where(builder.Expr("(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)")).
Table("issue_label").
Join("inner", "label", "issue_label.label_id = label.id ").
Join("inner", "issue", "issue.id = issue_label.issue_id ").
Join("inner", "repository", "issue.repo_id = repository.id").
Count(new(IssueLabel))
}
// FixIssueLabelWithOutsideLabels fix label comments with outside label
func FixIssueLabelWithOutsideLabels(ctx context.Context) (int64, error) {
res, err := db.GetEngine(ctx).Exec(`DELETE FROM issue_label WHERE issue_label.id IN (
SELECT il_too.id FROM (
SELECT il_too_too.id
FROM issue_label AS il_too_too
INNER JOIN label ON il_too_too.label_id = label.id
INNER JOIN issue on issue.id = il_too_too.issue_id
INNER JOIN repository on repository.id = issue.repo_id
WHERE
(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
) AS il_too )`)
if err != nil {
return 0, err
}
return res.RowsAffected()
}
// LoadLabels loads labels // LoadLabels loads labels
func (issue *Issue) LoadLabels(ctx context.Context) (err error) { func (issue *Issue) LoadLabels(ctx context.Context) (err error) {
if !issue.isLabelsLoaded && issue.Labels == nil && issue.ID != 0 { if !issue.isLabelsLoaded && issue.Labels == nil && issue.ID != 0 {
+187 -83
View File
@@ -40,16 +40,18 @@ func (issues IssueList) LoadRepositories(ctx context.Context) (repo_model.Reposi
repoIDs := issues.getRepoIDs() repoIDs := issues.getRepoIDs()
repoMaps := make(map[int64]*repo_model.Repository, len(repoIDs)) repoMaps := make(map[int64]*repo_model.Repository, len(repoIDs))
err := db.QueryAndProcessByChunk(repoIDs, func(chunk []int64) (out []*repo_model.Repository, err error) { left := len(repoIDs)
err = db.GetEngine(ctx).In("id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(repo *repo_model.Repository) error { err := db.GetEngine(ctx).
repoMaps[repo.ID] = repo In("id", repoIDs[:limit]).
return nil Find(&repoMaps)
})
if err != nil { if err != nil {
return nil, fmt.Errorf("find repository: %w", err) return nil, fmt.Errorf("find repository: %w", err)
} }
left -= limit
repoIDs = repoIDs[limit:]
}
for _, issue := range issues { for _, issue := range issues {
if issue.Repo == nil { if issue.Repo == nil {
@@ -109,20 +111,36 @@ func (issues IssueList) LoadLabels(ctx context.Context) error {
issueLabels := make(map[int64][]*Label, len(issues)*3) issueLabels := make(map[int64][]*Label, len(issues)*3)
issueIDs := issues.getIssueIDs() issueIDs := issues.getIssueIDs()
left := len(issueIDs)
err := db.QueryAndProcessByChunk(issueIDs, func(chunk []int64) (out []*LabelIssue, err error) { for left > 0 {
err = db.GetEngine(ctx).Table("label"). limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).Table("label").
Join("LEFT", "issue_label", "issue_label.label_id = label.id"). Join("LEFT", "issue_label", "issue_label.label_id = label.id").
In("issue_label.issue_id", chunk). In("issue_label.issue_id", issueIDs[:limit]).
Asc("label.name"). Asc("label.name").
Find(&out) Rows(new(LabelIssue))
return out, err
}, func(out *LabelIssue) error {
issueLabels[out.IssueLabel.IssueID] = append(issueLabels[out.IssueLabel.IssueID], out.Label)
return nil
})
if err != nil { if err != nil {
return fmt.Errorf("IssueList.LoadLabels: %w", err) return err
}
for rows.Next() {
var labelIssue LabelIssue
err = rows.Scan(&labelIssue)
if err != nil {
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.LoadLabels: Close: %w", err1)
}
return err
}
issueLabels[labelIssue.IssueLabel.IssueID] = append(issueLabels[labelIssue.IssueLabel.IssueID], labelIssue.Label)
}
// When there are no rows left and we try to close it.
// Since that is not relevant for us, we can safely ignore it.
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.LoadLabels: Close: %w", err1)
}
left -= limit
issueIDs = issueIDs[limit:]
} }
for _, issue := range issues { for _, issue := range issues {
@@ -145,16 +163,18 @@ func (issues IssueList) LoadMilestones(ctx context.Context) error {
} }
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
err := db.QueryAndProcessByChunk(milestoneIDs, func(chunk []int64) (out []*Milestone, err error) { left := len(milestoneIDs)
err = db.GetEngine(ctx).In("id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(milestone *Milestone) error { err := db.GetEngine(ctx).
milestoneMaps[milestone.ID] = milestone In("id", milestoneIDs[:limit]).
return nil Find(&milestoneMaps)
})
if err != nil { if err != nil {
return err return err
} }
left -= limit
milestoneIDs = milestoneIDs[limit:]
}
for _, issue := range issues { for _, issue := range issues {
issue.Milestone = milestoneMaps[issue.MilestoneID] issue.Milestone = milestoneMaps[issue.MilestoneID]
@@ -166,27 +186,32 @@ func (issues IssueList) LoadMilestones(ctx context.Context) error {
func (issues IssueList) LoadProjects(ctx context.Context) error { func (issues IssueList) LoadProjects(ctx context.Context) error {
issueIDs := issues.getIssueIDs() issueIDs := issues.getIssueIDs()
issueProjectMaps := make(map[int64][]*project_model.Project, len(issues)) issueProjectMaps := make(map[int64][]*project_model.Project, len(issues))
left := len(issueIDs)
type projectWithIssueID struct { type projectWithIssueID struct {
*project_model.Project `xorm:"extends"` *project_model.Project `xorm:"extends"`
IssueID int64 IssueID int64
} }
err := db.QueryAndProcessByChunk(issueIDs, func(chunk []int64) (out []*projectWithIssueID, err error) { for left > 0 {
err = db.GetEngine(ctx). limit := min(left, db.DefaultMaxInSize)
projects := make([]*projectWithIssueID, 0, limit)
err := db.GetEngine(ctx).
Table("project"). Table("project").
Select("project.*, project_issue.issue_id"). Select("project.*, project_issue.issue_id").
Join("INNER", "project_issue", "project.id = project_issue.project_id"). Join("INNER", "project_issue", "project.id = project_issue.project_id").
In("project_issue.issue_id", chunk). In("project_issue.issue_id", issueIDs[:limit]).
OrderBy("project_issue.issue_id ASC, project.id ASC"). OrderBy("project_issue.issue_id ASC, project.id ASC").
Find(&out) Find(&projects)
return out, err
}, func(project *projectWithIssueID) error {
issueProjectMaps[project.IssueID] = append(issueProjectMaps[project.IssueID], project.Project)
return nil
})
if err != nil { if err != nil {
return fmt.Errorf("IssueList.LoadProjects: %w", err) return err
}
for _, project := range projects {
issueProjectMaps[project.IssueID] = append(issueProjectMaps[project.IssueID], project.Project)
}
left -= limit
issueIDs = issueIDs[limit:]
} }
for _, issue := range issues { for _, issue := range issues {
@@ -208,18 +233,34 @@ func (issues IssueList) LoadAssignees(ctx context.Context) error {
assignees := make(map[int64][]*user_model.User, len(issues)) assignees := make(map[int64][]*user_model.User, len(issues))
issueIDs := issues.getIssueIDs() issueIDs := issues.getIssueIDs()
err := db.QueryAndProcessByChunk(issueIDs, func(chunk []int64) (out []*AssigneeIssue, err error) { left := len(issueIDs)
err = db.GetEngine(ctx).Table("issue_assignees"). for left > 0 {
limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).Table("issue_assignees").
Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id"). Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id").
In("`issue_assignees`.issue_id", chunk).OrderBy(user_model.GetOrderByName()). In("`issue_assignees`.issue_id", issueIDs[:limit]).OrderBy(user_model.GetOrderByName()).
Find(&out) Rows(new(AssigneeIssue))
return out, err
}, func(assigneeIssue *AssigneeIssue) error {
assignees[assigneeIssue.IssueAssignee.IssueID] = append(assignees[assigneeIssue.IssueAssignee.IssueID], assigneeIssue.Assignee)
return nil
})
if err != nil { if err != nil {
return fmt.Errorf("IssueList.LoadAssignees: %w", err) return err
}
for rows.Next() {
var assigneeIssue AssigneeIssue
err = rows.Scan(&assigneeIssue)
if err != nil {
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadAssignees: Close: %w", err1)
}
return err
}
assignees[assigneeIssue.IssueAssignee.IssueID] = append(assignees[assigneeIssue.IssueAssignee.IssueID], assigneeIssue.Assignee)
}
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadAssignees: Close: %w", err1)
}
left -= limit
issueIDs = issueIDs[limit:]
} }
for _, issue := range issues { for _, issue := range issues {
@@ -250,17 +291,32 @@ func (issues IssueList) LoadPullRequests(ctx context.Context) error {
} }
pullRequestMaps := make(map[int64]*PullRequest, len(issuesIDs)) pullRequestMaps := make(map[int64]*PullRequest, len(issuesIDs))
err := db.QueryAndProcessByChunk(issuesIDs, func(chunk []int64) (out []*PullRequest, err error) { left := len(issuesIDs)
err = db.GetEngine(ctx). for left > 0 {
In("issue_id", chunk). limit := min(left, db.DefaultMaxInSize)
Find(&out) rows, err := db.GetEngine(ctx).
return out, err In("issue_id", issuesIDs[:limit]).
}, func(pr *PullRequest) error { Rows(new(PullRequest))
pullRequestMaps[pr.IssueID] = pr
return nil
})
if err != nil { if err != nil {
return fmt.Errorf("IssueList.LoadPullRequests: %w", err) return err
}
for rows.Next() {
var pr PullRequest
err = rows.Scan(&pr)
if err != nil {
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadPullRequests: Close: %w", err1)
}
return err
}
pullRequestMaps[pr.IssueID] = &pr
}
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadPullRequests: Close: %w", err1)
}
left -= limit
issuesIDs = issuesIDs[limit:]
} }
for _, issue := range issues { for _, issue := range issues {
@@ -280,15 +336,32 @@ func (issues IssueList) LoadAttachments(ctx context.Context) (err error) {
attachments := make(map[int64][]*repo_model.Attachment, len(issues)) attachments := make(map[int64][]*repo_model.Attachment, len(issues))
issuesIDs := issues.getIssueIDs() issuesIDs := issues.getIssueIDs()
err = db.QueryAndProcessByChunk(issuesIDs, func(chunk []int64) (out []*repo_model.Attachment, err error) { left := len(issuesIDs)
err = db.GetEngine(ctx).In("issue_id", chunk).Find(&out) for left > 0 {
return out, err limit := min(left, db.DefaultMaxInSize)
}, func(attachment *repo_model.Attachment) error { rows, err := db.GetEngine(ctx).
attachments[attachment.IssueID] = append(attachments[attachment.IssueID], attachment) In("issue_id", issuesIDs[:limit]).
return nil Rows(new(repo_model.Attachment))
})
if err != nil { if err != nil {
return fmt.Errorf("IssueList.LoadAttachments: %w", err) return err
}
for rows.Next() {
var attachment repo_model.Attachment
err = rows.Scan(&attachment)
if err != nil {
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadAttachments: Close: %w", err1)
}
return err
}
attachments[attachment.IssueID] = append(attachments[attachment.IssueID], &attachment)
}
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadAttachments: Close: %w", err1)
}
left -= limit
issuesIDs = issuesIDs[limit:]
} }
for _, issue := range issues { for _, issue := range issues {
@@ -305,20 +378,35 @@ func (issues IssueList) loadComments(ctx context.Context, cond builder.Cond) (er
comments := make(map[int64][]*Comment, len(issues)) comments := make(map[int64][]*Comment, len(issues))
issuesIDs := issues.getIssueIDs() issuesIDs := issues.getIssueIDs()
err = db.QueryAndProcessByChunk(issuesIDs, func(chunk []int64) (out []*Comment, err error) { left := len(issuesIDs)
err = db.GetEngine(ctx).Table("comment"). for left > 0 {
limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).Table("comment").
Join("INNER", "issue", "issue.id = comment.issue_id"). Join("INNER", "issue", "issue.id = comment.issue_id").
In("issue.id", chunk). In("issue.id", issuesIDs[:limit]).
Where(cond). Where(cond).
NoAutoCondition(). NoAutoCondition().
Find(&out) Rows(new(Comment))
return out, err
}, func(comment *Comment) error {
comments[comment.IssueID] = append(comments[comment.IssueID], comment)
return nil
})
if err != nil { if err != nil {
return fmt.Errorf("IssueList.loadComments: %w", err) return err
}
for rows.Next() {
var comment Comment
err = rows.Scan(&comment)
if err != nil {
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadComments: Close: %w", err1)
}
return err
}
comments[comment.IssueID] = append(comments[comment.IssueID], &comment)
}
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadComments: Close: %w", err1)
}
left -= limit
issuesIDs = issuesIDs[limit:]
} }
for _, issue := range issues { for _, issue := range issues {
@@ -354,21 +442,37 @@ func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) {
} }
} }
err = db.QueryAndProcessByChunk(ids, func(chunk []int64) (out []*totalTimesByIssue, err error) { left := len(ids)
for left > 0 {
limit := min(left, db.DefaultMaxInSize)
// select issue_id, sum(time) from tracked_time where issue_id in (<issue ids in current page>) group by issue_id // select issue_id, sum(time) from tracked_time where issue_id in (<issue ids in current page>) group by issue_id
err = db.GetEngine(ctx).Table("tracked_time"). rows, err := db.GetEngine(ctx).Table("tracked_time").
Where("deleted = ?", false). Where("deleted = ?", false).
Select("issue_id, sum(time) as time"). Select("issue_id, sum(time) as time").
In("issue_id", chunk). In("issue_id", ids[:limit]).
GroupBy("issue_id"). GroupBy("issue_id").
Find(&out) Rows(new(totalTimesByIssue))
return out, err
}, func(totalTime *totalTimesByIssue) error {
trackedTimes[totalTime.IssueID] = totalTime.Time
return nil
})
if err != nil { if err != nil {
return fmt.Errorf("IssueList.loadTotalTrackedTimes: %w", err) return err
}
for rows.Next() {
var totalTime totalTimesByIssue
err = rows.Scan(&totalTime)
if err != nil {
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %w", err1)
}
return err
}
trackedTimes[totalTime.IssueID] = totalTime.Time
}
if err1 := rows.Close(); err1 != nil {
return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %w", err1)
}
left -= limit
ids = ids[limit:]
} }
for _, issue := range issues { for _, issue := range issues {
+7 -43
View File
@@ -6,11 +6,9 @@ package issues_test
import ( import (
"testing" "testing"
"gitea.dev/models/db"
issues_model "gitea.dev/models/issues" issues_model "gitea.dev/models/issues"
"gitea.dev/models/unittest" "gitea.dev/models/unittest"
"gitea.dev/modules/setting" "gitea.dev/modules/setting"
"gitea.dev/modules/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -50,36 +48,18 @@ func TestIssueList_LoadIsRead(t *testing.T) {
} }
func TestIssueList_LoadAttributes(t *testing.T) { func TestIssueList_LoadAttributes(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
defer test.MockVariableValue(&setting.Service.EnableTimetracking, true)() setting.Service.EnableTimetracking = true
fixtureIssues := issues_model.IssueList{ issueList := issues_model.IssueList{
unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}), unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}),
unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}),
unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}), unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}),
} }
issueList := issues_model.IssueList{fixtureIssues[0]}
for i := range db.DefaultMaxInSize {
issueList = append(issueList, &issues_model.Issue{ID: int64(1000 + i), RepoID: 1, IsPull: true})
}
issueList = append(issueList, fixtureIssues[1:]...)
require.NoError(t, issueList.LoadAttributes(t.Context())) assert.NoError(t, issueList.LoadAttributes(t.Context()))
require.NoError(t, issueList.LoadAttachments(t.Context())) for _, issue := range issueList {
require.NoError(t, issueList.LoadComments(t.Context()))
assert.Len(t, fixtureIssues[0].Labels, 1)
assert.Len(t, fixtureIssues[1].Labels, 2)
assert.Len(t, fixtureIssues[0].Assignees, 1)
assert.Empty(t, fixtureIssues[1].Assignees)
assert.Len(t, fixtureIssues[0].Attachments, 1)
assert.Len(t, fixtureIssues[1].Attachments, 1)
assert.Len(t, fixtureIssues[2].Attachments, 2)
assert.Len(t, fixtureIssues[0].Comments, 3)
assert.Len(t, fixtureIssues[1].Comments, 4)
assert.Len(t, fixtureIssues[2].Comments, 1)
for _, issue := range fixtureIssues {
assert.Equal(t, issue.RepoID, issue.Repo.ID) assert.Equal(t, issue.RepoID, issue.Repo.ID)
for _, label := range issue.Labels { for _, label := range issue.Labels {
assert.Equal(t, unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: label.ID}), label) assert.Equal(t, issue.RepoID, label.RepoID)
unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label.ID}) unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label.ID})
} }
if issue.PosterID > 0 { if issue.PosterID > 0 {
@@ -92,9 +72,7 @@ func TestIssueList_LoadAttributes(t *testing.T) {
assert.Equal(t, issue.MilestoneID, issue.Milestone.ID) assert.Equal(t, issue.MilestoneID, issue.Milestone.ID)
} }
if issue.IsPull { if issue.IsPull {
require.NotNil(t, issue.PullRequest)
assert.Equal(t, issue.ID, issue.PullRequest.IssueID) assert.Equal(t, issue.ID, issue.PullRequest.IssueID)
assert.Same(t, issue, issue.PullRequest.Issue)
} }
for _, attachment := range issue.Attachments { for _, attachment := range issue.Attachments {
assert.Equal(t, issue.ID, attachment.IssueID) assert.Equal(t, issue.ID, attachment.IssueID)
@@ -104,24 +82,10 @@ func TestIssueList_LoadAttributes(t *testing.T) {
} }
if issue.ID == int64(1) { if issue.ID == int64(1) {
assert.Equal(t, int64(400), issue.TotalTrackedTime) assert.Equal(t, int64(400), issue.TotalTrackedTime)
} assert.NotEmpty(t, issue.Projects)
if issue.ID == 1 || issue.ID == 2 {
require.Len(t, issue.Projects, 1)
assert.Equal(t, int64(1), issue.Projects[0].ID) assert.Equal(t, int64(1), issue.Projects[0].ID)
} else { } else {
assert.Empty(t, issue.Projects) assert.Empty(t, issue.Projects)
} }
} }
assert.Equal(t, int64(3682), fixtureIssues[1].TotalTrackedTime)
require.NoError(t, issueList.LoadDiscussComments(t.Context()))
assert.Len(t, fixtureIssues[0].Comments, 2)
assert.Empty(t, fixtureIssues[1].Comments)
assert.Len(t, fixtureIssues[2].Comments, 1)
for _, issue := range fixtureIssues {
for _, comment := range issue.Comments {
assert.Equal(t, issue.ID, comment.IssueID)
assert.Equal(t, issues_model.CommentTypeComment, comment.Type)
}
}
} }
+1 -2
View File
@@ -283,7 +283,6 @@ func applyConditions(sess db.Session, opts *IssuesOptions) {
sess.And(repo_model.UserOwnedRepoCond(opts.Owner.ID)) sess.And(repo_model.UserOwnedRepoCond(opts.Owner.ID))
} }
sess.And(repo_model.DoerTokenRepoCond(opts.Doer))
if opts.Doer != nil && !opts.Doer.IsAdmin { if opts.Doer != nil && !opts.Doer.IsAdmin {
sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.Doer.ID, opts.Owner, opts.Team, opts.IsPull.Value())) sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.Doer.ID, opts.Owner, opts.Team, opts.IsPull.Value()))
} }
@@ -478,7 +477,7 @@ func applySubscribedCondition(sess db.Session, subscriberID int64) {
Select("repo_id"). Select("repo_id").
From("watch"). From("watch").
Where(builder.And(builder.Eq{"user_id": subscriberID}, Where(builder.And(builder.Eq{"user_id": subscriberID},
builder.In("mode", repo_model.WatchModeNormal, repo_model.WatchModeAuto, repo_model.WatchModeCustom))), builder.In("mode", repo_model.WatchModeNormal, repo_model.WatchModeAuto))),
), ),
), ),
) )
-12
View File
@@ -16,7 +16,6 @@ import (
repo_model "gitea.dev/models/repo" repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest" "gitea.dev/models/unittest"
user_model "gitea.dev/models/user" user_model "gitea.dev/models/user"
"gitea.dev/modules/optional"
"gitea.dev/modules/setting" "gitea.dev/modules/setting"
"gitea.dev/modules/test" "gitea.dev/modules/test"
@@ -151,8 +150,6 @@ func TestUpdateIssueCols(t *testing.T) {
func TestIssues(t *testing.T) { func TestIssues(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
publicOnlyUser2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
publicOnlyUser2.ExtDoerData = user_model.NewTokenExtDoerData(user_model.CredentialAccessToken, 1, "public-only,read:issue")
for _, test := range []struct { for _, test := range []struct {
Opts issues_model.IssuesOptions Opts issues_model.IssuesOptions
ExpectedIssueIDs []int64 ExpectedIssueIDs []int64
@@ -207,15 +204,6 @@ func TestIssues(t *testing.T) {
}, },
[]int64{11, 5, 9, 8, 3, 2, 1}, []int64{11, 5, 9, 8, 3, 2, 1},
}, },
{
issues_model.IssuesOptions{
RepoCond: builder.In("repo_id", 1, 2),
Doer: publicOnlyUser2,
IsPull: optional.Some(false),
SortType: "oldest",
},
[]int64{1, 5},
},
} { } {
issues, err := issues_model.Issues(t.Context(), &test.Opts) issues, err := issues_model.Issues(t.Context(), &test.Opts)
assert.NoError(t, err) assert.NoError(t, err)
+22 -20
View File
@@ -269,23 +269,27 @@ func UpdateLabel(ctx context.Context, l *Label) error {
return updateLabelCols(ctx, l, "name", "description", "color", "exclusive", "exclusive_order", "archived_unix") return updateLabelCols(ctx, l, "name", "description", "color", "exclusive", "exclusive_order", "archived_unix")
} }
// DeleteRepoLabel deletes a repository label. // DeleteLabel delete a label
func DeleteRepoLabel(ctx context.Context, repoID, labelID int64) error { func DeleteLabel(ctx context.Context, id, labelID int64) error {
return deleteLabel(ctx, labelID, builder.Eq{"repo_id": repoID, "org_id": 0}) l, err := GetLabelByID(ctx, labelID)
if err != nil {
if IsErrLabelNotExist(err) {
return nil
} }
// DeleteOrgLabel deletes an organization label.
func DeleteOrgLabel(ctx context.Context, orgID, labelID int64) error {
return deleteLabel(ctx, labelID, builder.Eq{"org_id": orgID, "repo_id": 0})
}
func deleteLabel(ctx context.Context, labelID int64, ownerCond builder.Cond) error {
return db.WithTx(ctx, func(ctx context.Context) error {
deleted, err := db.GetEngine(ctx).ID(labelID).Where(ownerCond).Delete(new(Label))
if err != nil || deleted == 0 {
return err return err
} }
if _, err = db.GetEngine(ctx).
return db.WithTx(ctx, func(ctx context.Context) error {
if l.BelongsToOrg() && l.OrgID != id {
return nil
}
if l.BelongsToRepo() && l.RepoID != id {
return nil
}
if _, err = db.DeleteByID[Label](ctx, labelID); err != nil {
return err
} else if _, err = db.GetEngine(ctx).
Where("label_id = ?", labelID). Where("label_id = ?", labelID).
Delete(new(IssueLabel)); err != nil { Delete(new(IssueLabel)); err != nil {
return err return err
@@ -429,13 +433,12 @@ func GetLabelsByRepoID(ctx context.Context, repoID int64, sortType string, listO
case "reversealphabetically": case "reversealphabetically":
sess.Desc("name") sess.Desc("name")
case "leastissues": case "leastissues":
sess.OrderBy("num_issues - num_closed_issues ASC") sess.Asc("num_issues")
case "mostissues": case "mostissues":
sess.OrderBy("num_issues - num_closed_issues DESC") sess.Desc("num_issues")
default: default:
sess.Asc("name") sess.Asc("name")
} }
sess.Asc("id")
if listOptions.Page > 0 { if listOptions.Page > 0 {
db.SetSessionPagination(sess, &listOptions) db.SetSessionPagination(sess, &listOptions)
@@ -505,13 +508,12 @@ func GetLabelsByOrgID(ctx context.Context, orgID int64, sortType string, listOpt
case "reversealphabetically": case "reversealphabetically":
sess.Desc("name") sess.Desc("name")
case "leastissues": case "leastissues":
sess.OrderBy("num_issues - num_closed_issues ASC") sess.Asc("num_issues")
case "mostissues": case "mostissues":
sess.OrderBy("num_issues - num_closed_issues DESC") sess.Desc("num_issues")
default: default:
sess.Asc("name") sess.Asc("name")
} }
sess.Asc("id")
if listOptions.Page > 0 { if listOptions.Page > 0 {
db.SetSessionPagination(sess, &listOptions) db.SetSessionPagination(sess, &listOptions)
+4 -24
View File
@@ -191,8 +191,6 @@ func TestGetLabelsInRepoByIDs(t *testing.T) {
func TestGetLabelsByRepoID(t *testing.T) { func TestGetLabelsByRepoID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
_, err := db.GetEngine(t.Context()).ID(2).Cols("num_issues", "num_closed_issues").Update(&issues_model.Label{NumIssues: 3, NumClosedIssues: 3})
assert.NoError(t, err)
testSuccess := func(repoID int64, sortType string, expectedIssueIDs []int64) { testSuccess := func(repoID int64, sortType string, expectedIssueIDs []int64) {
labels, err := issues_model.GetLabelsByRepoID(t.Context(), repoID, sortType, db.ListOptions{}) labels, err := issues_model.GetLabelsByRepoID(t.Context(), repoID, sortType, db.ListOptions{})
assert.NoError(t, err) assert.NoError(t, err)
@@ -260,8 +258,6 @@ func TestGetLabelsInOrgByIDs(t *testing.T) {
func TestGetLabelsByOrgID(t *testing.T) { func TestGetLabelsByOrgID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
_, err := db.GetEngine(t.Context()).ID(3).Cols("num_issues", "num_closed_issues").Update(&issues_model.Label{NumIssues: 3, NumClosedIssues: 3})
assert.NoError(t, err)
testSuccess := func(orgID int64, sortType string, expectedIssueIDs []int64) { testSuccess := func(orgID int64, sortType string, expectedIssueIDs []int64) {
labels, err := issues_model.GetLabelsByOrgID(t.Context(), orgID, sortType, db.ListOptions{}) labels, err := issues_model.GetLabelsByOrgID(t.Context(), orgID, sortType, db.ListOptions{})
assert.NoError(t, err) assert.NoError(t, err)
@@ -275,7 +271,7 @@ func TestGetLabelsByOrgID(t *testing.T) {
testSuccess(3, "reversealphabetically", []int64{4, 3}) testSuccess(3, "reversealphabetically", []int64{4, 3})
testSuccess(3, "default", []int64{3, 4}) testSuccess(3, "default", []int64{3, 4})
_, err = issues_model.GetLabelsByOrgID(t.Context(), 0, "leastissues", db.ListOptions{}) _, err := issues_model.GetLabelsByOrgID(t.Context(), 0, "leastissues", db.ListOptions{})
assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) assert.True(t, issues_model.IsErrOrgLabelNotExist(err))
_, err = issues_model.GetLabelsByOrgID(t.Context(), -1, "leastissues", db.ListOptions{}) _, err = issues_model.GetLabelsByOrgID(t.Context(), -1, "leastissues", db.ListOptions{})
@@ -324,29 +320,13 @@ func TestUpdateLabel(t *testing.T) {
func TestDeleteLabel(t *testing.T) { func TestDeleteLabel(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
orgLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 3}) assert.NoError(t, issues_model.DeleteLabel(t.Context(), label.RepoID, label.ID))
repoLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 10, RepoID: orgLabel.OrgID})
assert.NoError(t, issues_model.DeleteOrgLabel(t.Context(), repoLabel.RepoID, repoLabel.ID))
unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: repoLabel.ID})
assert.NoError(t, issues_model.DeleteRepoLabel(t.Context(), orgLabel.OrgID, orgLabel.ID))
unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: orgLabel.ID})
assert.NoError(t, issues_model.DeleteOrgLabel(t.Context(), unittest.NonexistentID, orgLabel.ID))
unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: orgLabel.ID})
assert.NoError(t, issues_model.DeleteOrgLabel(t.Context(), orgLabel.OrgID, orgLabel.ID))
unittest.AssertNotExistsBean(t, &issues_model.Label{ID: orgLabel.ID})
assert.NoError(t, issues_model.DeleteRepoLabel(t.Context(), unittest.NonexistentID, label.ID))
unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: label.ID})
assert.NoError(t, issues_model.DeleteRepoLabel(t.Context(), label.RepoID, label.ID))
unittest.AssertNotExistsBean(t, &issues_model.Label{ID: label.ID, RepoID: label.RepoID}) unittest.AssertNotExistsBean(t, &issues_model.Label{ID: label.ID, RepoID: label.RepoID})
assert.NoError(t, issues_model.DeleteRepoLabel(t.Context(), label.RepoID, label.ID)) assert.NoError(t, issues_model.DeleteLabel(t.Context(), label.RepoID, label.ID))
unittest.AssertNotExistsBean(t, &issues_model.Label{ID: label.ID}) unittest.AssertNotExistsBean(t, &issues_model.Label{ID: label.ID})
assert.NoError(t, issues_model.DeleteRepoLabel(t.Context(), unittest.NonexistentID, unittest.NonexistentID)) assert.NoError(t, issues_model.DeleteLabel(t.Context(), unittest.NonexistentID, unittest.NonexistentID))
assert.NoError(t, issues_model.DeleteOrgLabel(t.Context(), unittest.NonexistentID, unittest.NonexistentID))
unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{}) unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{})
} }
+12 -5
View File
@@ -113,29 +113,36 @@ func (milestones MilestoneList) LoadTotalTrackedTimes(ctx context.Context) error
trackedTimes := make(map[int64]int64, len(milestones)) trackedTimes := make(map[int64]int64, len(milestones))
// Get total tracked time by milestone_id // Get total tracked time by milestone_id
var totalTimesByMilestones []*totalTimesByMilestone rows, err := db.GetEngine(ctx).Table("issue").
err := db.GetEngine(ctx).Table("issue").
Join("INNER", "milestone", "issue.milestone_id = milestone.id"). Join("INNER", "milestone", "issue.milestone_id = milestone.id").
Join("LEFT", "tracked_time", "tracked_time.issue_id = issue.id"). Join("LEFT", "tracked_time", "tracked_time.issue_id = issue.id").
Where("tracked_time.deleted = ?", false). Where("tracked_time.deleted = ?", false).
Select("milestone_id, sum(time) as time"). Select("milestone_id, sum(time) as time").
In("milestone_id", milestones.getMilestoneIDs()). In("milestone_id", milestones.getMilestoneIDs()).
GroupBy("milestone_id"). GroupBy("milestone_id").
Find(&totalTimesByMilestones) Rows(new(totalTimesByMilestone))
if err != nil { if err != nil {
return err return err
} }
for _, totalTime := range totalTimesByMilestones { defer rows.Close()
for rows.Next() {
var totalTime totalTimesByMilestone
err = rows.Scan(&totalTime)
if err != nil {
return err
}
trackedTimes[totalTime.MilestoneID] = totalTime.Time trackedTimes[totalTime.MilestoneID] = totalTime.Time
} }
for _, milestone := range milestones { for _, milestone := range milestones {
milestone.TotalTrackedTime = trackedTimes[milestone.ID] milestone.TotalTrackedTime = trackedTimes[milestone.ID]
} }
return nil return nil
} }
// CountMilestonesMap map from repo conditions and the keyword of milestones' name to number of milestones matching the options` // CountMilestonesByRepoCondAndKw map from repo conditions and the keyword of milestones' name to number of milestones matching the options`
func CountMilestonesMap(ctx context.Context, opts FindMilestoneOptions) (map[int64]int64, error) { func CountMilestonesMap(ctx context.Context, opts FindMilestoneOptions) (map[int64]int64, error) {
sess := db.GetEngine(ctx).Where(opts.ToConds()) sess := db.GetEngine(ctx).Where(opts.ToConds())
+106 -80
View File
@@ -58,18 +58,15 @@ func (err ErrReactionAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist return util.ErrAlreadyExist
} }
// Reaction represents a reaction on an issue, comment or release. // Reaction represents a reactions on issues and comments.
type Reaction struct { type Reaction struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
Type string `xorm:"INDEX UNIQUE(s) NOT NULL"`
UserID int64 `xorm:"UNIQUE(s) NOT NULL"` // first unique column implies INDEX IssueID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
IssueID int64 `xorm:"UNIQUE(s) INDEX(issue_comment) NOT NULL"` CommentID int64 `xorm:"INDEX UNIQUE(s)"`
CommentID int64 `xorm:"UNIQUE(s) INDEX(issue_comment) INDEX NOT NULL"` UserID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"`
ReleaseID int64 `xorm:"UNIQUE(s) INDEX NOT NULL DEFAULT(0)"` OriginalAuthorID int64 `xorm:"INDEX UNIQUE(s) NOT NULL DEFAULT(0)"`
OriginalAuthorID int64 `xorm:"UNIQUE(s) NOT NULL DEFAULT(0)"` OriginalAuthor string `xorm:"INDEX UNIQUE(s)"`
OriginalAuthor string `xorm:"UNIQUE(s) NOT NULL DEFAULT('')"`
Type string `xorm:"UNIQUE(s) NOT NULL"`
User *user_model.User `xorm:"-"` User *user_model.User `xorm:"-"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
} }
@@ -111,48 +108,53 @@ func init() {
// FindReactionsOptions describes the conditions to Find reactions // FindReactionsOptions describes the conditions to Find reactions
type FindReactionsOptions struct { type FindReactionsOptions struct {
db.ListOptions db.ListOptions
IssueIDOnly int64 IssueID int64
IssueIDWithComments int64
CommentID int64 CommentID int64
ReleaseID int64
UserID int64 UserID int64
Reaction string
} }
func (opts *FindReactionsOptions) toConds() builder.Cond { func (opts *FindReactionsOptions) toConds() builder.Cond {
// If Issue ID is set add to Query // If Issue ID is set add to Query
cond := builder.NewCond() cond := builder.NewCond()
if opts.IssueIDOnly > 0 { if opts.IssueID > 0 {
cond = cond.And(builder.Eq{"reaction.issue_id": opts.IssueIDOnly}).And(builder.Expr("reaction.comment_id = 0")) cond = cond.And(builder.Eq{"reaction.issue_id": opts.IssueID})
}
if opts.IssueIDWithComments > 0 {
cond = cond.And(builder.Eq{"reaction.issue_id": opts.IssueIDWithComments})
} }
// If CommentID is > 0 add to Query
// If it is 0 Query ignore CommentID to select
// If it is -1 it explicit search of Issue Reactions where CommentID = 0
if opts.CommentID > 0 { if opts.CommentID > 0 {
cond = cond.And(builder.Eq{"reaction.comment_id": opts.CommentID}) cond = cond.And(builder.Eq{"reaction.comment_id": opts.CommentID})
} } else if opts.CommentID == -1 {
if opts.ReleaseID > 0 { cond = cond.And(builder.Eq{"reaction.comment_id": 0})
cond = cond.And(builder.Eq{"reaction.release_id": opts.ReleaseID})
} }
if opts.UserID > 0 { if opts.UserID > 0 {
cond = cond.And(builder.Eq{"reaction.user_id": opts.UserID, "reaction.original_author_id": 0}) cond = cond.And(builder.Eq{
"reaction.user_id": opts.UserID,
"reaction.original_author_id": 0,
})
} }
if !cond.IsValid() { if opts.Reaction != "" {
setting.PanicInDevOrTesting("FindReactionsOptions.toConds: no conditions specified") cond = cond.And(builder.Eq{"reaction.type": opts.Reaction})
cond = cond.And(builder.Expr("1=0"))
} }
return cond return cond
} }
// FindCommentReactions returns a ReactionList of all reactions from a comment // FindCommentReactions returns a ReactionList of all reactions from an comment
func FindCommentReactions(ctx context.Context, commentID int64) (ReactionList, int64, error) { func FindCommentReactions(ctx context.Context, issueID, commentID int64) (ReactionList, int64, error) {
return FindReactions(ctx, FindReactionsOptions{CommentID: commentID}) return FindReactions(ctx, FindReactionsOptions{
IssueID: issueID,
CommentID: commentID,
})
} }
// FindIssueReactions returns a ReactionList of all reactions from an issue // FindIssueReactions returns a ReactionList of all reactions from an issue
func FindIssueReactions(ctx context.Context, issueID int64, listOptions db.ListOptions) (ReactionList, int64, error) { func FindIssueReactions(ctx context.Context, issueID int64, listOptions db.ListOptions) (ReactionList, int64, error) {
return FindReactions(ctx, FindReactionsOptions{ return FindReactions(ctx, FindReactionsOptions{
ListOptions: listOptions, ListOptions: listOptions,
IssueIDOnly: issueID, IssueID: issueID,
CommentID: -1,
}) })
} }
@@ -175,14 +177,37 @@ func FindReactions(ctx context.Context, opts FindReactionsOptions) (ReactionList
return reactions, count, err return reactions, count, err
} }
func FindReleaseReactions(ctx context.Context, releaseIDs ...int64) (ReactionList, error) { func createReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, error) {
reactions := make(ReactionList, 0, 10) reaction := &Reaction{
err := db.GetEngine(ctx). Type: opts.Type,
In("release_id", releaseIDs). UserID: opts.DoerID,
In("`type`", setting.UI.ReleaseReactions). IssueID: opts.IssueID,
Asc("created_unix", "id"). CommentID: opts.CommentID,
Find(&reactions) }
return reactions, err findOpts := FindReactionsOptions{
IssueID: opts.IssueID,
CommentID: opts.CommentID,
Reaction: opts.Type,
UserID: opts.DoerID,
}
if findOpts.CommentID == 0 {
// explicit search of Issue Reactions where CommentID = 0
findOpts.CommentID = -1
}
existingR, _, err := FindReactions(ctx, findOpts)
if err != nil {
return nil, err
}
if len(existingR) > 0 {
return existingR[0], ErrReactionAlreadyExist{Reaction: opts.Type}
}
if err := db.Insert(ctx, reaction); err != nil {
return nil, err
}
return reaction, nil
} }
// ReactionOptions defines options for creating or deleting reactions // ReactionOptions defines options for creating or deleting reactions
@@ -191,63 +216,56 @@ type ReactionOptions struct {
DoerID int64 DoerID int64
IssueID int64 IssueID int64
CommentID int64 CommentID int64
ReleaseID int64
} }
// CreateReaction creates reaction for issue, comment or release. // CreateReaction creates reaction for issue or comment.
func CreateReaction(ctx context.Context, allowed container.Set[string], opts *ReactionOptions) (*Reaction, error) { func CreateReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, error) {
if !allowed.Contains(opts.Type) { if !setting.UI.ReactionsLookup.Contains(opts.Type) {
return nil, ErrForbiddenIssueReaction{opts.Type} return nil, ErrForbiddenIssueReaction{opts.Type}
} }
return db.WithTx2(ctx, func(ctx context.Context) (*Reaction, error) {
return createReaction(ctx, opts)
})
}
// DeleteReaction deletes reaction for issue or comment.
func DeleteReaction(ctx context.Context, opts *ReactionOptions) error {
reaction := &Reaction{ reaction := &Reaction{
Type: opts.Type, Type: opts.Type,
UserID: opts.DoerID, UserID: opts.DoerID,
IssueID: opts.IssueID, IssueID: opts.IssueID,
CommentID: opts.CommentID, CommentID: opts.CommentID,
ReleaseID: opts.ReleaseID,
} }
err := db.Insert(ctx, reaction) sess := db.GetEngine(ctx).Where("original_author_id = 0")
if err != nil { if opts.CommentID == -1 {
if has, _ := db.GetEngine(ctx).Get(reaction); has { reaction.CommentID = 0
return reaction, ErrReactionAlreadyExist{Reaction: opts.Type} sess.MustCols("comment_id")
}
return nil, err
}
return reaction, nil
} }
_, err := sess.Delete(reaction)
return err
}
// DeleteIssueReaction deletes a reaction on issue.
func DeleteIssueReaction(ctx context.Context, doerID, issueID int64, content string) error { func DeleteIssueReaction(ctx context.Context, doerID, issueID int64, content string) error {
// here and below: the migration reactions have unrelated user_id (might be the doer?) return DeleteReaction(ctx, &ReactionOptions{
// so skip the deletion if the reaction is from an external user (original_author_id != 0) Type: content,
sess := db.GetEngine(ctx).Where("issue_id = ? AND comment_id = 0 AND user_id = ? AND original_author_id = 0 AND `type` = ?", issueID, doerID, content) DoerID: doerID,
_, err := sess.Delete(&Reaction{}) IssueID: issueID,
return err CommentID: -1,
})
} }
// DeleteCommentReaction deletes a reaction on comment.
func DeleteCommentReaction(ctx context.Context, doerID, issueID, commentID int64, content string) error { func DeleteCommentReaction(ctx context.Context, doerID, issueID, commentID int64, content string) error {
sess := db.GetEngine(ctx).Where("issue_id = ? AND comment_id = ? AND user_id = ? AND original_author_id = 0 AND `type` = ?", issueID, commentID, doerID, content) return DeleteReaction(ctx, &ReactionOptions{
_, err := sess.Delete(&Reaction{}) Type: content,
return err DoerID: doerID,
} IssueID: issueID,
CommentID: commentID,
func DeleteCommentAllReactions(ctx context.Context, commentID int64) error { })
sess := db.GetEngine(ctx).Where("comment_id = ?", commentID)
_, err := sess.Delete(&Reaction{})
return err
}
func DeleteUserReactions(ctx context.Context, userID int64) error {
sess := db.GetEngine(ctx).Where("user_id = ?", userID)
_, err := sess.Delete(&Reaction{})
return err
}
func DeleteReleaseReaction(ctx context.Context, doerID, releaseID int64, content string) error {
sess := db.GetEngine(ctx).Where("release_id = ? AND user_id = ? AND `type` = ?", releaseID, doerID, content)
_, err := sess.Delete(&Reaction{})
return err
} }
// ReactionList represents list of reactions // ReactionList represents list of reactions
@@ -284,15 +302,23 @@ func (list ReactionList) getUserIDs() []int64 {
}) })
} }
func valuesUser(m map[int64]*user_model.User) []*user_model.User {
values := make([]*user_model.User, 0, len(m))
for _, v := range m {
values = append(values, v)
}
return values
}
// newMigrationOriginalUser creates and returns a fake user for external user // newMigrationOriginalUser creates and returns a fake user for external user
func newMigrationOriginalUser(name string) *user_model.User { func newMigrationOriginalUser(name string) *user_model.User {
return &user_model.User{ID: 0, Name: name, LowerName: strings.ToLower(name)} return &user_model.User{ID: 0, Name: name, LowerName: strings.ToLower(name)}
} }
// LoadUsers loads reactions' all users // LoadUsers loads reactions' all users
func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Repository) (map[int64]*user_model.User, error) { func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Repository) ([]*user_model.User, error) {
if len(list) == 0 { if len(list) == 0 {
return map[int64]*user_model.User{}, nil return nil, nil
} }
userIDs := list.getUserIDs() userIDs := list.getUserIDs()
@@ -313,7 +339,7 @@ func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Reposit
reaction.User = user_model.NewGhostUser() reaction.User = user_model.NewGhostUser()
} }
} }
return userMaps, nil return valuesUser(userMaps), nil
} }
// GetFirstUsers returns first reacted user display names separated by comma // GetFirstUsers returns first reacted user display names separated by comma
@@ -324,7 +350,7 @@ func (list ReactionList) GetFirstUsers() string {
if buffer.Len() > 0 { if buffer.Len() > 0 {
buffer.WriteString(", ") buffer.WriteString(", ")
} }
buffer.WriteString(reaction.User.DisplayNameWithFullName()) buffer.WriteString(reaction.User.Name)
if rem--; rem == 0 { if rem--; rem == 0 {
break break
} }
+6 -4
View File
@@ -428,6 +428,9 @@ func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error
// GetCurrentReview returns the current pending review of reviewer for given issue // GetCurrentReview returns the current pending review of reviewer for given issue
func GetCurrentReview(ctx context.Context, reviewer *user_model.User, issue *Issue) (*Review, error) { func GetCurrentReview(ctx context.Context, reviewer *user_model.User, issue *Issue) (*Review, error) {
if reviewer == nil {
return nil, nil //nolint:nilnil // return nil when reviewer is nil
}
reviews, err := FindReviews(ctx, FindReviewOptions{ reviews, err := FindReviews(ctx, FindReviewOptions{
Types: []ReviewType{ReviewTypePending}, Types: []ReviewType{ReviewTypePending},
IssueID: issue.ID, IssueID: issue.ID,
@@ -439,10 +442,9 @@ func GetCurrentReview(ctx context.Context, reviewer *user_model.User, issue *Iss
if len(reviews) == 0 { if len(reviews) == 0 {
return nil, ErrReviewNotExist{} return nil, ErrReviewNotExist{}
} }
pendingReview := reviews[0] reviews[0].Reviewer = reviewer
pendingReview.Reviewer = reviewer reviews[0].Issue = issue
pendingReview.Issue = issue return reviews[0], nil
return pendingReview, nil
} }
// ReviewExists returns whether a review exists for a particular line of code in the PR // ReviewExists returns whether a review exists for a particular line of code in the PR
+1 -1
View File
@@ -433,7 +433,7 @@ func TestRecalculateReviewsOfficial(t *testing.T) {
// Once the reviewer is whitelisted, re-evaluating restores the official flag. // Once the reviewer is whitelisted, re-evaluating restores the official flag.
rule.ApprovalsWhitelistUserIDs = []int64{2, reviewer.ID} rule.ApprovalsWhitelistUserIDs = []int64{2, reviewer.ID}
_, err = db.GetEngine(t.Context()).ID(rule.ID).Cols("config").Update(rule) _, err = db.GetEngine(t.Context()).ID(rule.ID).Cols("approvals_whitelist_user_i_ds").Update(rule)
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, issues_model.RecalculateReviewsOfficial(t.Context(), issue)) assert.NoError(t, issues_model.RecalculateReviewsOfficial(t.Context(), issue))
+3 -3
View File
@@ -178,7 +178,7 @@ type FindOrgMembersOpts struct {
} }
func (opts FindOrgMembersOpts) PublicOnly() bool { func (opts FindOrgMembersOpts) PublicOnly() bool {
return !user_model.DoerCanViewPrivate(opts.Doer) || !(opts.IsDoerMember || user_model.DoerIsSiteAdminFullAccess(opts.Doer)) return opts.Doer == nil || !(opts.IsDoerMember || opts.Doer.IsAdmin)
} }
// applyKeywordFilter adds keyword search conditions to session // applyKeywordFilter adds keyword search conditions to session
@@ -456,8 +456,8 @@ func ownerVisibilitySatisfiesDoer(orgOrUser, user *user_model.User) bool {
// HasOrgOrUserVisible tells if the given user can see the given org or user // HasOrgOrUserVisible tells if the given user can see the given org or user
func HasOrgOrUserVisible(ctx context.Context, owner, doer *user_model.User) bool { func HasOrgOrUserVisible(ctx context.Context, owner, doer *user_model.User) bool {
return user_model.DoerTokenAllowsOwner(doer, owner) && (ownerVisibilitySatisfiesDoer(owner, doer) || return ownerVisibilitySatisfiesDoer(owner, doer) ||
(doer != nil && OrgFromUser(owner).HasMemberWithUserID(ctx, doer.ID))) (doer != nil && OrgFromUser(owner).HasMemberWithUserID(ctx, doer.ID))
} }
// HasOrgsVisible tells if the given user can see at least one of the orgs provided // HasOrgsVisible tells if the given user can see at least one of the orgs provided
+7 -1
View File
@@ -54,6 +54,12 @@ type FindOrgOptions struct {
IncludeVisibility structs.VisibleType IncludeVisibility structs.VisibleType
} }
func (opts *FindOrgOptions) ApplyPublicOnly(publicOnly bool) {
if publicOnly {
opts.IncludeVisibility = structs.VisibleTypePublic
}
}
func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder { func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
cond := builder.Eq{"uid": userID} cond := builder.Eq{"uid": userID}
if !includePrivate { if !includePrivate {
@@ -77,7 +83,7 @@ func (opts FindOrgOptions) ToOrders() string {
} }
func DoerViewOtherVisibility(doer, other *user_model.User) structs.VisibleType { func DoerViewOtherVisibility(doer, other *user_model.User) structs.VisibleType {
if other == nil || !user_model.DoerCanViewPrivate(doer) { if doer == nil || other == nil {
return structs.VisibleTypePublic return structs.VisibleTypePublic
} }
if doer.IsAdmin || doer.ID == other.ID { if doer.IsAdmin || doer.ID == other.ID {
+7 -19
View File
@@ -58,30 +58,18 @@ func (opts *SearchOptions) toConds() builder.Cond {
return cond return cond
} }
func SearchLatestVersions(ctx context.Context, opts *SearchOptions) (latest []*packages.PackageVersion, err error) { func SearchLatestVersions(ctx context.Context, opts *SearchOptions) ([]*packages.PackageVersion, error) {
var allPkgVers []*packages.PackageVersion sess := db.GetEngine(ctx).
err = db.GetEngine(ctx).
Table("package_version"). Table("package_version").
Select("package_version.*"). Select("package_version.*").
Join("LEFT", "package_version pv2", builder.Expr("package_version.package_id = pv2.package_id AND pv2.is_internal = ? AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))", false)).
Join("INNER", "package", "package.id = package_version.package_id"). Join("INNER", "package", "package.id = package_version.package_id").
Join("INNER", "package_file", "package_file.version_id = package_version.id"). Join("INNER", "package_file", "package_file.version_id = package_version.id").
Where(opts.toConds()). Where(opts.toConds().And(builder.Expr("pv2.id IS NULL"))).
Asc("package.name", "package.id"). Asc("package.name")
Find(&allPkgVers)
if err != nil {
return nil, err
}
// SQL can't order R version strings, so pick the highest version per package here pvs := make([]*packages.PackageVersion, 0, 10)
for _, pv := range allPkgVers { return pvs, sess.Find(&pvs)
curIdx := len(latest) - 1
if curIdx == -1 || latest[curIdx].PackageID != pv.PackageID {
latest = append(latest, pv) // start a new package version
} else if cran_module.CompareVersions(pv.Version, latest[curIdx].Version) > 0 {
latest[curIdx] = pv // replace with a higher version
}
}
return latest, nil
} }
func SearchFile(ctx context.Context, opts *SearchOptions) (*packages.PackageFile, error) { func SearchFile(ctx context.Context, opts *SearchOptions) (*packages.PackageFile, error) {
+1 -4
View File
@@ -123,7 +123,7 @@ func GetTotalUnreferencedBlobSize(ctx context.Context) (int64, error) {
// IsBlobAccessibleForUser tests if the user has access to the blob // IsBlobAccessibleForUser tests if the user has access to the blob
func IsBlobAccessibleForUser(ctx context.Context, blobID int64, user *user_model.User) (bool, error) { func IsBlobAccessibleForUser(ctx context.Context, blobID int64, user *user_model.User) (bool, error) {
if user_model.DoerIsSiteAdminFullAccess(user) { if user.IsAdmin {
return true, nil return true, nil
} }
ownerVisibilities := []structs.VisibleType{structs.VisibleTypePublic} ownerVisibilities := []structs.VisibleType{structs.VisibleTypePublic}
@@ -153,9 +153,6 @@ func IsBlobAccessibleForUser(ctx context.Context, blobID int64, user *user_model
Or(builder.Eq{"`user`.type": user_model.UserTypeOrganization}. Or(builder.Eq{"`user`.type": user_model.UserTypeOrganization}.
And(builder.Lte{strconv.Itoa(int(perm.AccessModeRead)): maxTeamAuthorize}.Or(builder.Lte{strconv.Itoa(int(perm.AccessModeRead)): maxTeamUnitAccessMode}))), And(builder.Lte{strconv.Itoa(int(perm.AccessModeRead)): maxTeamAuthorize}.Or(builder.Lte{strconv.Itoa(int(perm.AccessModeRead)): maxTeamUnitAccessMode}))),
) )
if !user_model.DoerCanViewPrivate(user) {
cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})
}
return db.GetEngine(ctx). return db.GetEngine(ctx).
Table("package_blob"). Table("package_blob").
+1 -8
View File
@@ -424,9 +424,6 @@ func GetDoerRepoPermission(ctx context.Context, repo *repo_model.Repository, use
// In most request paths, callers should use GetDoerRepoPermission instead. // In most request paths, callers should use GetDoerRepoPermission instead.
// Unlike GetDoerRepoPermission, this helper does not resolve Actions task users. // Unlike GetDoerRepoPermission, this helper does not resolve Actions task users.
func GetIndividualUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) { func GetIndividualUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
if !repo_model.DoerTokenAllowsRepo(ctx, user, repo) {
return PermissionNoAccess(), nil
}
defer func() { defer func() {
if err == nil { if err == nil {
finalProcessRepoUnitPermission(user, &perm) finalProcessRepoUnitPermission(user, &perm)
@@ -678,14 +675,10 @@ func PermissionNoAccess() Permission {
// permission on a repository. Producers and consumers must agree on it, so it lives here. // permission on a repository. Producers and consumers must agree on it, so it lives here.
func RepoUserPermissionCacheKey(repoID int64, doer *user_model.User) string { func RepoUserPermissionCacheKey(repoID int64, doer *user_model.User) string {
var doerID int64 var doerID int64
var doerExtData string
if doer != nil { if doer != nil {
doerID = doer.ID doerID = doer.ID
if doer.ExtDoerData != nil {
doerExtData = doer.ExtDoerData.EncodeToString()
} }
} return fmt.Sprintf("%d-%d", repoID, doerID)
return fmt.Sprintf("%d-%d-%s", repoID, doerID, doerExtData)
} }
// CanReadWorkflowCrossRepo checks whether the run can read workflow files from targetRepo. // CanReadWorkflowCrossRepo checks whether the run can read workflow files from targetRepo.
@@ -272,21 +272,4 @@ func testGetDoerRepoPermission(t *testing.T) {
individualPerm, err := GetIndividualUserRepoPermission(ctx, repo1, regularUser) individualPerm, err := GetIndividualUserRepoPermission(ctx, repo1, regularUser)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, individualPerm, doerPerm) assert.Equal(t, individualPerm, doerPerm)
t.Run("PublicOnlyTokenReachesOnlyPublicReposOfPublicOwners", func(t *testing.T) {
publicOnlyUser := *regularUser
publicOnlyUser.ExtDoerData = user_model.NewTokenExtDoerData(user_model.CredentialAccessToken, 1, "public-only,write:repository")
publicOnlyAdmin := *unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
publicOnlyAdmin.ExtDoerData = publicOnlyUser.ExtDoerData
for _, repoID := range []int64{2, 38} {
perm, err := GetDoerRepoPermission(ctx, unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}), &publicOnlyAdmin)
require.NoError(t, err)
assert.False(t, perm.HasAnyUnitAccessOrPublicAccess(), "repo %d", repoID)
}
perm, err := GetDoerRepoPermission(ctx, repo1, &publicOnlyUser)
require.NoError(t, err)
assert.True(t, perm.CanWrite(unit.TypeCode))
assert.NotEqual(t, RepoUserPermissionCacheKey(repo1.ID, regularUser), RepoUserPermissionCacheKey(repo1.ID, &publicOnlyUser))
})
} }
+1 -1
View File
@@ -50,7 +50,7 @@ func (c *commitChecker) IsCommitIDExisting(commitID string) bool {
c.gitRepo, c.gitRepoCloser = r, closer c.gitRepo, c.gitRepoCloser = r, closer
} }
exist = c.gitRepo.IsReferenceExist(c.ctx, commitID) exist = c.gitRepo.IsReferenceExist(c.ctx, commitID) // Don't use IsObjectExist since it doesn't support short hashes with gogit edition.
c.commitCache[commitID] = exist c.commitCache[commitID] = exist
return exist return exist
} }
-18
View File
@@ -9,7 +9,6 @@ import (
repo_model "gitea.dev/models/repo" repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest" "gitea.dev/models/unittest"
"gitea.dev/modules/markup/markdown" "gitea.dev/modules/markup/markdown"
"gitea.dev/modules/test"
_ "gitea.dev/modules/markup/orgmode" _ "gitea.dev/modules/markup/orgmode"
@@ -81,23 +80,6 @@ func TestRepoFile(t *testing.T) {
<video src="/user2/repo1/media/commit/1234/my%20dir/LINK"> <video src="/user2/repo1/media/commit/1234/my%20dir/LINK">
</video>`, rendered) </video>`, rendered)
}) })
t.Run("PictureSource", func(t *testing.T) {
rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{CurrentRefSubURL: "branch/main"}).
WithMarkupType(markdown.MarkupName)
rendered, err := testRenderString(rctx, `<picture><source media="(prefers-color-scheme: dark)" srcset="dark.svg"><img src="light.svg"></picture>`)
assert.NoError(t, err)
assert.Equal(t, test.NormalizeHTMLSpaces(`
<p>
<a href="/user2/repo1/src/branch/main/light.svg" target="_blank" rel="nofollow noopener">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="/user2/repo1/media/branch/main/dark.svg"/>
<img src="/user2/repo1/media/branch/main/light.svg"/>
</picture>
</a>
</p>
`), test.NormalizeHTMLSpaces(rendered))
})
} }
func TestRepoFileOrgMode(t *testing.T) { func TestRepoFileOrgMode(t *testing.T) {
+6 -11
View File
@@ -133,9 +133,6 @@ func ExistAttachmentsByUUID(ctx context.Context, uuid string) (bool, error) {
// GetAttachmentsByIssueID returns all attachments of an issue. // GetAttachmentsByIssueID returns all attachments of an issue.
func GetAttachmentsByIssueID(ctx context.Context, issueID int64) ([]*Attachment, error) { func GetAttachmentsByIssueID(ctx context.Context, issueID int64) ([]*Attachment, error) {
if issueID == 0 { // unsaved issue, would match every issue-less attachment
return []*Attachment{}, nil
}
attachments := make([]*Attachment, 0, 10) attachments := make([]*Attachment, 0, 10)
return attachments, db.GetEngine(ctx).Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments) return attachments, db.GetEngine(ctx).Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments)
} }
@@ -260,17 +257,15 @@ func DeleteAttachmentsByRelease(ctx context.Context, releaseID int64) error {
return err return err
} }
func sqlOrphanedAttachmentsWhereCond() string { // CountOrphanedAttachments returns the number of bad attachments
return "(issue_id > 0 and issue_id not in (select id from `issue`))" +
" or (release_id > 0 and release_id not in (select id from `release`))" +
" or (comment_id > 0 and comment_id not in (select id from `comment`))"
}
func CountOrphanedAttachments(ctx context.Context) (int64, error) { func CountOrphanedAttachments(ctx context.Context) (int64, error) {
return db.GetEngine(ctx).Where(sqlOrphanedAttachmentsWhereCond()).Count(new(Attachment)) return db.GetEngine(ctx).Where("(issue_id > 0 and issue_id not in (select id from issue)) or (release_id > 0 and release_id not in (select id from `release`))").
Count(new(Attachment))
} }
// DeleteOrphanedAttachments delete all bad attachments
func DeleteOrphanedAttachments(ctx context.Context) error { func DeleteOrphanedAttachments(ctx context.Context) error {
_, err := db.GetEngine(ctx).Where(sqlOrphanedAttachmentsWhereCond()).Delete(new(Attachment)) _, err := db.GetEngine(ctx).Where("(issue_id > 0 and issue_id not in (select id from issue)) or (release_id > 0 and release_id not in (select id from `release`))").
Delete(new(Attachment))
return err return err
} }
Loaded 100 of 983 files, more files were not shown because too many files have changed in this diff. Show more